feat(ui): Codex 主模型余额为主视觉,Spark 弱化为次要摘要
- 首页卡片:主模型窗口独占进度行,Spark 收为单行小字摘要\n- 详情页:Spark 收进分隔线 + 小标题次要小节,细条低饱和进度\n- 桌面小组件:跳过 SPARK 窗口,仅保留主模型三行布局\n- 新增 window_spark 多语言文案
This commit is contained in:
parent
7e44e545bf
commit
43d8df90f6
@ -297,21 +297,34 @@ internal fun CodexUsageWindows(balance: ServiceBalance) {
|
|||||||
|
|
||||||
val weeklyLabel = stringResource(R.string.window_every_week)
|
val weeklyLabel = stringResource(R.string.window_every_week)
|
||||||
val monthlyLabel = stringResource(R.string.window_every_month)
|
val monthlyLabel = stringResource(R.string.window_every_month)
|
||||||
|
val usageLabel = stringResource(R.string.window_usage)
|
||||||
|
val sparkTitle = stringResource(R.string.window_spark)
|
||||||
|
|
||||||
|
data class UiWindow(val label: String, val isSpark: Boolean, val remainingPct: Int?, val resetAt: Long?)
|
||||||
|
|
||||||
val windows = (0 until windowCount).map { i ->
|
val windows = (0 until windowCount).map { i ->
|
||||||
|
val rawLabel = extras["window_$i.label"] ?: "usage"
|
||||||
val label = normalizeWindowLabel(
|
val label = normalizeWindowLabel(
|
||||||
extras["window_$i.label"] ?: "usage",
|
rawLabel,
|
||||||
weeklyLabel = weeklyLabel,
|
weeklyLabel = weeklyLabel,
|
||||||
monthlyLabel = monthlyLabel,
|
monthlyLabel = monthlyLabel,
|
||||||
usageLabel = stringResource(R.string.window_usage)
|
usageLabel = usageLabel
|
||||||
|
)
|
||||||
|
UiWindow(
|
||||||
|
label = label,
|
||||||
|
isSpark = extras["window_$i.group"] == "SPARK",
|
||||||
|
remainingPct = extras["window_$i.remainingPct"]?.toIntOrNull(),
|
||||||
|
resetAt = extras["window_$i.resetAt"]?.toLongOrNull()?.takeIf { it > 0 }
|
||||||
)
|
)
|
||||||
val remainingPct = extras["window_$i.remainingPct"]?.toIntOrNull()
|
|
||||||
val resetAt = extras["window_$i.resetAt"]?.toLongOrNull()?.takeIf { it > 0 }
|
|
||||||
Triple(label, remainingPct, resetAt)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断是否有 5h 窗口(与语言无关:匹配 "5h"/"5H" 或本地化标签,用于保留空槽位)
|
// 主模型(普通 Codex)窗口独占全部进度行;Spark 独立限额是次要通道,只留一行小字摘要
|
||||||
|
val mainWindows = windows.filter { !it.isSpark }
|
||||||
|
val sparkWindows = windows.filter { it.isSpark }
|
||||||
|
|
||||||
|
// 判断是否有 5h 窗口(与语言无关:匹配 "5h"/"5H" 或本地化标签,仅看主模型窗口)
|
||||||
val fiveHourLabel = stringResource(R.string.window_5h)
|
val fiveHourLabel = stringResource(R.string.window_5h)
|
||||||
val has5h = windows.any { isFiveHourLabel(it.first, fiveHourLabel, stringResource(R.string.window_5h_short)) }
|
val has5h = mainWindows.any { isFiveHourLabel(it.label, fiveHourLabel, stringResource(R.string.window_5h_short)) }
|
||||||
|
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
if (windows.isEmpty()) {
|
if (windows.isEmpty()) {
|
||||||
@ -321,14 +334,26 @@ internal fun CodexUsageWindows(balance: ServiceBalance) {
|
|||||||
if (!has5h) {
|
if (!has5h) {
|
||||||
CompactUsageRowEmpty(label = fiveHourLabel, resetInSec = null)
|
CompactUsageRowEmpty(label = fiveHourLabel, resetInSec = null)
|
||||||
}
|
}
|
||||||
windows.forEach { (label, remainingPct, resetAt) ->
|
mainWindows.forEach { w ->
|
||||||
if (remainingPct != null) {
|
val resetSec = w.resetAt?.let { (it - System.currentTimeMillis()) / 1000 }?.takeIf { it > 0 }
|
||||||
val usedPct = (100 - remainingPct).coerceIn(0, 100)
|
if (w.remainingPct != null) {
|
||||||
CompactUsageRow(label = label, pct = usedPct, resetInSec = resetAt?.let { (it - System.currentTimeMillis()) / 1000 }?.takeIf { it > 0 })
|
CompactUsageRow(label = w.label, pct = (100 - w.remainingPct).coerceIn(0, 100), resetInSec = resetSec)
|
||||||
} else {
|
} else {
|
||||||
CompactUsageRowEmpty(label = label, resetInSec = resetAt?.let { (it - System.currentTimeMillis()) / 1000 }?.takeIf { it > 0 })
|
CompactUsageRowEmpty(label = w.label, resetInSec = resetSec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Spark 次要限额:单行摘要,不与主模型争夺视觉焦点
|
||||||
|
if (sparkWindows.isNotEmpty()) {
|
||||||
|
val summary = sparkWindows.joinToString(" · ") { w ->
|
||||||
|
val used = w.remainingPct?.let { (100 - it).coerceIn(0, 100) }
|
||||||
|
if (used != null) "${w.label} $used%" else "${w.label} —"
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = "$sparkTitle · $summary",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = inkMuted()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -485,10 +485,13 @@ private fun CodexUsageCard(state: State) {
|
|||||||
}
|
}
|
||||||
val extras = balance?.extras ?: return
|
val extras = balance?.extras ?: return
|
||||||
val plan = extras["plan"].orEmpty()
|
val plan = extras["plan"].orEmpty()
|
||||||
val windows = remember(extras) { extractCodexWindows(extras) }
|
val allWindows = remember(extras) { extractCodexWindows(extras) }
|
||||||
if (windows.isEmpty()) return
|
if (allWindows.isEmpty()) return
|
||||||
|
// 主模型窗口占据主视觉;Spark 独立限额收进底部次要小节
|
||||||
|
val windows = allWindows.filter { it.group == CodexWindowGroup.CODEX }
|
||||||
|
val sparkWindows = allWindows.filter { it.group == CodexWindowGroup.SPARK }
|
||||||
|
|
||||||
// 判断是否有 5h 窗口(与语言无关:匹配 "5h"/"5H" 或本地化标签)
|
// 判断是否有 5h 窗口(与语言无关:匹配 "5h"/"5H" 或本地化标签;仅看主模型窗口)
|
||||||
val has5h = windows.any {
|
val has5h = windows.any {
|
||||||
isFiveHourLabel(it.rawLabel, stringResource(R.string.window_5h), stringResource(R.string.window_5h_short))
|
isFiveHourLabel(it.rawLabel, stringResource(R.string.window_5h), stringResource(R.string.window_5h_short))
|
||||||
}
|
}
|
||||||
@ -547,14 +550,100 @@ private fun CodexUsageCard(state: State) {
|
|||||||
decimals = 2
|
decimals = 2
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Spark 独立限额(次要通道):分隔线 + 小标题 + 紧凑细条,视觉权重远低于主模型
|
||||||
|
if (sparkWindows.isNotEmpty()) {
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.window_spark),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = inkMuted()
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
sparkWindows.forEach { window ->
|
||||||
|
val resetInSec = window.resetAt?.let {
|
||||||
|
(it - System.currentTimeMillis()) / 1000
|
||||||
|
}?.takeIf { it > 0 }
|
||||||
|
SparkUsageRow(
|
||||||
|
label = codexWindowLabel(window.rawLabel),
|
||||||
|
pct = window.usedPct,
|
||||||
|
resetInSec = resetInSec
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spark 次要限额的紧凑行:小字号 + 细进度条,视觉权重明显低于主模型的 [UsageWindowRow]。
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun SparkUsageRow(label: String, pct: Float, resetInSec: Long?) {
|
||||||
|
val durationText = DurationText(
|
||||||
|
day = stringResource(R.string.format_day),
|
||||||
|
hour = stringResource(R.string.format_hour),
|
||||||
|
minute = stringResource(R.string.format_minute)
|
||||||
|
)
|
||||||
|
val pctValue = pct.coerceIn(0f, 100f)
|
||||||
|
Column {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = String.format(Locale.US, "%.1f", pctValue),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "%",
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = inkMuted(),
|
||||||
|
modifier = Modifier.padding(start = 1.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
LinearProgressIndicator(
|
||||||
|
progress = { pctValue / 100f },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(2.5.dp)
|
||||||
|
.clip(RoundedCornerShape(1.25.dp)),
|
||||||
|
color = when {
|
||||||
|
pctValue >= 80f -> MaterialTheme.colorScheme.error.copy(alpha = 0.75f)
|
||||||
|
else -> StrawberryPink.copy(alpha = 0.55f)
|
||||||
|
},
|
||||||
|
trackColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
)
|
||||||
|
if (resetInSec != null && resetInSec > 0) {
|
||||||
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.service_reset_in, formatResetInSec(resetInSec, durationText)),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = inkMuted()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class CodexWindowGroup { CODEX, SPARK }
|
||||||
|
|
||||||
private data class CodexWindow(
|
private data class CodexWindow(
|
||||||
val rawLabel: String,
|
val rawLabel: String,
|
||||||
val usedPct: Float,
|
val usedPct: Float,
|
||||||
val resetAt: Long?
|
val resetAt: Long?,
|
||||||
|
val group: CodexWindowGroup = CodexWindowGroup.CODEX
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun extractCodexWindows(extras: Map<String, String>): List<CodexWindow> {
|
private fun extractCodexWindows(extras: Map<String, String>): List<CodexWindow> {
|
||||||
@ -565,7 +654,8 @@ private fun extractCodexWindows(extras: Map<String, String>): List<CodexWindow>
|
|||||||
val remaining = extras["window_${i}.remainingPct"]?.toFloatOrNull() ?: 0f
|
val remaining = extras["window_${i}.remainingPct"]?.toFloatOrNull() ?: 0f
|
||||||
val resetAt = extras["window_${i}.resetAt"]?.toLongOrNull()?.takeIf { it > 0 }
|
val resetAt = extras["window_${i}.resetAt"]?.toLongOrNull()?.takeIf { it > 0 }
|
||||||
val usedPct = (100f - remaining).coerceIn(0f, 100f)
|
val usedPct = (100f - remaining).coerceIn(0f, 100f)
|
||||||
result.add(CodexWindow(rawLabel, usedPct, resetAt))
|
val group = if (extras["window_${i}.group"] == "SPARK") CodexWindowGroup.SPARK else CodexWindowGroup.CODEX
|
||||||
|
result.add(CodexWindow(rawLabel, usedPct, resetAt, group))
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@ -574,14 +664,14 @@ private fun extractCodexWindows(extras: Map<String, String>): List<CodexWindow>
|
|||||||
@Composable
|
@Composable
|
||||||
private fun codexWindowLabel(raw: String): String = when (raw.lowercase()) {
|
private fun codexWindowLabel(raw: String): String = when (raw.lowercase()) {
|
||||||
"5h" -> stringResource(R.string.window_5h)
|
"5h" -> stringResource(R.string.window_5h)
|
||||||
"7d", "weekly", "每周" -> stringResource(R.string.window_weekly)
|
"7d", "weekly", "每周", "每週" -> stringResource(R.string.window_weekly)
|
||||||
"30d", "monthly", "每月" -> stringResource(R.string.window_monthly)
|
"30d", "monthly", "每月" -> stringResource(R.string.window_monthly)
|
||||||
"usage" -> stringResource(R.string.window_usage)
|
"usage" -> stringResource(R.string.window_usage)
|
||||||
else -> raw
|
else -> raw
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ollama Pro 专属:5h + 每周用量窗口卡 + 模型级调用次数。
|
* Ollama 专属:5h + 每周用量窗口卡 + 模型级调用次数。
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun OllamaUsageCard(state: State) {
|
private fun OllamaUsageCard(state: State) {
|
||||||
|
|||||||
@ -205,26 +205,27 @@ class OpenCodeGoWidgetProvider : AppWidgetProvider() {
|
|||||||
val weeklyLabel = context.getString(R.string.window_every_week)
|
val weeklyLabel = context.getString(R.string.window_every_week)
|
||||||
val monthlyLabel = context.getString(R.string.window_every_month)
|
val monthlyLabel = context.getString(R.string.window_every_month)
|
||||||
val usageLabel = context.getString(R.string.window_usage)
|
val usageLabel = context.getString(R.string.window_usage)
|
||||||
val windows = (0 until windowCount).map { index ->
|
// 仅主模型窗口进入桌面组件;Spark 独立限额为次要通道,不占用组件行位
|
||||||
|
val normal = mutableListOf<Triple<String, Int?, Long?>>()
|
||||||
|
for (index in 0 until windowCount) {
|
||||||
|
if (extras["window_$index.group"] == "SPARK") continue
|
||||||
|
val rawLabel = extras["window_$index.label"] ?: "usage"
|
||||||
val label = normalizeWindowLabel(
|
val label = normalizeWindowLabel(
|
||||||
extras["window_$index.label"] ?: "usage",
|
rawLabel,
|
||||||
weeklyLabel = weeklyLabel,
|
weeklyLabel = weeklyLabel,
|
||||||
monthlyLabel = monthlyLabel,
|
monthlyLabel = monthlyLabel,
|
||||||
usageLabel = usageLabel
|
usageLabel = usageLabel
|
||||||
)
|
)
|
||||||
val remaining = extras["window_$index.remainingPct"]?.toIntOrNull()
|
val remaining = extras["window_$index.remainingPct"]?.toIntOrNull()
|
||||||
val resetAt = extras["window_$index.resetAt"]?.toLongOrNull()?.takeIf { it > 0 }
|
val resetAt = extras["window_$index.resetAt"]?.toLongOrNull()?.takeIf { it > 0 }
|
||||||
Triple(label, remaining?.let { (100 - it).coerceIn(0, 100) }, resetAt?.let { (it - System.currentTimeMillis()) / 1000 }?.takeIf { it > 0 })
|
normal.add(Triple(label, remaining?.let { (100 - it).coerceIn(0, 100) }, resetAt?.let { (it - System.currentTimeMillis()) / 1000 }?.takeIf { it > 0 }))
|
||||||
}
|
}
|
||||||
// 判断是否有 5h 窗口
|
// 判断是否有 5h 窗口
|
||||||
val has5h = windows.any { it.first.contains("5") }
|
val has5h = normal.any { it.first.contains("5") }
|
||||||
// 始终保留 5h 槽位在第一行
|
// 始终保留 5h 槽位在第一行(按索引取行,避免内容相同的窗口被误过滤)
|
||||||
val row1 = if (!has5h) {
|
val row1Idx = if (has5h) (normal.indexOfFirst { it.first.contains("5") }.takeIf { it >= 0 } ?: 0) else -1
|
||||||
Triple("5h", null, null)
|
val row1 = if (row1Idx >= 0) normal[row1Idx] else Triple("5h", null, null)
|
||||||
} else {
|
val otherWindows = normal.filterIndexed { idx, _ -> idx != row1Idx }
|
||||||
windows.firstOrNull { it.first.contains("5") } ?: windows.getOrNull(0) ?: Triple("5h", null, null)
|
|
||||||
}
|
|
||||||
val otherWindows = windows.filter { it != row1 }
|
|
||||||
val row2 = otherWindows.getOrNull(0) ?: Triple(weeklyLabel, null, null)
|
val row2 = otherWindows.getOrNull(0) ?: Triple(weeklyLabel, null, null)
|
||||||
val row3 = otherWindows.getOrNull(1) ?: Triple(monthlyLabel, null, null)
|
val row3 = otherWindows.getOrNull(1) ?: Triple(monthlyLabel, null, null)
|
||||||
setRowLabel(views, row1.first, row2.first, row3.first)
|
setRowLabel(views, row1.first, row2.first, row3.first)
|
||||||
|
|||||||
@ -106,7 +106,7 @@
|
|||||||
<string name="service_desc_deepseek">REST API · ¥</string>
|
<string name="service_desc_deepseek">REST API · ¥</string>
|
||||||
<string name="service_desc_opencode_go">WebView 抓取 · 5h 配额</string>
|
<string name="service_desc_opencode_go">WebView 抓取 · 5h 配额</string>
|
||||||
<string name="service_desc_commandcode_go">JSON API · $</string>
|
<string name="service_desc_commandcode_go">JSON API · $</string>
|
||||||
<string name="service_desc_codex">ChatGPT Plus · Codex 额度</string>
|
<string name="service_desc_codex">ChatGPT · Codex 额度</string>
|
||||||
<string name="service_desc_ollama">Cookie 抓取 · Cloud 配额</string>
|
<string name="service_desc_ollama">Cookie 抓取 · Cloud 配额</string>
|
||||||
|
|
||||||
<!-- ══════════ 时长标签(DurationText 注入用) ══════════ -->
|
<!-- ══════════ 时长标签(DurationText 注入用) ══════════ -->
|
||||||
@ -385,7 +385,7 @@
|
|||||||
<string name="help_codex_12"> }}</string>
|
<string name="help_codex_12"> }}</string>
|
||||||
<string name="help_codex_note">提示:导入后 APP 会自动刷新 token,</string>
|
<string name="help_codex_note">提示:导入后 APP 会自动刷新 token,</string>
|
||||||
<string name="help_codex_note2">不需要手动更新。</string>
|
<string name="help_codex_note2">不需要手动更新。</string>
|
||||||
<string name="credential_title_ollama">Ollama Pro 凭据</string>
|
<string name="credential_title_ollama">Ollama 凭据</string>
|
||||||
<string name="credential_hint_ollama">需要从浏览器复制完整 Cookie。登录 ollama.com/settings 后,在 DevTools → Network → 请求头里找到 Cookie 行复制。</string>
|
<string name="credential_hint_ollama">需要从浏览器复制完整 Cookie。登录 ollama.com/settings 后,在 DevTools → Network → 请求头里找到 Cookie 行复制。</string>
|
||||||
<string name="credential_update">更新凭据</string>
|
<string name="credential_update">更新凭据</string>
|
||||||
<string name="credential_save">保存凭据</string>
|
<string name="credential_save">保存凭据</string>
|
||||||
@ -414,7 +414,7 @@
|
|||||||
<string name="api_key_form_title">请输入 API Key</string>
|
<string name="api_key_form_title">请输入 API Key</string>
|
||||||
<string name="api_key_form_hint">凭据会使用 Android Keystore (AES-256 GCM) 加密后存储到本机 DataStore。</string>
|
<string name="api_key_form_hint">凭据会使用 Android Keystore (AES-256 GCM) 加密后存储到本机 DataStore。</string>
|
||||||
<string name="action_update">更新</string>
|
<string name="action_update">更新</string>
|
||||||
<string name="credential_title_codex">Codex / ChatGPT Plus 凭据</string>
|
<string name="credential_title_codex">Codex / ChatGPT 凭据</string>
|
||||||
<string name="credential_hint_codex">推荐使用 OAuth 登录自动获取凭据。也可手动粘贴 auth.json 内容。</string>
|
<string name="credential_hint_codex">推荐使用 OAuth 登录自动获取凭据。也可手动粘贴 auth.json 内容。</string>
|
||||||
<string name="action_oauth_login">🔐 OAuth 登录(推荐)</string>
|
<string name="action_oauth_login">🔐 OAuth 登录(推荐)</string>
|
||||||
<string name="or_manual_import">── 或手动导入 ──</string>
|
<string name="or_manual_import">── 或手动导入 ──</string>
|
||||||
@ -435,6 +435,7 @@
|
|||||||
<string name="error_parse_models_empty">模型列表为空</string>
|
<string name="error_parse_models_empty">模型列表为空</string>
|
||||||
<string name="error_parse_malformed">响应格式异常</string>
|
<string name="error_parse_malformed">响应格式异常</string>
|
||||||
<string name="window_every_week">每周</string>
|
<string name="window_every_week">每周</string>
|
||||||
|
<string name="window_spark">Spark</string>
|
||||||
<string name="msg_model_call_count">模型调用次数</string>
|
<string name="msg_model_call_count">模型调用次数</string>
|
||||||
<string name="msg_stale_cache">缓存数据 · %1$s</string>
|
<string name="msg_stale_cache">缓存数据 · %1$s</string>
|
||||||
<string name="msg_showing_last_balance">显示的是上次成功获取的余额</string>
|
<string name="msg_showing_last_balance">显示的是上次成功获取的余额</string>
|
||||||
|
|||||||
@ -106,7 +106,7 @@
|
|||||||
<string name="service_desc_deepseek">REST API · ¥</string>
|
<string name="service_desc_deepseek">REST API · ¥</string>
|
||||||
<string name="service_desc_opencode_go">WebView 抓取 · 5h 配額</string>
|
<string name="service_desc_opencode_go">WebView 抓取 · 5h 配額</string>
|
||||||
<string name="service_desc_commandcode_go">JSON API · $</string>
|
<string name="service_desc_commandcode_go">JSON API · $</string>
|
||||||
<string name="service_desc_codex">ChatGPT Plus · Codex 額度</string>
|
<string name="service_desc_codex">ChatGPT · Codex 額度</string>
|
||||||
<string name="service_desc_ollama">Cookie 抓取 · Cloud 配額</string>
|
<string name="service_desc_ollama">Cookie 抓取 · Cloud 配額</string>
|
||||||
|
|
||||||
<!-- ══════════ 时长标签(DurationText 注入用) ══════════ -->
|
<!-- ══════════ 时长标签(DurationText 注入用) ══════════ -->
|
||||||
@ -385,7 +385,7 @@
|
|||||||
<string name="help_codex_12"> }}</string>
|
<string name="help_codex_12"> }}</string>
|
||||||
<string name="help_codex_note">提示:匯入後 APP 會自動重新整理 token,</string>
|
<string name="help_codex_note">提示:匯入後 APP 會自動重新整理 token,</string>
|
||||||
<string name="help_codex_note2">不需要手動更新。</string>
|
<string name="help_codex_note2">不需要手動更新。</string>
|
||||||
<string name="credential_title_ollama">Ollama Pro 憑據</string>
|
<string name="credential_title_ollama">Ollama 憑據</string>
|
||||||
<string name="credential_hint_ollama">需要從瀏覽器複製完整 Cookie。登入 ollama.com/settings 後,在 DevTools → Network → 請求頭裡找到 Cookie 行複製。</string>
|
<string name="credential_hint_ollama">需要從瀏覽器複製完整 Cookie。登入 ollama.com/settings 後,在 DevTools → Network → 請求頭裡找到 Cookie 行複製。</string>
|
||||||
<string name="credential_update">更新憑據</string>
|
<string name="credential_update">更新憑據</string>
|
||||||
<string name="credential_save">儲存憑據</string>
|
<string name="credential_save">儲存憑據</string>
|
||||||
@ -414,7 +414,7 @@
|
|||||||
<string name="api_key_form_title">請輸入 API Key</string>
|
<string name="api_key_form_title">請輸入 API Key</string>
|
||||||
<string name="api_key_form_hint">憑據會使用 Android Keystore (AES-256 GCM) 加密後儲存到本機 DataStore。</string>
|
<string name="api_key_form_hint">憑據會使用 Android Keystore (AES-256 GCM) 加密後儲存到本機 DataStore。</string>
|
||||||
<string name="action_update">更新</string>
|
<string name="action_update">更新</string>
|
||||||
<string name="credential_title_codex">Codex / ChatGPT Plus 憑據</string>
|
<string name="credential_title_codex">Codex / ChatGPT 憑據</string>
|
||||||
<string name="credential_hint_codex">推薦使用 OAuth 登入自動獲取憑據。也可手動貼上 auth.json 內容。</string>
|
<string name="credential_hint_codex">推薦使用 OAuth 登入自動獲取憑據。也可手動貼上 auth.json 內容。</string>
|
||||||
<string name="action_oauth_login">🔐 OAuth 登入(推薦)</string>
|
<string name="action_oauth_login">🔐 OAuth 登入(推薦)</string>
|
||||||
<string name="or_manual_import">── 或手動匯入 ──</string>
|
<string name="or_manual_import">── 或手動匯入 ──</string>
|
||||||
@ -435,6 +435,7 @@
|
|||||||
<string name="error_parse_models_empty">模型清單為空</string>
|
<string name="error_parse_models_empty">模型清單為空</string>
|
||||||
<string name="error_parse_malformed">回應格式異常</string>
|
<string name="error_parse_malformed">回應格式異常</string>
|
||||||
<string name="window_every_week">每週</string>
|
<string name="window_every_week">每週</string>
|
||||||
|
<string name="window_spark">Spark</string>
|
||||||
<string name="msg_model_call_count">模型呼叫次數</string>
|
<string name="msg_model_call_count">模型呼叫次數</string>
|
||||||
<string name="msg_stale_cache">快取數據 · %1$s</string>
|
<string name="msg_stale_cache">快取數據 · %1$s</string>
|
||||||
<string name="msg_showing_last_balance">顯示的是上次成功獲取的餘額</string>
|
<string name="msg_showing_last_balance">顯示的是上次成功獲取的餘額</string>
|
||||||
|
|||||||
@ -109,7 +109,7 @@
|
|||||||
<string name="service_desc_deepseek">REST API · ¥</string>
|
<string name="service_desc_deepseek">REST API · ¥</string>
|
||||||
<string name="service_desc_opencode_go">WebView fetch · 5h quota</string>
|
<string name="service_desc_opencode_go">WebView fetch · 5h quota</string>
|
||||||
<string name="service_desc_commandcode_go">JSON API · $</string>
|
<string name="service_desc_commandcode_go">JSON API · $</string>
|
||||||
<string name="service_desc_codex">ChatGPT Plus · Codex quota</string>
|
<string name="service_desc_codex">ChatGPT · Codex quota</string>
|
||||||
<string name="service_desc_ollama">Cookie fetch · Cloud quota</string>
|
<string name="service_desc_ollama">Cookie fetch · Cloud quota</string>
|
||||||
|
|
||||||
<!-- ══════════ 时长标签(DurationText 注入用) ══════════ -->
|
<!-- ══════════ 时长标签(DurationText 注入用) ══════════ -->
|
||||||
@ -388,7 +388,7 @@
|
|||||||
<string name="help_codex_12"> }}</string>
|
<string name="help_codex_12"> }}</string>
|
||||||
<string name="help_codex_note">Tip: the app auto-refreshes the token after import,</string>
|
<string name="help_codex_note">Tip: the app auto-refreshes the token after import,</string>
|
||||||
<string name="help_codex_note2">no manual updates needed.</string>
|
<string name="help_codex_note2">no manual updates needed.</string>
|
||||||
<string name="credential_title_ollama">Ollama Pro credentials</string>
|
<string name="credential_title_ollama">Ollama credentials</string>
|
||||||
<string name="credential_hint_ollama">Copy the full cookie from your browser. After logging into ollama.com/settings, find the Cookie header in DevTools → Network and copy it.</string>
|
<string name="credential_hint_ollama">Copy the full cookie from your browser. After logging into ollama.com/settings, find the Cookie header in DevTools → Network and copy it.</string>
|
||||||
<string name="credential_update">Update credentials</string>
|
<string name="credential_update">Update credentials</string>
|
||||||
<string name="credential_save">Save credentials</string>
|
<string name="credential_save">Save credentials</string>
|
||||||
@ -417,7 +417,7 @@
|
|||||||
<string name="api_key_form_title">Enter API Key</string>
|
<string name="api_key_form_title">Enter API Key</string>
|
||||||
<string name="api_key_form_hint">Credentials are encrypted with Android Keystore (AES-256 GCM) and stored in local DataStore.</string>
|
<string name="api_key_form_hint">Credentials are encrypted with Android Keystore (AES-256 GCM) and stored in local DataStore.</string>
|
||||||
<string name="action_update">Update</string>
|
<string name="action_update">Update</string>
|
||||||
<string name="credential_title_codex">Codex / ChatGPT Plus credentials</string>
|
<string name="credential_title_codex">Codex / ChatGPT credentials</string>
|
||||||
<string name="credential_hint_codex">OAuth login fetches credentials automatically. You can also paste auth.json manually.</string>
|
<string name="credential_hint_codex">OAuth login fetches credentials automatically. You can also paste auth.json manually.</string>
|
||||||
<string name="action_oauth_login">🔐 OAuth login (recommended)</string>
|
<string name="action_oauth_login">🔐 OAuth login (recommended)</string>
|
||||||
<string name="or_manual_import">── or import manually ──</string>
|
<string name="or_manual_import">── or import manually ──</string>
|
||||||
@ -438,6 +438,7 @@
|
|||||||
<string name="error_parse_models_empty">Model list is empty</string>
|
<string name="error_parse_models_empty">Model list is empty</string>
|
||||||
<string name="error_parse_malformed">Response format is abnormal</string>
|
<string name="error_parse_malformed">Response format is abnormal</string>
|
||||||
<string name="window_every_week">Weekly</string>
|
<string name="window_every_week">Weekly</string>
|
||||||
|
<string name="window_spark">Spark</string>
|
||||||
<string name="msg_model_call_count">Model call count</string>
|
<string name="msg_model_call_count">Model call count</string>
|
||||||
<string name="msg_stale_cache">Cached data · %1$s</string>
|
<string name="msg_stale_cache">Cached data · %1$s</string>
|
||||||
<string name="msg_showing_last_balance">Showing the last successfully fetched balance</string>
|
<string name="msg_showing_last_balance">Showing the last successfully fetched balance</string>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user