@goodandready/dsh-clinebot 0.6.9 → 0.6.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +3 -1
- package/README.ru.md +1 -1
- package/README.zh.md +1 -1
- package/lib/account-events.js +85 -0
- package/lib/adapter.js +1 -0
- package/lib/client.js +11 -5
- package/lib/config.js +3 -0
- package/lib/index.js +13 -0
- package/lib/model-prices.js +5 -2
- package/lib/routes/accounts.js +17 -0
- package/lib/session-router.js +38 -2
- package/lib/stats-storage.js +7 -3
- package/lib/transport/cline-transport.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ All notable changes to `@goodandready/dsh-clinebot` will be documented in this f
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.6.11] - 2026-10-21
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Account event history**: cooldowns, automatic/manual recoveries and routing decisions are kept in a 200-entry ring buffer at `eventLogPath`, exposed through `GET /dsh-clinebot/accounts/history` and a new History view in the accounts section.
|
|
12
|
+
- Routing events cover new session assignments, pinned overrides, model routes and mid-session failover.
|
|
13
|
+
|
|
14
|
+
## [0.6.10] - 2026-10-21
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- **Per-account analytics**: the analytics group selector now includes Accounts, and Settings → ClineBot → Stats shows the top accounts by token usage with requests, tokens and estimated cost.
|
|
18
|
+
- **Cache savings**: cache-read savings versus the normal input rate are recorded per request and shown as a total in analytics and the settings stats summary.
|
|
19
|
+
|
|
8
20
|
## [0.6.9] - 2026-10-21
|
|
9
21
|
|
|
10
22
|
### Added
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<h3>Native ClineBot / ClinePass Provider Companion for DeepSeek Harness</h3>
|
|
6
6
|
|
|
7
|
-
<p><b>0.6.
|
|
7
|
+
<p><b>0.6.11</b> adds a persistent account event history for cooldowns, recoveries and routing decisions. Chat runs on the native <code>ctx.llm</code> adapter and requires DSH <code>0.2.1-rc.2</code>; the legacy <code>llm-pi-ai</code>, loopback proxy and <code>llm/stream</code> path are removed.</p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
10
|
<a href="https://www.npmjs.com/package/@goodandready/dsh-clinebot"><img src="https://img.shields.io/npm/v/@goodandready/dsh-clinebot.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
|
|
@@ -199,6 +199,7 @@ dsh-clinebot:
|
|
|
199
199
|
statsPath: ~/.dsh/clinebot-stats.json
|
|
200
200
|
priceCatalogUrl: https://models.dev/api.json
|
|
201
201
|
priceCatalogPath: ~/.dsh/clinebot-prices.json
|
|
202
|
+
eventLogPath: ~/.dsh/clinebot-events.json
|
|
202
203
|
statsRetentionDays: 95
|
|
203
204
|
dailyBudgetUsd: 0
|
|
204
205
|
monthlyBudgetUsd: 0
|
|
@@ -225,6 +226,7 @@ dsh-clinebot:
|
|
|
225
226
|
| `statsPath` | `string` | `"~/.dsh/clinebot-stats.json"` | Persistent on-disk path for token usage analytics |
|
|
226
227
|
| `priceCatalogUrl` | `string` | `"https://models.dev/api.json"` | Public price catalog used by the optional sync |
|
|
227
228
|
| `priceCatalogPath` | `string` | `"~/.dsh/clinebot-prices.json"` | Local cache for the public price catalog |
|
|
229
|
+
| `eventLogPath` | `string` | `"~/.dsh/clinebot-events.json"` | Ring buffer for account cooldown/recovery/routing history |
|
|
228
230
|
| `statsRetentionDays` | `number` | `95` | Retention for day/hour/session telemetry; 0 keeps everything |
|
|
229
231
|
| `dailyBudgetUsd` | `number` | `0` | Optional estimated daily spend guard; 0 disables it |
|
|
230
232
|
| `monthlyBudgetUsd` | `number` | `0` | Optional estimated monthly spend guard; 0 disables it |
|
package/README.ru.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<h3>Нативное подключение провайдера ClineBot / ClinePass для DeepSeek Harness</h3>
|
|
6
6
|
|
|
7
|
-
<p><b>0.6.
|
|
7
|
+
<p><b>0.6.11</b> добавляет постоянную историю событий аккаунтов: cooldown, восстановление и решения роутинга. Чат работает через нативный адаптер <code>ctx.llm</code>, требуется DSH <code>0.2.1-rc.2</code>; старые <code>llm-pi-ai</code>, loopback-прокси и <code>llm/stream</code> удалены.</p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
10
|
<a href="https://www.npmjs.com/package/@goodandready/dsh-clinebot"><img src="https://img.shields.io/npm/v/@goodandready/dsh-clinebot.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
|
package/README.zh.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<h3>适用于 DeepSeek Harness 的 ClineBot / ClinePass 原生模型提供商伴侣插件</h3>
|
|
6
6
|
|
|
7
|
-
<p><b>0.6.
|
|
7
|
+
<p><b>0.6.11</b> 添加持久的账户事件历史:冷却、恢复和路由决策。聊天使用原生 <code>ctx.llm</code> 适配器,需要 DSH <code>0.2.1-rc.2</code>;旧的 <code>llm-pi-ai</code>、回环代理和 <code>llm/stream</code> 已删除。</p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
10
|
<a href="https://www.npmjs.com/package/@goodandready/dsh-clinebot"><img src="https://img.shields.io/npm/v/@goodandready/dsh-clinebot.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, renameSync, mkdirSync, existsSync } from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { resolvePath } from './stats-storage.js'
|
|
4
|
+
|
|
5
|
+
const MAX_EVENTS = 200
|
|
6
|
+
const EVENT_TYPES = new Set(['cooldown', 'recover', 'route'])
|
|
7
|
+
|
|
8
|
+
let events = []
|
|
9
|
+
let activePath = ''
|
|
10
|
+
let saveTimer = null
|
|
11
|
+
|
|
12
|
+
function clean(value, max = 240) {
|
|
13
|
+
return String(value ?? '').trim().slice(0, max)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function sanitize(input = {}) {
|
|
17
|
+
const type = clean(input.type, 20).toLowerCase()
|
|
18
|
+
if (!EVENT_TYPES.has(type)) return null
|
|
19
|
+
const event = {
|
|
20
|
+
at: Number.isFinite(Number(input.at)) ? Number(input.at) : Date.now(),
|
|
21
|
+
type,
|
|
22
|
+
accountId: clean(input.accountId, 160),
|
|
23
|
+
reason: clean(input.reason, 120),
|
|
24
|
+
}
|
|
25
|
+
if (Number.isFinite(Number(input.until))) event.until = Number(input.until)
|
|
26
|
+
if (input.sessionId) event.sessionId = clean(input.sessionId, 160)
|
|
27
|
+
return event.accountId ? event : null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function loadAccountEvents(customPath) {
|
|
31
|
+
const file = customPath ? resolvePath(customPath) : ''
|
|
32
|
+
activePath = file
|
|
33
|
+
events = []
|
|
34
|
+
if (!file || !existsSync(file)) return []
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(readFileSync(file, 'utf8'))
|
|
37
|
+
const source = Array.isArray(parsed) ? parsed : parsed?.events
|
|
38
|
+
events = Array.isArray(source) ? source.map(sanitize).filter(Boolean).slice(-MAX_EVENTS) : []
|
|
39
|
+
} catch {
|
|
40
|
+
events = []
|
|
41
|
+
}
|
|
42
|
+
return events
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function recordAccountEvent(input) {
|
|
46
|
+
const event = sanitize(input)
|
|
47
|
+
if (!event) return null
|
|
48
|
+
events.push(event)
|
|
49
|
+
if (events.length > MAX_EVENTS) events = events.slice(-MAX_EVENTS)
|
|
50
|
+
if (activePath && !saveTimer) {
|
|
51
|
+
saveTimer = setTimeout(() => {
|
|
52
|
+
saveTimer = null
|
|
53
|
+
flushAccountEvents()
|
|
54
|
+
}, 250)
|
|
55
|
+
saveTimer.unref?.()
|
|
56
|
+
}
|
|
57
|
+
return event
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function getAccountEvents(limit = 50) {
|
|
61
|
+
const safeLimit = Math.max(1, Math.min(MAX_EVENTS, Number(limit) || 50))
|
|
62
|
+
return events.slice(-safeLimit).reverse().map((event) => ({ ...event }))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function flushAccountEvents() {
|
|
66
|
+
if (saveTimer) {
|
|
67
|
+
clearTimeout(saveTimer)
|
|
68
|
+
saveTimer = null
|
|
69
|
+
}
|
|
70
|
+
if (!activePath) return
|
|
71
|
+
if (events.length === 0 && !existsSync(activePath)) return
|
|
72
|
+
try {
|
|
73
|
+
mkdirSync(path.dirname(activePath), { recursive: true })
|
|
74
|
+
const temp = `${activePath}.${Date.now()}.tmp`
|
|
75
|
+
writeFileSync(temp, JSON.stringify({ version: 1, events }, null, 2), 'utf8')
|
|
76
|
+
renameSync(temp, activePath)
|
|
77
|
+
} catch {
|
|
78
|
+
/* event history is best-effort */
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function clearAccountEvents() {
|
|
83
|
+
events = []
|
|
84
|
+
flushAccountEvents()
|
|
85
|
+
}
|
package/lib/adapter.js
CHANGED
|
@@ -608,6 +608,7 @@ export class ClineBotAdapter extends LlmAdapter {
|
|
|
608
608
|
latencyMs,
|
|
609
609
|
cacheReadTokens: usage?.cacheReadTokens,
|
|
610
610
|
cacheWriteTokens: usage?.cacheWriteTokens,
|
|
611
|
+
cacheSavingsUsd: cost.cacheSavingsUsd,
|
|
611
612
|
costUsd: cost.usd,
|
|
612
613
|
priced: cost.priced,
|
|
613
614
|
})
|
package/lib/client.js
CHANGED
|
@@ -12,10 +12,10 @@ window.__ModuleLoader__.load({
|
|
|
12
12
|
const NS="dsh-clinebot";const ROUTE_PREFIX="/dsh-clinebot";const SNAPSHOT_READY=Object.freeze({status:"ready",view:null});function readConfigForms(ctx){if(!ctx||typeof ctx.get!=="function")return void 0;try{return ctx.get("configForms")||void 0}catch{return void 0}}function useConfigFormsSnapshot(ctx){const scope=React.useMemo(()=>{try{return readConfigForms(ctx)?.get?.(NS)||void 0}catch{return void 0}},[ctx]);const subscribe=React.useMemo(()=>cb=>{try{return scope?.subscribe?scope.subscribe(cb)||(()=>{}):()=>{}}catch{return()=>{}}},[scope]);const getSnapshot=React.useCallback(()=>{try{return scope?.getSnapshot?.()||SNAPSHOT_READY}catch{return SNAPSHOT_READY}},[scope]);return React.useSyncExternalStore(subscribe,getSnapshot,()=>SNAPSHOT_READY)||SNAPSHOT_READY}
|
|
13
13
|
|
|
14
14
|
// --- src/client/locales.js ---
|
|
15
|
-
const en={nav:"ClineBot",title:"ClineBot",subtitle:"ClinePass subscription with dynamic plan models.","settings.loading":"Loading ClineBot settings\u2026","settings.retry":"Retry","settings.unavailable":"ClineBot settings unavailable (host namespace is not ready).","api.timeout":"Request timed out ({seconds}s)","header.title":"ClineBot (ClinePass) Provider","header.sub":"Connect models under your active ClinePass subscription. OpenAI API compatible, automated rolling limit tracking, and instant auto-registration in DSH.","badge.online":"Host online ({latency} ms)","badge.offline":"Host unreachable","badge.key_ok":"Key set ({source})","badge.key_missing":"Key missing","badge.registered":"DSH Registered ({count} models)","badge.not_registered":"Not registered","key.title":"Authorization & API Key","key.desc":"The key is saved directly to DSH Credentials storage (~/.dsh/.credentials.yaml) and never exposed in plaintext configuration files.","key.placeholder_has":"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022","key.placeholder_empty":"Paste ClinePass key (cline_...)","key.show":"Show","key.hide":"Hide","key.save":"Save Key","key.saving":"Saving\u2026","key.login_fast":"Open API Keys \u2197","key.verifying":"Validating key with Cline\u2026","key.verified":"Valid key for {email} ({plan})","key.invalid":"Key validation failed: {error}","key.env_label":"Credential environment name: ","key.get_key":"Get key in app.cline.bot console \u2197","key.saved_msg":"Key saved to DSH Credentials. Validation: {status}","key.empty_err":"Please enter an API key before saving","accounts.title":"Multi-Account Failover Pool","accounts.desc":"Add extra accounts. When an account hits 429 rate limits, ClineBot switches to the next available account automatically.","accounts.active_badge":"Active","accounts.pinned_badge":"Pinned","accounts.pin_btn":"Pin Active","accounts.add_btn":"+ Add Account","accounts.label_placeholder":"Account Label (e.g. Work)","accounts.env_placeholder":"CLINEBOT_API_KEY_2","accounts.delete_btn":"Delete","accounts.delete_confirm":'Are you sure you want to delete account "{label}"?',"accounts.save_btn":"Save Account","accounts.cancel_btn":"Cancel","accounts.quota_used":"{pct}% used","accounts.last_failover":"Last failover: {from} \u2192 {to} ({reason})","accounts.mode_label":"Pool Strategy","accounts.mode_least_used":"Least Used (Quota)","accounts.mode_round_robin":"Round Robin (Sequential)","accounts.routes_title":"Per-Model Account Routing","accounts.routes_desc":"Optionally route specific models to designated accounts in the pool.","accounts.route_model_placeholder":"Model ID (e.g. cline-pass/deepseek-v4-pro)","accounts.route_account_placeholder":"Target Account","accounts.route_btn_add":"Add Route","accounts.actions":"Account actions","accounts.configured":"Configured","accounts.missing_key":"Missing key","accounts.source":"Source: {source}","accounts.quota_5h":"5h {pct}%","accounts.rename":"Rename","accounts.rename_save":"Save name","accounts.rename_placeholder":"Account label","accounts.clear_key":"Clear stored key","accounts.clear_confirm":"Clear the stored key for {name}? The account row stays.","accounts.remove_confirm":"Remove {name} from the pool?","accounts.confirm":"Confirm","accounts.cancel":"Cancel","accounts.unpin":"Unpin","accounts.no_accounts":"No accounts configured yet.","accounts.route_remove":"Remove route","quota.title":"ClinePass Quota & Rate Limits","quota.desc":"Official rolling window request limits from ClinePass","quota.account":"Account: {email} \xB7 Plan: {plan}","quota.refresh":"Refresh Quotas","quota.refreshing":"Refreshing\u2026","quota.refreshed_msg":"Subscription quotas refreshed","quota.window_5h":"\u23F1 5-Hour Rolling Limit","quota.window_weekly":"Weekly Window","quota.used":"{pct}% used","quota.reset_at":"Reset: {time}","quota.window_monthly":"Monthly Window","quota.countdown":"Resets in {time}","quota.resetting":"Resetting\u2026","quota.warning_low":"Quota is below 10%. Requests may be throttled soon.","quota.forecast_no_usage":"No usage yet","quota.forecast_holds":"Holds until reset at the current pace","quota.forecast_left":"~{time} left at the current pace","quota.forecast_exhausted":"Exhausted","quota.exhausted_5h":"5-hour rolling limit is almost exhausted ({percentUsed}%). New requests may be rejected until quota reset.","quota.warning_5h":"Low quota warning: {percentUsed}% of 5-hour limit consumed (<10% remaining).","quota.warning_weekly":"Low weekly quota warning: {percentUsed}% consumed ({remainingPercent}% remaining).","quota.warning_monthly":"Low monthly quota warning: {percentUsed}% consumed ({remainingPercent}% remaining).","models.reasoning_tag":"Reasoning","models.effort_low":"Low","models.effort_medium":"Medium","models.effort_high":"High","models.effort_max":"Max","models.caps_vision":"Vision","models.caps_general":"General","models.caps_coding":"Coding","diag.smoke_result":"Latency: {latency} ms | Model: {model}\nResponse: {preview}","diag.empty":"(empty)","ui.error_title":"ClineBot UI Error: ","ui.retry":"Retry","update.check_failed":"Failed to check for updates","update.retry":"Retry check","models.filter_by_plan":"Hide models not in active plan","models.filter_by_plan_hint":"Automatically filter chat model picker to models included in your active ClinePass subscription plan.","models.visible_models":"Visible Models in Chat","models.visible_models_hint":"Choose which models appear in the DSH chat selector (empty means all supported models).","models.in_plan":"In Plan","models.out_of_plan":"Out of Plan","models.group_plan":"Included in plan","models.group_other":"Other models","models.group_custom":"Custom models","models.plan_only":"Plan Only","models.show_all":"Show All","models.clean_stale":"Clean Stale ({count})","models.models_selected":"{selected} of {total} selected for chat","models.title":"Model Picker in DSH ({enabled} of {total} active)","models.desc":"Catalog synchronizes directly from ClinePass subscription. New plan models are enabled automatically. Uncheck models to hide them from DSH chat picker. Thinking effort is selectable in the composer model menu.","models.reasoning_tooltip":"Supports thinking effort selection in composer model menu (Off / Low / Medium / High / Max)","models.sync":"Check Plan Models","models.syncing":"Checking\u2026","models.synced_msg":"Synchronized subscription models: {total} ({discovered} discovered from ClinePass)","models.verified":"Verified with plan","models.unverified":"Not synced (fallback catalog)","models.synced_at":"Synced: {date}","models.search_placeholder":"Search models by name, ID or description\u2026","models.filter_all":"All","models.filter_vision":"Vision","models.filter_coding":"Coding","models.filter_recommended":"Recommended","models.filter_disabled":"Disabled","models.all":"All","models.vision":"Vision Only","models.coding":"Coding Only","models.recommended":"Recommended","models.th_active":"On","models.th_name":"Model Name","models.th_id":"Model ID","models.th_ctx":"Context","models.th_caps":"Capabilities","models.th_route":"Account Route","models.route_auto":"Auto / Pool","models.btn_probe":"Ping","models.star":"Star","models.auto_new":"New","models.apply_original_all":"Original Specs","models.reset_contexts_all":"\u21BA Reset Contexts","models.context_updated_msg":"Model context limits updated and synchronized to DSH.","models.ctx_default":"Default ({ctx} / {max})","models.ctx_original":"Original ({ctx} / {max} \xB7 {provider})","models.ctx_edit":"Edit Limits","models.ctx_window_label":"Context (e.g. 200000)","models.ctx_max_tokens_label":"Max Out (e.g. 16384)","models.ctx_apply_all":"All","models.ctx_apply_all_title":"Apply these limits across all models","models.ctx_custom_placeholder":"Tokens (e.g. 2000000)","models.ctx_save":"Save","models.ctx_cancel":"Cancel","models.custom_ctx_label":"Context","models.custom_max_label":"Max Out","models.ctx_tag_original":"Original","models.ctx_tag_custom":"Custom","stats.title":"Current DSH Session Telemetry","stats.desc":"Local monitoring of requests, network latency, and estimated token usage via ClinePass.","stats.requests":"Successful / Total Requests","stats.tokens":"Estimated Session Tokens","stats.reasoning_tokens":"Reasoning Tokens (Thinking)","stats.export_csv":"Export CSV","stats.export_json":"Export JSON","stats.latency":"Last Request Latency","stats.last_req":"Last Request Time","models.custom_add_title":"Custom Models","models.custom_id":"Model ID (e.g. anthropic/claude-3-7-sonnet)","models.custom_name":"Display Name (optional)","models.custom_btn_add":"Add Model","models.custom_btn_del":"Delete","models.effort_auto":"Auto Effort","stats.errors429":"429 Rate Limits (Failover)","stats.top_models":"Top Models by Token Usage","stats.reset_btn":"Reset Stats","stats.confirm_reset":"Confirm Reset?","stats.cancel_reset":"Cancel","diag.title":"Diagnostics & Auto-Registration","diag.desc":"ClineBot registers into DSH Models automatically when a key is configured. You can run a connection smoke test here.","diag.smoke_btn":"Run Smoke Test (Ping)","diag.smoke_testing":"Testing\u2026","update.available":"Update available: v{latestVersion} (current: v{currentVersion})","update.btn":"Update Now","update.updating":"Updating\u2026","update.done":"Successfully updated to v{version}! Please restart DSH.","update.failed":"Update failed: {error}","update.checking":"Checking for updates\u2026","update.up_to_date":"Up to date","sidebar.quota_title":"Cline Quota","sidebar.show_quota":"Show quota meter in sidebar","sidebar.show_quota_hint":"Display compact quota meter in DSH sidebar footer.","dashboard.title":"Cline Quota & Status","dashboard.subtitle":"Real-time subscription quota, rolling limits, and account pool status.","dashboard.close":"Close dashboard","dashboard.refresh":"Refresh","dashboard.refreshing":"Refreshing\u2026","dashboard.rolling_window":"5-Hour Window","dashboard.weekly_window":"Weekly Window","dashboard.remaining":"remaining","dashboard.used":"used","dashboard.account_pool":"Active Routing Account","dashboard.credits":"Credit Balance","dashboard.updated_at":"Updated at","composer.cost_title":"ClineBot Session Cost","composer.last_request":"Last request","composer.free":"Free","composer.cost_breakdown":"Token & Cost Breakdown","composer.prompt_tok":"Prompt Tokens","composer.completion_tok":"Completion Tokens","composer.reasoning_tok":"Reasoning Tokens","composer.est_cost":"Estimated Cost","composer.unpriced":"unpriced","composer.cache_read":"Cache Read","composer.cache_write":"Cache Write","composer.price_source":"Price","composer.price_estimate":"estimate","composer.price_override":"override","composer.price_public":"public catalog","discovery.title":"Discovered Local Keys","discovery.import_btn":"Import from {source}","discovery.imported":"Imported to accounts pool","discovery.scanning":"Scanning for local Cline keys\u2026","settings.unsaved":"{count} unsaved change(s)","settings.saving":"Saving\u2026","settings.saved":"Settings saved","settings.save_failed":"Could not save settings","settings.save":"Save","settings.discard":"Discard","settings.save_invalid_base_url":"Base URL must be https:// or a localhost address.","settings.save_invalid_number":"Timeouts and payload limits must be positive numbers.","settings.models_group":"Models & context","settings.models_group_hint":"Plan filter, visible models, context windows, output limits and reasoning defaults.","settings.integrations":"Integrations & display","settings.integrations_desc":"Surfaces outside the chat request that reuse this provider.","settings.web_search":"Serve web search with ClineBot","settings.web_search_hint":"Use the ClinePass endpoint for the DSH web_search tool.","settings.sidebar_quota":"Show quota meter in the sidebar","settings.sidebar_quota_hint":"Display the compact plan and rolling-window meter above Settings.","settings.composer_cost":"Show session cost in the composer","settings.composer_cost_hint":"Display the estimated token cost beside the composer.","settings.advanced":"Advanced","settings.advanced_desc":"Connection, timeouts and storage paths.","settings.base_url":"API base URL","settings.base_url_hint":"ClinePass API root. Remote endpoints must use https://.","settings.timeout":"Connect timeout (ms)","settings.timeout_hint":"Maximum wait for the upstream response headers.","settings.smoke_timeout":"Smoke-test timeout (ms)","settings.smoke_timeout_hint":"Maximum wait for the diagnostic chat completion.","settings.stream_idle":"Stream idle timeout (ms)","settings.stream_idle_hint":"Maximum gap between SSE chunks before the stream is treated as dead.","settings.max_payload":"Max multimodal payload (MB)","settings.max_payload_hint":"Oldest images are evicted once the request exceeds this budget.","settings.stats_retention":"Stats retention (days)","settings.stats_retention_hint":"Trim day/hour/session telemetry older than this. 0 keeps everything.","settings.daily_budget":"Daily budget (USD)","settings.daily_budget_hint":"Estimated spend guard for the current UTC day. 0 disables it.","settings.monthly_budget":"Monthly budget (USD)","settings.monthly_budget_hint":"Estimated spend guard for the current UTC month. 0 disables it.","settings.stats_path":"Stats file path","settings.stats_path_hint":"Persistent JSON telemetry path (supports ~/).","settings.models_cache_path":"Models cache path","settings.models_cache_path_hint":"Offline model catalogue cache (supports ~/).","providerCard.title":"ClineBot (ClinePass)","providerCard.hint":"Paste a ClinePass API key or manage the full account pool in Settings \u2192 ClineBot.","providerCard.keyPresent":"Key configured","providerCard.keyMissing":"Key missing","providerCard.routeActive":"Route active","providerCard.placeholder":"Paste API key","providerCard.replace":"Replace API key","providerCard.save":"Save key","providerCard.saving":"Saving\u2026","providerCard.saved":"Key saved and validated","providerCard.failed":"Could not save the key","providerCard.show":"Show","providerCard.hide":"Hide","providerCard.openSettings":"Open Settings \u2192 ClineBot for accounts, models and quotas.","dashboard.tab_quota":"Quota","dashboard.tab_analytics":"Analytics","dashboard.auto_account":"Automatic (least used)","analytics.title":"Usage analytics","analytics.requests":"Requests","analytics.tokens":"Tokens","analytics.cost":"Estimated cost","analytics.success":"Success rate","analytics.ttft":"Avg TTFT","analytics.loading":"Loading analytics\u2026","analytics.no_data":"No analytics yet. Send a few messages first.","analytics.unpriced":"{count} request(s) have no price estimate and are excluded from cost totals.","analytics.group_model":"Models","analytics.group_day":"Days","analytics.group_session":"Sessions","analytics.heatmap":"Request heatmap","analytics.heatmap_hint":"Requests per hour over the last 7 days.","analytics.heatmap_less":"Less","analytics.heatmap_more":"More","analytics.heatmap_empty":"No hourly data yet. Send a message to start the heatmap.","analytics.input":"Input","analytics.output":"Output","analytics.reasoning":"Reasoning","analytics.cache_read":"Cache read","analytics.cache_write":"Cache write","analytics.avg_ttft":"Avg TTFT","analytics.avg_latency":"Avg latency","analytics.p50_latency":"P50 latency","analytics.p95_latency":"P95 latency","analytics.heatmap_selected":"{day} {hour}:00 \u2014 {count} requests. Click the cell again to clear.","analytics.errors":"Errors","analytics.no_key":"ClinePass key is not configured.","analytics.no_key_hint":"Open Settings \u2192 ClineBot and paste a key, or import a discovered local key.","analytics.health_error":"ClinePass endpoint is unreachable.","analytics.not_registered":"ClineBot provider is not registered with DSH.","analytics.not_registered_hint":"Open Settings \u2192 ClineBot and check Diagnostics.","analytics.stale":"Showing the last successful snapshot.","analytics.range":"Range","analytics.range_7":"7 days","analytics.range_30":"30 days","analytics.range_90":"90 days","analytics.heatmap_all_models":"All models","analytics.heatmap_peak":"Peak","analytics.heatmap_by_hour":"By hour","analytics.heatmap_model":"Heatmap model","quota.no_windows":"No quota windows reported.","prices.title":"Price overrides","prices.desc":"USD per 1M tokens. Blank uses the local estimate.","prices.show":"Show prices","prices.hide":"Hide prices","prices.model":"Model","prices.input":"Input","prices.output":"Output","prices.cache_read":"Cache read","prices.cache_write":"Cache write","prices.auto":"auto","prices.empty":"No models available.","prices.hint":"These rates affect the composer and local analytics only; they are not an invoice.","prices.public_hint":"Sync uses the public models.dev catalog as an estimate; your overrides always win.","prices.sync":"Sync public prices","prices.syncing":"Syncing\u2026","prices.synced":"Public catalog synced: {count} models","prices.sync_failed":"Price sync failed: {error}"};function makeT(dict,fallback){return function t(key,vars){if(key==="quota.countdown"&&vars?.time==="resetting"){return dict&&dict["quota.resetting"]||fallback&&fallback["quota.resetting"]||"\u6B63\u5728\u91CD\u7F6E"}let val=dict&&dict[key]||fallback&&fallback[key]||key;if(vars&&typeof val==="string"){for(const k of Object.keys(vars)){val=val.replace(new RegExp("\\{"+k+"\\}","g"),String(vars[k]))}}return val}}
|
|
15
|
+
const en={nav:"ClineBot",title:"ClineBot",subtitle:"ClinePass subscription with dynamic plan models.","settings.loading":"Loading ClineBot settings\u2026","settings.retry":"Retry","settings.unavailable":"ClineBot settings unavailable (host namespace is not ready).","api.timeout":"Request timed out ({seconds}s)","header.title":"ClineBot (ClinePass) Provider","header.sub":"Connect models under your active ClinePass subscription. OpenAI API compatible, automated rolling limit tracking, and instant auto-registration in DSH.","badge.online":"Host online ({latency} ms)","badge.offline":"Host unreachable","badge.key_ok":"Key set ({source})","badge.key_missing":"Key missing","badge.registered":"DSH Registered ({count} models)","badge.not_registered":"Not registered","key.title":"Authorization & API Key","key.desc":"The key is saved directly to DSH Credentials storage (~/.dsh/.credentials.yaml) and never exposed in plaintext configuration files.","key.placeholder_has":"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022","key.placeholder_empty":"Paste ClinePass key (cline_...)","key.show":"Show","key.hide":"Hide","key.save":"Save Key","key.saving":"Saving\u2026","key.login_fast":"Open API Keys \u2197","key.verifying":"Validating key with Cline\u2026","key.verified":"Valid key for {email} ({plan})","key.invalid":"Key validation failed: {error}","key.env_label":"Credential environment name: ","key.get_key":"Get key in app.cline.bot console \u2197","key.saved_msg":"Key saved to DSH Credentials. Validation: {status}","key.empty_err":"Please enter an API key before saving","accounts.title":"Multi-Account Failover Pool","accounts.desc":"Add extra accounts. When an account hits 429 rate limits, ClineBot switches to the next available account automatically.","accounts.active_badge":"Active","accounts.pinned_badge":"Pinned","accounts.pin_btn":"Pin Active","accounts.add_btn":"+ Add Account","accounts.label_placeholder":"Account Label (e.g. Work)","accounts.env_placeholder":"CLINEBOT_API_KEY_2","accounts.delete_btn":"Delete","accounts.delete_confirm":'Are you sure you want to delete account "{label}"?',"accounts.save_btn":"Save Account","accounts.cancel_btn":"Cancel","accounts.quota_used":"{pct}% used","accounts.last_failover":"Last failover: {from} \u2192 {to} ({reason})","accounts.mode_label":"Pool Strategy","accounts.mode_least_used":"Least Used (Quota)","accounts.mode_round_robin":"Round Robin (Sequential)","accounts.history_show":"History","accounts.history_hide":"Hide history","accounts.history_title":"Account event history","accounts.history_hint":"Recent cooldowns, recoveries and routing decisions. Newest first, last 50.","accounts.history_empty":"No account events yet.","accounts.history_loading":"Loading history\u2026","accounts.history_until":"until {time}","accounts.event_cooldown":"Cooldown","accounts.event_recover":"Recovered","accounts.event_route":"Routed","accounts.routes_title":"Per-Model Account Routing","accounts.routes_desc":"Optionally route specific models to designated accounts in the pool.","accounts.route_model_placeholder":"Model ID (e.g. cline-pass/deepseek-v4-pro)","accounts.route_account_placeholder":"Target Account","accounts.route_btn_add":"Add Route","accounts.actions":"Account actions","accounts.configured":"Configured","accounts.missing_key":"Missing key","accounts.source":"Source: {source}","accounts.quota_5h":"5h {pct}%","accounts.rename":"Rename","accounts.rename_save":"Save name","accounts.rename_placeholder":"Account label","accounts.clear_key":"Clear stored key","accounts.clear_confirm":"Clear the stored key for {name}? The account row stays.","accounts.remove_confirm":"Remove {name} from the pool?","accounts.confirm":"Confirm","accounts.cancel":"Cancel","accounts.unpin":"Unpin","accounts.no_accounts":"No accounts configured yet.","accounts.route_remove":"Remove route","quota.title":"ClinePass Quota & Rate Limits","quota.desc":"Official rolling window request limits from ClinePass","quota.account":"Account: {email} \xB7 Plan: {plan}","quota.refresh":"Refresh Quotas","quota.refreshing":"Refreshing\u2026","quota.refreshed_msg":"Subscription quotas refreshed","quota.window_5h":"\u23F1 5-Hour Rolling Limit","quota.window_weekly":"Weekly Window","quota.used":"{pct}% used","quota.reset_at":"Reset: {time}","quota.window_monthly":"Monthly Window","quota.countdown":"Resets in {time}","quota.resetting":"Resetting\u2026","quota.warning_low":"Quota is below 10%. Requests may be throttled soon.","quota.forecast_no_usage":"No usage yet","quota.forecast_holds":"Holds until reset at the current pace","quota.forecast_left":"~{time} left at the current pace","quota.forecast_exhausted":"Exhausted","quota.exhausted_5h":"5-hour rolling limit is almost exhausted ({percentUsed}%). New requests may be rejected until quota reset.","quota.warning_5h":"Low quota warning: {percentUsed}% of 5-hour limit consumed (<10% remaining).","quota.warning_weekly":"Low weekly quota warning: {percentUsed}% consumed ({remainingPercent}% remaining).","quota.warning_monthly":"Low monthly quota warning: {percentUsed}% consumed ({remainingPercent}% remaining).","models.reasoning_tag":"Reasoning","models.effort_low":"Low","models.effort_medium":"Medium","models.effort_high":"High","models.effort_max":"Max","models.caps_vision":"Vision","models.caps_general":"General","models.caps_coding":"Coding","diag.smoke_result":"Latency: {latency} ms | Model: {model}\nResponse: {preview}","diag.empty":"(empty)","ui.error_title":"ClineBot UI Error: ","ui.retry":"Retry","update.check_failed":"Failed to check for updates","update.retry":"Retry check","models.filter_by_plan":"Hide models not in active plan","models.filter_by_plan_hint":"Automatically filter chat model picker to models included in your active ClinePass subscription plan.","models.visible_models":"Visible Models in Chat","models.visible_models_hint":"Choose which models appear in the DSH chat selector (empty means all supported models).","models.in_plan":"In Plan","models.out_of_plan":"Out of Plan","models.group_plan":"Included in plan","models.group_other":"Other models","models.group_custom":"Custom models","models.plan_only":"Plan Only","models.show_all":"Show All","models.clean_stale":"Clean Stale ({count})","models.models_selected":"{selected} of {total} selected for chat","models.title":"Model Picker in DSH ({enabled} of {total} active)","models.desc":"Catalog synchronizes directly from ClinePass subscription. New plan models are enabled automatically. Uncheck models to hide them from DSH chat picker. Thinking effort is selectable in the composer model menu.","models.reasoning_tooltip":"Supports thinking effort selection in composer model menu (Off / Low / Medium / High / Max)","models.sync":"Check Plan Models","models.syncing":"Checking\u2026","models.synced_msg":"Synchronized subscription models: {total} ({discovered} discovered from ClinePass)","models.verified":"Verified with plan","models.unverified":"Not synced (fallback catalog)","models.synced_at":"Synced: {date}","models.search_placeholder":"Search models by name, ID or description\u2026","models.filter_all":"All","models.filter_vision":"Vision","models.filter_coding":"Coding","models.filter_recommended":"Recommended","models.filter_disabled":"Disabled","models.all":"All","models.vision":"Vision Only","models.coding":"Coding Only","models.recommended":"Recommended","models.th_active":"On","models.th_name":"Model Name","models.th_id":"Model ID","models.th_ctx":"Context","models.th_caps":"Capabilities","models.th_route":"Account Route","models.route_auto":"Auto / Pool","models.btn_probe":"Ping","models.star":"Star","models.auto_new":"New","models.apply_original_all":"Original Specs","models.reset_contexts_all":"\u21BA Reset Contexts","models.context_updated_msg":"Model context limits updated and synchronized to DSH.","models.ctx_default":"Default ({ctx} / {max})","models.ctx_original":"Original ({ctx} / {max} \xB7 {provider})","models.ctx_edit":"Edit Limits","models.ctx_window_label":"Context (e.g. 200000)","models.ctx_max_tokens_label":"Max Out (e.g. 16384)","models.ctx_apply_all":"All","models.ctx_apply_all_title":"Apply these limits across all models","models.ctx_custom_placeholder":"Tokens (e.g. 2000000)","models.ctx_save":"Save","models.ctx_cancel":"Cancel","models.custom_ctx_label":"Context","models.custom_max_label":"Max Out","models.ctx_tag_original":"Original","models.ctx_tag_custom":"Custom","stats.title":"Current DSH Session Telemetry","stats.desc":"Local monitoring of requests, network latency, and estimated token usage via ClinePass.","stats.requests":"Successful / Total Requests","stats.tokens":"Estimated Session Tokens","stats.reasoning_tokens":"Reasoning Tokens (Thinking)","stats.export_csv":"Export CSV","stats.export_json":"Export JSON","stats.latency":"Last Request Latency","stats.last_req":"Last Request Time","models.custom_add_title":"Custom Models","models.custom_id":"Model ID (e.g. anthropic/claude-3-7-sonnet)","models.custom_name":"Display Name (optional)","models.custom_btn_add":"Add Model","models.custom_btn_del":"Delete","models.effort_auto":"Auto Effort","stats.errors429":"429 Rate Limits (Failover)","stats.top_models":"Top Models by Token Usage","stats.top_accounts":"Top Accounts by Token Usage","stats.cache_saved":"Cache savings: \u2248 ${amount}","stats.reset_btn":"Reset Stats","stats.confirm_reset":"Confirm Reset?","stats.cancel_reset":"Cancel","diag.title":"Diagnostics & Auto-Registration","diag.desc":"ClineBot registers into DSH Models automatically when a key is configured. You can run a connection smoke test here.","diag.smoke_btn":"Run Smoke Test (Ping)","diag.smoke_testing":"Testing\u2026","update.available":"Update available: v{latestVersion} (current: v{currentVersion})","update.btn":"Update Now","update.updating":"Updating\u2026","update.done":"Successfully updated to v{version}! Please restart DSH.","update.failed":"Update failed: {error}","update.checking":"Checking for updates\u2026","update.up_to_date":"Up to date","sidebar.quota_title":"Cline Quota","sidebar.show_quota":"Show quota meter in sidebar","sidebar.show_quota_hint":"Display compact quota meter in DSH sidebar footer.","dashboard.title":"Cline Quota & Status","dashboard.subtitle":"Real-time subscription quota, rolling limits, and account pool status.","dashboard.close":"Close dashboard","dashboard.refresh":"Refresh","dashboard.refreshing":"Refreshing\u2026","dashboard.rolling_window":"5-Hour Window","dashboard.weekly_window":"Weekly Window","dashboard.remaining":"remaining","dashboard.used":"used","dashboard.account_pool":"Active Routing Account","dashboard.credits":"Credit Balance","dashboard.updated_at":"Updated at","composer.cost_title":"ClineBot Session Cost","composer.last_request":"Last request","composer.free":"Free","composer.cost_breakdown":"Token & Cost Breakdown","composer.prompt_tok":"Prompt Tokens","composer.completion_tok":"Completion Tokens","composer.reasoning_tok":"Reasoning Tokens","composer.est_cost":"Estimated Cost","composer.unpriced":"unpriced","composer.cache_read":"Cache Read","composer.cache_write":"Cache Write","composer.price_source":"Price","composer.price_estimate":"estimate","composer.price_override":"override","composer.price_public":"public catalog","discovery.title":"Discovered Local Keys","discovery.import_btn":"Import from {source}","discovery.imported":"Imported to accounts pool","discovery.scanning":"Scanning for local Cline keys\u2026","settings.unsaved":"{count} unsaved change(s)","settings.saving":"Saving\u2026","settings.saved":"Settings saved","settings.save_failed":"Could not save settings","settings.save":"Save","settings.discard":"Discard","settings.save_invalid_base_url":"Base URL must be https:// or a localhost address.","settings.save_invalid_number":"Timeouts and payload limits must be positive numbers.","settings.models_group":"Models & context","settings.models_group_hint":"Plan filter, visible models, context windows, output limits and reasoning defaults.","settings.integrations":"Integrations & display","settings.integrations_desc":"Surfaces outside the chat request that reuse this provider.","settings.web_search":"Serve web search with ClineBot","settings.web_search_hint":"Use the ClinePass endpoint for the DSH web_search tool.","settings.sidebar_quota":"Show quota meter in the sidebar","settings.sidebar_quota_hint":"Display the compact plan and rolling-window meter above Settings.","settings.composer_cost":"Show session cost in the composer","settings.composer_cost_hint":"Display the estimated token cost beside the composer.","settings.advanced":"Advanced","settings.advanced_desc":"Connection, timeouts and storage paths.","settings.base_url":"API base URL","settings.base_url_hint":"ClinePass API root. Remote endpoints must use https://.","settings.timeout":"Connect timeout (ms)","settings.timeout_hint":"Maximum wait for the upstream response headers.","settings.smoke_timeout":"Smoke-test timeout (ms)","settings.smoke_timeout_hint":"Maximum wait for the diagnostic chat completion.","settings.stream_idle":"Stream idle timeout (ms)","settings.stream_idle_hint":"Maximum gap between SSE chunks before the stream is treated as dead.","settings.max_payload":"Max multimodal payload (MB)","settings.max_payload_hint":"Oldest images are evicted once the request exceeds this budget.","settings.stats_retention":"Stats retention (days)","settings.stats_retention_hint":"Trim day/hour/session telemetry older than this. 0 keeps everything.","settings.daily_budget":"Daily budget (USD)","settings.daily_budget_hint":"Estimated spend guard for the current UTC day. 0 disables it.","settings.monthly_budget":"Monthly budget (USD)","settings.monthly_budget_hint":"Estimated spend guard for the current UTC month. 0 disables it.","settings.stats_path":"Stats file path","settings.stats_path_hint":"Persistent JSON telemetry path (supports ~/).","settings.models_cache_path":"Models cache path","settings.models_cache_path_hint":"Offline model catalogue cache (supports ~/).","providerCard.title":"ClineBot (ClinePass)","providerCard.hint":"Paste a ClinePass API key or manage the full account pool in Settings \u2192 ClineBot.","providerCard.keyPresent":"Key configured","providerCard.keyMissing":"Key missing","providerCard.routeActive":"Route active","providerCard.placeholder":"Paste API key","providerCard.replace":"Replace API key","providerCard.save":"Save key","providerCard.saving":"Saving\u2026","providerCard.saved":"Key saved and validated","providerCard.failed":"Could not save the key","providerCard.show":"Show","providerCard.hide":"Hide","providerCard.openSettings":"Open Settings \u2192 ClineBot for accounts, models and quotas.","dashboard.tab_quota":"Quota","dashboard.tab_analytics":"Analytics","dashboard.auto_account":"Automatic (least used)","analytics.title":"Usage analytics","analytics.requests":"Requests","analytics.tokens":"Tokens","analytics.cost":"Estimated cost","analytics.success":"Success rate","analytics.ttft":"Avg TTFT","analytics.loading":"Loading analytics\u2026","analytics.no_data":"No analytics yet. Send a few messages first.","analytics.unpriced":"{count} request(s) have no price estimate and are excluded from cost totals.","analytics.group_model":"Models","analytics.group_day":"Days","analytics.group_account":"Accounts","analytics.group_session":"Sessions","analytics.cache_saved":"Cache Savings","analytics.heatmap":"Request heatmap","analytics.heatmap_hint":"Requests per hour over the last 7 days.","analytics.heatmap_less":"Less","analytics.heatmap_more":"More","analytics.heatmap_empty":"No hourly data yet. Send a message to start the heatmap.","analytics.input":"Input","analytics.output":"Output","analytics.reasoning":"Reasoning","analytics.cache_read":"Cache read","analytics.cache_write":"Cache write","analytics.avg_ttft":"Avg TTFT","analytics.avg_latency":"Avg latency","analytics.p50_latency":"P50 latency","analytics.p95_latency":"P95 latency","analytics.heatmap_selected":"{day} {hour}:00 \u2014 {count} requests. Click the cell again to clear.","analytics.errors":"Errors","analytics.no_key":"ClinePass key is not configured.","analytics.no_key_hint":"Open Settings \u2192 ClineBot and paste a key, or import a discovered local key.","analytics.health_error":"ClinePass endpoint is unreachable.","analytics.not_registered":"ClineBot provider is not registered with DSH.","analytics.not_registered_hint":"Open Settings \u2192 ClineBot and check Diagnostics.","analytics.stale":"Showing the last successful snapshot.","analytics.range":"Range","analytics.range_7":"7 days","analytics.range_30":"30 days","analytics.range_90":"90 days","analytics.heatmap_all_models":"All models","analytics.heatmap_peak":"Peak","analytics.heatmap_by_hour":"By hour","analytics.heatmap_model":"Heatmap model","quota.no_windows":"No quota windows reported.","prices.title":"Price overrides","prices.desc":"USD per 1M tokens. Blank uses the local estimate.","prices.show":"Show prices","prices.hide":"Hide prices","prices.model":"Model","prices.input":"Input","prices.output":"Output","prices.cache_read":"Cache read","prices.cache_write":"Cache write","prices.auto":"auto","prices.empty":"No models available.","prices.hint":"These rates affect the composer and local analytics only; they are not an invoice.","prices.public_hint":"Sync uses the public models.dev catalog as an estimate; your overrides always win.","prices.sync":"Sync public prices","prices.syncing":"Syncing\u2026","prices.synced":"Public catalog synced: {count} models","prices.sync_failed":"Price sync failed: {error}"};function makeT(dict,fallback){return function t(key,vars){if(key==="quota.countdown"&&vars?.time==="resetting"){return dict&&dict["quota.resetting"]||fallback&&fallback["quota.resetting"]||"\u6B63\u5728\u91CD\u7F6E"}let val=dict&&dict[key]||fallback&&fallback[key]||key;if(vars&&typeof val==="string"){for(const k of Object.keys(vars)){val=val.replace(new RegExp("\\{"+k+"\\}","g"),String(vars[k]))}}return val}}
|
|
16
16
|
|
|
17
17
|
// --- src/client/locales-zh.js ---
|
|
18
|
-
const zh={nav:"ClineBot",title:"ClineBot",subtitle:"ClinePass \u8BA2\u9605\u670D\u52A1\uFF0C\u5305\u542B\u52A8\u6001\u5957\u9910\u6A21\u578B\u3002","settings.loading":"\u6B63\u5728\u52A0\u8F7D ClineBot \u8BBE\u7F6E\u2026","settings.retry":"\u91CD\u8BD5","settings.unavailable":"ClineBot \u8BBE\u7F6E\u4E0D\u53EF\u7528\uFF08\u5BBF\u4E3B\u547D\u540D\u7A7A\u95F4\u5C1A\u672A\u5C31\u7EEA\uFF09\u3002","api.timeout":"\u8BF7\u6C42\u8D85\u65F6\uFF08{seconds}\u79D2\uFF09","header.title":"ClineBot (ClinePass) \u6A21\u578B\u670D\u52A1\u5546","header.sub":"\u901A\u8FC7 ClinePass \u8BA2\u9605\u8FDE\u63A5\u4E3B\u6D41\u63A8\u7406\u4E0E\u7F16\u7A0B\u6A21\u578B\u3002\u5B8C\u5168\u517C\u5BB9 OpenAI \u63A5\u53E3\uFF0C\u81EA\u52A8\u8FFD\u8E2A\u6EDA\u52A8\u9650\u989D\u5E76\u5728 DSH \u4E2D\u5373\u65F6\u751F\u6548\u3002","badge.online":"\u670D\u52A1\u5728\u7EBF ({latency} ms)","badge.offline":"\u4E3B\u673A\u65E0\u6CD5\u8FDE\u63A5","badge.key_ok":"\u5BC6\u94A5\u6B63\u5E38 ({source})","badge.key_missing":"\u672A\u914D\u7F6E\u5BC6\u94A5","badge.registered":"\u5DF2\u63A5\u5165 DSH ({count} \u4E2A\u6A21\u578B)","badge.not_registered":"\u672A\u63A5\u5165 DSH","key.title":"\u6388\u6743\u4E0E API \u5BC6\u94A5","key.desc":"\u5BC6\u94A5\u76F4\u63A5\u52A0\u5BC6\u4FDD\u5B58\u5728 DSH \u51ED\u636E\u7BA1\u7406\u5668 (~/.dsh/.credentials.yaml)\uFF0C\u7EDD\u4E0D\u4F1A\u4EE5\u660E\u6587\u5F62\u5F0F\u66B4\u9732\u5728\u666E\u901A\u914D\u7F6E\u6587\u4EF6\u4E2D\u3002","key.placeholder_has":"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022","key.placeholder_empty":"\u7C98\u8D34 ClinePass \u5BC6\u94A5 (cline_...)","key.show":"\u663E\u793A","key.hide":"\u9690\u85CF","key.save":"\u4FDD\u5B58\u5BC6\u94A5","key.saving":"\u6B63\u5728\u4FDD\u5B58\u2026","key.login_fast":"\u6253\u5F00 API \u5BC6\u94A5\u9875 \u2197","key.verifying":"\u6B63\u5728\u8FDE\u63A5 Cline \u9A8C\u8BC1\u5BC6\u94A5\u2026","key.verified":"\u5BC6\u94A5\u6709\u6548\uFF1A{email} ({plan})","key.invalid":"\u5BC6\u94A5\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF1A{error}","key.env_label":"\u51ED\u636E\u73AF\u5883\u53D8\u91CF\u540D\u79F0\uFF1A","key.get_key":"\u524D\u5F80 app.cline.bot \u63A7\u5236\u53F0\u83B7\u53D6\u5BC6\u94A5 \u2197","key.saved_msg":"\u5BC6\u94A5\u5DF2\u6210\u529F\u4FDD\u5B58\u81F3 DSH \u51ED\u636E\u670D\u52A1\u3002\u9A8C\u8BC1\u72B6\u6001\uFF1A{status}","key.empty_err":"\u4FDD\u5B58\u524D\u8BF7\u8F93\u5165 API \u5BC6\u94A5","accounts.title":"\u591A\u8D26\u53F7\u6545\u969C\u8F6C\u79FB\u4E0E\u8F6E\u6362\u6C60 (Failover)","accounts.desc":"\u6DFB\u52A0\u5907\u7528\u8D26\u53F7\u3002\u5F53\u67D0\u4E2A\u8D26\u53F7\u89E6\u53D1 429 \u901F\u7387\u9650\u5236\u6216\u914D\u989D\u8017\u5C3D\u65F6\uFF0CClineBot \u4F1A\u667A\u80FD\u65E0\u7F1D\u5207\u6362\u81F3\u5269\u4F59\u989D\u5EA6\u6700\u9AD8\u7684\u5907\u7528\u8D26\u53F7\u3002","accounts.active_badge":"\u5F53\u524D\u6D3B\u52A8","accounts.pinned_badge":"\u56FA\u5B9A\u4F7F\u7528","accounts.pin_btn":"\u8BBE\u4E3A\u6D3B\u52A8","accounts.add_btn":"+ \u6DFB\u52A0\u8D26\u53F7","accounts.label_placeholder":"\u8D26\u53F7\u6807\u7B7E\uFF08\u4F8B\u5982\uFF1A\u5DE5\u4F5C\u8D26\u53F7\uFF09","accounts.env_placeholder":"CLINEBOT_API_KEY_2","accounts.delete_btn":"\u5220\u9664","accounts.delete_confirm":"\u786E\u5B9A\u8981\u5220\u9664\u8D26\u53F7\u201C{label}\u201D\u5417\uFF1F","accounts.save_btn":"\u4FDD\u5B58\u8D26\u53F7","accounts.cancel_btn":"\u53D6\u6D88","accounts.quota_used":"\u5DF2\u7528 {pct}%","accounts.last_failover":"\u6700\u8FD1\u6545\u969C\u8F6C\u79FB\uFF1A{from} \u2192 {to} ({reason})","accounts.mode_label":"\u8C03\u5EA6\u7B56\u7565","accounts.mode_least_used":"\u4F4E\u5360\u7528\u4F18\u5148 (Least Used)","accounts.mode_round_robin":"\u987A\u5E8F\u8F6E\u8BE2 (Round Robin)","accounts.routes_title":"\u7279\u5B9A\u6A21\u578B\u4E13\u5C5E\u8DEF\u7531 (Per-Model)","accounts.routes_desc":"\u53EF\u9488\u5BF9\u7279\u5B9A\u6A21\u578B\u6307\u5B9A\u7ED1\u5B9A\u7684\u8D26\u53F7\u51ED\u636E\uFF08\u5982\u5C06\u6602\u8D35\u6A21\u578B\u6307\u5411\u7279\u5B9A\u8D26\u53F7\uFF09\u3002","accounts.route_model_placeholder":"\u6A21\u578B ID (\u4F8B\u5982 cline-pass/deepseek-v4-pro)","accounts.route_account_placeholder":"\u76EE\u6807\u8D26\u53F7","accounts.route_btn_add":"\u6DFB\u52A0\u8DEF\u7531","accounts.actions":"\u8D26\u6237\u64CD\u4F5C","accounts.configured":"\u5DF2\u914D\u7F6E","accounts.missing_key":"\u7F3A\u5C11\u5BC6\u94A5","accounts.source":"\u6765\u6E90\uFF1A{source}","accounts.quota_5h":"5\u5C0F\u65F6 {pct}%","accounts.rename":"\u91CD\u547D\u540D","accounts.rename_save":"\u4FDD\u5B58\u540D\u79F0","accounts.rename_placeholder":"\u8D26\u6237\u540D\u79F0","accounts.clear_key":"\u6E05\u9664\u5DF2\u5B58\u5BC6\u94A5","accounts.clear_confirm":"\u6E05\u9664 {name} \u7684\u5DF2\u5B58\u5BC6\u94A5\uFF1F\u8D26\u6237\u884C\u4F1A\u4FDD\u7559\u3002","accounts.remove_confirm":"\u4ECE\u6C60\u4E2D\u79FB\u9664 {name}\uFF1F","accounts.confirm":"\u786E\u8BA4","accounts.cancel":"\u53D6\u6D88","accounts.unpin":"\u53D6\u6D88\u56FA\u5B9A","accounts.no_accounts":"\u5C1A\u672A\u914D\u7F6E\u8D26\u6237\u3002","accounts.route_remove":"\u79FB\u9664\u8DEF\u7531","quota.title":"ClinePass \u7528\u91CF\u914D\u989D\u4E0E\u9650\u989D\u76D1\u63A7","quota.desc":"\u6765\u81EA ClinePass \u5B98\u65B9\u7684\u5B9E\u65F6\u6EDA\u52A8\u7A97\u53E3\u7528\u91CF\u9650\u989D","quota.account":"\u8D26\u53F7\uFF1A{email} \xB7 \u5957\u9910\uFF1A{plan}","quota.refresh":"\u5237\u65B0\u914D\u989D","quota.refreshing":"\u6B63\u5728\u5237\u65B0\u2026","quota.refreshed_msg":"\u8BA2\u9605\u7528\u91CF\u914D\u989D\u5DF2\u5237\u65B0","quota.window_5h":"\u23F1 5 \u5C0F\u65F6\u6EDA\u52A8\u9650\u989D","quota.window_weekly":"\u6BCF\u5468\u6EDA\u52A8\u7A97\u53E3","quota.used":"\u5DF2\u4F7F\u7528 {pct}%","quota.reset_at":"\u91CD\u7F6E\u65F6\u95F4\uFF1A{time}","quota.window_monthly":"\u6BCF\u6708\u6EDA\u52A8\u7A97\u53E3","quota.countdown":"{time}\u540E\u91CD\u7F6E","quota.resetting":"\u6B63\u5728\u91CD\u7F6E","quota.warning_low":"\u989D\u5EA6\u4F4E\u4E8E 10%\uFF0C\u8BF7\u6C42\u53EF\u80FD\u5F88\u5FEB\u4F1A\u53D7\u5230\u9650\u5236\u3002","quota.forecast_no_usage":"\u5C1A\u672A\u4F7F\u7528","quota.forecast_holds":"\u6309\u5F53\u524D\u901F\u5EA6\u53EF\u575A\u6301\u5230\u91CD\u7F6E","quota.forecast_left":"\u6309\u5F53\u524D\u901F\u5EA6\u7EA6\u5269 {time}","quota.forecast_exhausted":"\u5DF2\u8017\u5C3D","quota.exhausted_5h":"5 \u5C0F\u65F6\u6EDA\u52A8\u9650\u989D\u5DF2\u63A5\u8FD1\u8017\u5C3D\uFF08\u5DF2\u7528 {percentUsed}%\uFF09\u3002\u5728\u989D\u5EA6\u91CD\u7F6E\u524D\u65B0\u8BF7\u6C42\u53EF\u80FD\u4F1A\u88AB\u62D2\u7EDD\u3002","quota.warning_5h":"\u4F4E\u989D\u5EA6\u8B66\u544A\uFF1A5 \u5C0F\u65F6\u9650\u989D\u5DF2\u6D88\u8017 {percentUsed}%\uFF08\u5269\u4F59\u4E0D\u8DB3 10%\uFF09\u3002","quota.warning_weekly":"\u5468\u9650\u989D\u504F\u4F4E\u8B66\u544A\uFF1A\u5DF2\u6D88\u8017 {percentUsed}%\uFF08\u5269\u4F59 {remainingPercent}%\uFF09\u3002","quota.warning_monthly":"\u6708\u9650\u989D\u504F\u4F4E\u8B66\u544A\uFF1A\u5DF2\u6D88\u8017 {percentUsed}%\uFF08\u5269\u4F59 {remainingPercent}%\uFF09\u3002","models.reasoning_tag":"\u6DF1\u5EA6\u601D\u8003","models.effort_low":"\u4F4E","models.effort_medium":"\u4E2D","models.effort_high":"\u9AD8","models.effort_max":"\u6700\u9AD8","models.caps_vision":"\u89C6\u89C9","models.caps_general":"\u901A\u7528","models.caps_coding":"\u7F16\u7A0B","diag.smoke_result":"\u5EF6\u8FDF\uFF1A{latency} ms | \u6A21\u578B\uFF1A{model}\n\u54CD\u5E94\uFF1A{preview}","diag.empty":"(\u7A7A)","ui.error_title":"ClineBot \u754C\u9762\u9519\u8BEF\uFF1A","ui.retry":"\u91CD\u8BD5","update.check_failed":"\u68C0\u67E5\u66F4\u65B0\u5931\u8D25","update.retry":"\u91CD\u8BD5\u68C0\u67E5","models.filter_by_plan":"\u9690\u85CF\u672A\u8BA2\u9605\u6A21\u578B","models.filter_by_plan_hint":"\u5728\u804A\u5929\u6A21\u578B\u9009\u62E9\u5668\u4E2D\u81EA\u52A8\u9690\u85CF\u5F53\u524D ClinePass \u8BA2\u9605\u5957\u9910\u672A\u5305\u542B\u7684\u6A21\u578B\u3002","models.visible_models":"\u804A\u5929\u53EF\u89C1\u6A21\u578B","models.visible_models_hint":"\u9009\u62E9\u5728 DSH \u804A\u5929\u9009\u62E9\u5668\u4E2D\u663E\u793A\u7684\u6A21\u578B\uFF08\u4E3A\u7A7A\u65F6\u663E\u793A\u5168\u90E8\u53EF\u7528\u6A21\u578B\uFF09\u3002","models.in_plan":"\u5957\u9910\u5185","models.out_of_plan":"\u5957\u9910\u5916","models.group_plan":"\u5957\u9910\u5185\u6A21\u578B","models.group_other":"\u5176\u4ED6\u6A21\u578B","models.group_custom":"\u81EA\u5B9A\u4E49\u6A21\u578B","models.plan_only":"\u4EC5\u5957\u9910\u6A21\u578B","models.show_all":"\u663E\u793A\u5168\u90E8","models.clean_stale":"\u6E05\u7406\u8FC7\u671F ({count})","models.models_selected":"\u5DF2\u9009\u62E9 {selected} / {total} \u4E2A\u6A21\u578B\u7528\u4E8E\u804A\u5929","models.title":"DSH \u6A21\u578B\u9009\u62E9\u5217\u8868\uFF08\u5DF2\u542F\u7528 {enabled}/{total}\uFF09","models.desc":"\u6A21\u578B\u76EE\u5F55\u4E0E ClinePass \u5B98\u65B9\u8BA2\u9605\u5B9E\u65F6\u540C\u6B65\u3002\u65B0\u8BA2\u9605\u6A21\u578B\u9ED8\u8BA4\u81EA\u52A8\u542F\u7528\u3002\u53D6\u6D88\u52FE\u9009\u53EF\u5728 DSH \u5BF9\u8BDD\u6A21\u578B\u5217\u8868\u4E2D\u9690\u85CF\u4E0D\u9700\u8981\u7684\u6A21\u578B\u3002\u652F\u6301\u5728\u5BF9\u8BDD\u8F93\u5165\u6846\u4E0A\u65B9\u5207\u6362\u63A8\u7406\u601D\u8003\u5F3A\u5EA6\uFF08Effort\uFF09\u3002","models.reasoning_tooltip":"\u652F\u6301\u5728\u5BF9\u8BDD\u6846\u4E0A\u65B9\u6A21\u578B\u83DC\u5355\u4E2D\u9009\u62E9\u601D\u8003\u5F3A\u5EA6\uFF08Off / Low / Medium / High / Max\uFF09","models.sync":"\u540C\u6B65\u8BA2\u9605\u6A21\u578B","models.syncing":"\u6B63\u5728\u540C\u6B65\u2026","models.synced_msg":"\u5DF2\u6210\u529F\u540C\u6B65\u8BA2\u9605\u6A21\u578B\uFF1A\u5171 {total} \u4E2A\uFF08\u53D1\u73B0 {discovered} \u4E2A\u65B0\u6A21\u578B\uFF09","models.verified":"\u5DF2\u4E0E\u5957\u9910\u540C\u6B65","models.unverified":"\u672A\u540C\u6B65\uFF08\u5907\u7528\u76EE\u5F55\uFF09","models.synced_at":"\u540C\u6B65\u65F6\u95F4\uFF1A{date}","models.search_placeholder":"\u6309\u540D\u79F0\u3001ID \u6216\u63CF\u8FF0\u641C\u7D22\u6A21\u578B\u2026","models.filter_all":"\u5168\u90E8","models.filter_vision":"\u4EC5\u89C6\u89C9","models.filter_coding":"\u4EC5\u7F16\u7A0B","models.filter_recommended":"\u63A8\u8350","models.filter_disabled":"\u5DF2\u7981\u7528","models.all":"\u5168\u90E8\u6A21\u578B","models.vision":"\u4EC5\u89C6\u89C9 (Vision)","models.coding":"\u4EC5\u7F16\u7A0B (Coding)","models.recommended":"\u63A8\u8350\u6A21\u578B","models.th_active":"\u542F\u7528","models.th_name":"\u6A21\u578B\u540D\u79F0","models.th_id":"\u6A21\u578B\u6807\u8BC6\u7B26 (ID)","models.th_ctx":"\u4E0A\u4E0B\u6587\u7A97\u53E3","models.th_caps":"\u7279\u6027\u80FD\u529B","models.th_route":"\u6307\u5B9A\u8DEF\u7531\u8D26\u53F7","models.route_auto":"\u81EA\u52A8 / \u9ED8\u8BA4\u6C60","models.btn_probe":"\u6D4B\u8BD5\u5EF6\u8FDF","models.star":"\u63A8\u8350","models.auto_new":"\u65B0\u4E0A\u7EBF","models.apply_original_all":"\u539F\u5382\u89C4\u683C","models.reset_contexts_all":"\u21BA \u91CD\u7F6E\u4E0A\u4E0B\u6587","models.context_updated_msg":"\u6A21\u578B\u4E0A\u4E0B\u6587\u9650\u5236\u5DF2\u66F4\u65B0\u5E76\u540C\u6B65\u751F\u6548\u81F3 DSH\u3002","models.ctx_default":"\u9ED8\u8BA4 ({ctx} / {max})","models.ctx_original":"\u539F\u5382 ({ctx} / {max} \xB7 {provider})","models.ctx_edit":"\u7F16\u8F91\u9650\u5236","models.ctx_window_label":"\u4E0A\u4E0B\u6587 (\u5982 200000)","models.ctx_max_tokens_label":"\u6700\u5927\u8F93\u51FA (\u5982 16384)","models.ctx_apply_all":"\u5168\u90E8","models.ctx_apply_all_title":"\u5C06\u6B64\u9650\u5236\u6279\u91CF\u5E94\u7528\u5230\u6240\u6709\u6A21\u578B","models.ctx_custom_placeholder":"Token \u6570\u91CF (\u5982 2000000)","models.ctx_save":"\u4FDD\u5B58","models.ctx_cancel":"\u53D6\u6D88","models.custom_ctx_label":"\u4E0A\u4E0B\u6587","models.custom_max_label":"\u6700\u5927\u8F93\u51FA","models.ctx_tag_original":"\u539F\u5382","models.ctx_tag_custom":"\u81EA\u5B9A\u4E49","stats.title":"\u5F53\u524D DSH \u4F1A\u8BDD\u8BF7\u6C42\u9065\u6D4B","stats.desc":"\u672C\u5730\u5B9E\u65F6\u76D1\u63A7\u8BF7\u6C42\u6B21\u6570\u3001\u7F51\u7EDC\u5EF6\u8FDF\u4EE5\u53CA\u901A\u8FC7 ClinePass \u6D88\u8017\u7684\u4F30\u7B97 Token \u6570\u91CF\u3002","stats.requests":"\u6210\u529F / \u603B\u8BF7\u6C42\u6B21\u6570","stats.tokens":"\u4F1A\u8BDD\u7D2F\u8BA1 Token \u4F30\u7B97","stats.reasoning_tokens":"\u6DF1\u5EA6\u601D\u8003 Token \u6D88\u8017","stats.export_csv":"\u5BFC\u51FA CSV","stats.export_json":"\u5BFC\u51FA JSON","stats.latency":"\u6700\u8FD1\u4E00\u6B21\u8BF7\u6C42\u5EF6\u8FDF","stats.last_req":"\u6700\u8FD1\u8BF7\u6C42\u65F6\u95F4","models.custom_add_title":"\u81EA\u5B9A\u4E49\u6A21\u578B","models.custom_id":"\u6A21\u578B ID (\u4F8B\u5982 anthropic/claude-3-7-sonnet)","models.custom_name":"\u663E\u793A\u540D\u79F0 (\u53EF\u9009)","models.custom_btn_add":"\u6DFB\u52A0\u6A21\u578B","models.custom_btn_del":"\u5220\u9664","models.effort_auto":"\u81EA\u52A8\u601D\u8003\u5F3A\u5EA6","stats.errors429":"429 \u9650\u6D41\u4FDD\u62A4\u6B21\u6570","stats.top_models":"Token \u6D88\u8017\u6700\u9AD8\u6A21\u578B","stats.reset_btn":"\u91CD\u7F6E\u7EDF\u8BA1","stats.confirm_reset":"\u786E\u8BA4\u91CD\u7F6E\uFF1F","stats.cancel_reset":"\u53D6\u6D88","diag.title":"\u8BCA\u65AD\u5DE5\u5177\u4E0E\u670D\u52A1\u63A5\u5165","diag.desc":"\u914D\u7F6E API \u5BC6\u94A5\u540E\uFF0CClineBot \u4F1A\u81EA\u52A8\u5411 DSH \u6CE8\u518C\u6A21\u578B\u670D\u52A1\u3002\u60A8\u53EF\u4EE5\u5728\u6B64\u8FDB\u884C\u8FDE\u901A\u6027\u6D4B\u8BD5\u3002","diag.smoke_btn":"\u6267\u884C\u8FDE\u901A\u6027\u5192\u70DF\u6D4B\u8BD5 (Ping)","diag.smoke_testing":"\u6B63\u5728\u6D4B\u8BD5\u2026","update.available":"\u53D1\u73B0\u65B0\u7248\u672C v{latestVersion}\uFF08\u5F53\u524D\u7248\u672C v{currentVersion}\uFF09","update.btn":"\u7ACB\u5373\u66F4\u65B0","update.updating":"\u6B63\u5728\u66F4\u65B0\u2026","update.done":"\u66F4\u65B0\u6210\u529F\u81F3 v{version}\uFF01\u8BF7\u91CD\u542F DSH \u670D\u52A1\u751F\u6548\u3002","update.failed":"\u66F4\u65B0\u5931\u8D25\uFF1A{error}","update.checking":"\u6B63\u5728\u68C0\u67E5\u66F4\u65B0\u2026","update.up_to_date":"\u5DF2\u662F\u6700\u65B0\u7248\u672C","sidebar.quota_title":"Cline \u989D\u5EA6","sidebar.show_quota":"\u5728\u4FA7\u8FB9\u680F\u663E\u793A\u989D\u5EA6\u6761","sidebar.show_quota_hint":"\u5728 DSH \u4FA7\u8FB9\u680F\u5E95\u90E8\u5E38\u9A7B\u663E\u793A\u989D\u5EA6 mini \u8FDB\u5EA6\u6307\u793A\u5668\u3002","dashboard.title":"Cline \u989D\u5EA6\u4E0E\u72B6\u6001","dashboard.subtitle":"\u5B9E\u65F6\u8BA2\u9605\u989D\u5EA6\u3001\u6ED1\u52A8\u7A97\u53E3\u9650\u5236\u4E0E\u8D26\u6237\u6C60\u72B6\u6001\u3002","dashboard.close":"\u5173\u95ED\u9762\u677F","dashboard.refresh":"\u5237\u65B0","dashboard.refreshing":"\u6B63\u5728\u5237\u65B0\u2026","dashboard.rolling_window":"5\u5C0F\u65F6\u6ED1\u52A8\u7A97\u53E3","dashboard.weekly_window":"\u6BCF\u5468\u914D\u989D\u7A97\u53E3","dashboard.remaining":"\u5269\u4F59","dashboard.used":"\u5DF2\u7528","dashboard.account_pool":"\u5F53\u524D\u627F\u8F7D\u8D26\u6237","dashboard.credits":"\u70B9\u6570\u4F59\u989D","dashboard.updated_at":"\u66F4\u65B0\u4E8E","composer.cost_title":"ClineBot \u4F1A\u8BDD\u6210\u672C","composer.last_request":"\u6700\u8FD1\u4E00\u6B21\u8BF7\u6C42","composer.free":"\u514D\u8D39","composer.cost_breakdown":"Token \u4E0E\u8D39\u7528\u660E\u7EC6","composer.prompt_tok":"\u63D0\u793A\u8BCD Token","composer.completion_tok":"\u8865\u5168 Token","composer.reasoning_tok":"\u601D\u8003\u63A8\u7406 Token","composer.est_cost":"\u9884\u4F30\u82B1\u8D39","composer.unpriced":"\u65E0\u4EF7\u683C","composer.cache_read":"\u7F13\u5B58\u8BFB\u53D6","composer.cache_write":"\u7F13\u5B58\u5199\u5165","composer.price_source":"\u4EF7\u683C\u6765\u6E90","composer.price_estimate":"\u4F30\u7B97","composer.price_override":"\u8986\u76D6","composer.price_public":"\u516C\u5F00\u76EE\u5F55","discovery.title":"\u53D1\u73B0\u672C\u5730\u5BC6\u94A5","discovery.import_btn":"\u4ECE {source} \u5BFC\u5165","discovery.imported":"\u5DF2\u6210\u529F\u5BFC\u5165\u8D26\u6237\u6C60","discovery.scanning":"\u6B63\u5728\u626B\u63CF\u672C\u5730 Cline \u5BC6\u94A5\u2026","settings.unsaved":"{count} \u9879\u672A\u4FDD\u5B58\u66F4\u6539","settings.saving":"\u6B63\u5728\u4FDD\u5B58\u2026","settings.saved":"\u8BBE\u7F6E\u5DF2\u4FDD\u5B58","settings.save_failed":"\u65E0\u6CD5\u4FDD\u5B58\u8BBE\u7F6E","settings.save":"\u4FDD\u5B58","settings.discard":"\u653E\u5F03","settings.save_invalid_base_url":"Base URL \u5FC5\u987B\u4F7F\u7528 https:// \u6216 localhost\u3002","settings.save_invalid_number":"\u8D85\u65F6\u548C\u8D1F\u8F7D\u9650\u5236\u5FC5\u987B\u662F\u6B63\u6570\u3002","settings.models_group":"\u6A21\u578B\u4E0E\u4E0A\u4E0B\u6587","settings.models_group_hint":"\u5957\u9910\u8FC7\u6EE4\u3001\u53EF\u89C1\u6A21\u578B\u3001\u4E0A\u4E0B\u6587\u7A97\u53E3\u3001\u8F93\u51FA\u4E0A\u9650\u548C\u63A8\u7406\u9ED8\u8BA4\u503C\u3002","settings.integrations":"\u96C6\u6210\u4E0E\u663E\u793A","settings.integrations_desc":"\u804A\u5929\u8BF7\u6C42\u4E4B\u5916\u590D\u7528\u6B64\u670D\u52A1\u5546\u7684\u754C\u9762\u5165\u53E3\u3002","settings.web_search":"\u4F7F\u7528 ClineBot \u63D0\u4F9B\u7F51\u9875\u641C\u7D22","settings.web_search_hint":"\u4E3A DSH web_search \u5DE5\u5177\u4F7F\u7528 ClinePass \u7AEF\u70B9\u3002","settings.sidebar_quota":"\u5728\u4FA7\u8FB9\u680F\u663E\u793A\u914D\u989D","settings.sidebar_quota_hint":"\u5728\u8BBE\u7F6E\u4E0A\u65B9\u663E\u793A\u7D27\u51D1\u7684\u5957\u9910\u4E0E\u6EDA\u52A8\u7A97\u53E3\u914D\u989D\u3002","settings.composer_cost":"\u5728\u8F93\u5165\u6846\u663E\u793A\u4F1A\u8BDD\u6210\u672C","settings.composer_cost_hint":"\u5728\u8F93\u5165\u6846\u65C1\u663E\u793A\u9884\u4F30 token \u6210\u672C\u3002","settings.advanced":"\u9AD8\u7EA7","settings.advanced_desc":"\u8FDE\u63A5\u3001\u8D85\u65F6\u548C\u5B58\u50A8\u8DEF\u5F84\u3002","settings.base_url":"API \u57FA\u7840\u5730\u5740","settings.base_url_hint":"ClinePass API \u6839\u5730\u5740\u3002\u8FDC\u7A0B\u7AEF\u70B9\u5FC5\u987B\u4F7F\u7528 https://\u3002","settings.timeout":"\u8FDE\u63A5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09","settings.timeout_hint":"\u7B49\u5F85\u4E0A\u6E38\u54CD\u5E94\u5934\u7684\u6700\u5927\u65F6\u95F4\u3002","settings.smoke_timeout":"\u70DF\u96FE\u6D4B\u8BD5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09","settings.smoke_timeout_hint":"\u7B49\u5F85\u8BCA\u65AD\u804A\u5929\u8865\u5168\u7684\u6700\u5927\u65F6\u95F4\u3002","settings.stream_idle":"\u6D41\u7A7A\u95F2\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09","settings.stream_idle_hint":"SSE \u6570\u636E\u5757\u4E4B\u95F4\u7684\u6700\u5927\u95F4\u9694\uFF0C\u8D85\u8FC7\u540E\u89C6\u4E3A\u65AD\u6D41\u3002","settings.max_payload":"\u591A\u6A21\u6001\u8D1F\u8F7D\u4E0A\u9650\uFF08MB\uFF09","settings.max_payload_hint":"\u8BF7\u6C42\u8D85\u8FC7\u9884\u7B97\u65F6\u4F1A\u6DD8\u6C70\u6700\u65E7\u7684\u56FE\u7247\u3002","settings.stats_retention":"\u7EDF\u8BA1\u4FDD\u7559\u5929\u6570","settings.stats_retention_hint":"\u8D85\u8FC7\u8BE5\u5929\u6570\u7684\u65E5/\u5C0F\u65F6/\u4F1A\u8BDD\u9065\u6D4B\u4F1A\u88AB\u6E05\u7406\u30020 \u8868\u793A\u6C38\u4E45\u4FDD\u7559\u3002","settings.daily_budget":"\u6BCF\u65E5\u9884\u7B97\uFF08USD\uFF09","settings.daily_budget_hint":"\u6309\u5F53\u524D UTC \u65E5\u4F30\u7B97\u7684\u82B1\u8D39\u4E0A\u9650\u30020 \u8868\u793A\u5173\u95ED\u3002","settings.monthly_budget":"\u6BCF\u6708\u9884\u7B97\uFF08USD\uFF09","settings.monthly_budget_hint":"\u6309\u5F53\u524D UTC \u6708\u4F30\u7B97\u7684\u82B1\u8D39\u4E0A\u9650\u30020 \u8868\u793A\u5173\u95ED\u3002","settings.stats_path":"\u7EDF\u8BA1\u6587\u4EF6\u8DEF\u5F84","settings.stats_path_hint":"\u6301\u4E45\u5316 JSON \u9065\u6D4B\u8DEF\u5F84\uFF08\u652F\u6301 ~/\uFF09\u3002","settings.models_cache_path":"\u6A21\u578B\u7F13\u5B58\u8DEF\u5F84","settings.models_cache_path_hint":"\u79BB\u7EBF\u6A21\u578B\u76EE\u5F55\u7F13\u5B58\uFF08\u652F\u6301 ~/\uFF09\u3002","providerCard.title":"ClineBot\uFF08ClinePass\uFF09","providerCard.hint":"\u7C98\u8D34 ClinePass API \u5BC6\u94A5\uFF0C\u6216\u524D\u5F80\u201C\u8BBE\u7F6E \u2192 ClineBot\u201D\u7BA1\u7406\u5B8C\u6574\u8D26\u6237\u6C60\u3002","providerCard.keyPresent":"\u5BC6\u94A5\u5DF2\u914D\u7F6E","providerCard.keyMissing":"\u7F3A\u5C11\u5BC6\u94A5","providerCard.routeActive":"\u8DEF\u7531\u5DF2\u542F\u7528","providerCard.placeholder":"\u7C98\u8D34 API \u5BC6\u94A5","providerCard.replace":"\u66FF\u6362 API \u5BC6\u94A5","providerCard.save":"\u4FDD\u5B58\u5BC6\u94A5","providerCard.saving":"\u6B63\u5728\u4FDD\u5B58\u2026","providerCard.saved":"\u5BC6\u94A5\u5DF2\u4FDD\u5B58\u5E76\u9A8C\u8BC1","providerCard.failed":"\u65E0\u6CD5\u4FDD\u5B58\u5BC6\u94A5","providerCard.show":"\u663E\u793A","providerCard.hide":"\u9690\u85CF","providerCard.openSettings":"\u524D\u5F80\u201C\u8BBE\u7F6E \u2192 ClineBot\u201D\u7BA1\u7406\u8D26\u6237\u3001\u6A21\u578B\u548C\u914D\u989D\u3002","dashboard.tab_quota":"\u914D\u989D","dashboard.tab_analytics":"\u5206\u6790","dashboard.auto_account":"\u81EA\u52A8\uFF08\u6700\u5C11\u4F7F\u7528\uFF09","analytics.title":"\u7528\u91CF\u5206\u6790","analytics.requests":"\u8BF7\u6C42\u6570","analytics.tokens":"Token","analytics.cost":"\u9884\u4F30\u6210\u672C","analytics.success":"\u6210\u529F\u7387","analytics.ttft":"\u5E73\u5747\u9996\u5B57\u5EF6\u8FDF","analytics.loading":"\u6B63\u5728\u52A0\u8F7D\u5206\u6790\u2026","analytics.no_data":"\u6682\u65E0\u5206\u6790\u6570\u636E\uFF0C\u8BF7\u5148\u53D1\u9001\u51E0\u6761\u6D88\u606F\u3002","analytics.unpriced":"{count} \u4E2A\u8BF7\u6C42\u6CA1\u6709\u4EF7\u683C\u4F30\u7B97\uFF0C\u672A\u8BA1\u5165\u6210\u672C\u5408\u8BA1\u3002","analytics.group_model":"\u6A21\u578B","analytics.group_day":"\u65E5\u671F","analytics.group_session":"\u4F1A\u8BDD","analytics.heatmap":"\u8BF7\u6C42\u70ED\u529B\u56FE","analytics.heatmap_hint":"\u6700\u8FD1 7 \u5929\u6BCF\u5C0F\u65F6\u7684\u8BF7\u6C42\u6570\u3002","analytics.heatmap_less":"\u5C11","analytics.heatmap_more":"\u591A","analytics.heatmap_empty":"\u6682\u65E0\u6309\u5C0F\u65F6\u6570\u636E\u3002\u53D1\u9001\u4E00\u6761\u6D88\u606F\u540E\u5F00\u59CB\u8BB0\u5F55\u3002","analytics.input":"\u8F93\u5165","analytics.output":"\u8F93\u51FA","analytics.reasoning":"\u63A8\u7406","analytics.cache_read":"\u7F13\u5B58\u8BFB\u53D6","analytics.cache_write":"\u7F13\u5B58\u5199\u5165","analytics.avg_ttft":"\u5E73\u5747\u9996\u5B57\u5EF6\u8FDF","analytics.avg_latency":"\u5E73\u5747\u5EF6\u8FDF","analytics.errors":"\u9519\u8BEF","analytics.p50_latency":"P50 \u5EF6\u8FDF","analytics.p95_latency":"P95 \u5EF6\u8FDF","analytics.heatmap_selected":"{day} {hour}:00 \u2014 {count} \u6B21\u8BF7\u6C42\u3002\u518D\u6B21\u70B9\u51FB\u53EF\u6E05\u9664\u3002","analytics.no_key":"\u5C1A\u672A\u914D\u7F6E ClinePass \u5BC6\u94A5\u3002","analytics.no_key_hint":"\u6253\u5F00\u201C\u8BBE\u7F6E \u2192 ClineBot\u201D\u7C98\u8D34\u5BC6\u94A5\uFF0C\u6216\u5BFC\u5165\u5DF2\u53D1\u73B0\u7684\u672C\u5730\u5BC6\u94A5\u3002","analytics.health_error":"ClinePass \u7AEF\u70B9\u4E0D\u53EF\u8FBE\u3002","analytics.not_registered":"ClineBot \u63D0\u4F9B\u5546\u672A\u5728 DSH \u4E2D\u6CE8\u518C\u3002","analytics.not_registered_hint":"\u6253\u5F00\u201C\u8BBE\u7F6E \u2192 ClineBot\u201D\u68C0\u67E5\u8BCA\u65AD\u3002","analytics.stale":"\u6B63\u5728\u663E\u793A\u4E0A\u6B21\u6210\u529F\u83B7\u53D6\u7684\u6570\u636E\u3002","analytics.range":"\u8303\u56F4","analytics.range_7":"7 \u5929","analytics.range_30":"30 \u5929","analytics.range_90":"90 \u5929","analytics.heatmap_all_models":"\u6240\u6709\u6A21\u578B","analytics.heatmap_peak":"\u5CF0\u503C","analytics.heatmap_by_hour":"\u6309\u5C0F\u65F6","analytics.heatmap_model":"\u70ED\u529B\u56FE\u6A21\u578B","quota.no_windows":"\u672A\u8FD4\u56DE\u914D\u989D\u7A97\u53E3\u3002","prices.title":"\u4EF7\u683C\u8986\u76D6","prices.desc":"\u6BCF 100 \u4E07 token \u7684\u7F8E\u5143\u4EF7\u683C\u3002\u7559\u7A7A\u4F7F\u7528\u672C\u5730\u4F30\u7B97\u3002","prices.show":"\u663E\u793A\u4EF7\u683C","prices.hide":"\u9690\u85CF\u4EF7\u683C","prices.model":"\u6A21\u578B","prices.input":"\u8F93\u5165","prices.output":"\u8F93\u51FA","prices.cache_read":"\u7F13\u5B58\u8BFB\u53D6","prices.cache_write":"\u7F13\u5B58\u5199\u5165","prices.auto":"\u81EA\u52A8","prices.empty":"\u6682\u65E0\u6A21\u578B\u3002","prices.hint":"\u8FD9\u4E9B\u4EF7\u683C\u53EA\u5F71\u54CD\u8F93\u5165\u6846\u6210\u672C\u548C\u672C\u5730\u5206\u6790\uFF0C\u4E0D\u662F\u8D26\u5355\u3002","prices.public_hint":"\u540C\u6B65\u4F7F\u7528\u516C\u5F00\u7684 models.dev \u76EE\u5F55\u4F5C\u4E3A\u4F30\u7B97\uFF1B\u624B\u52A8\u8986\u76D6\u59CB\u7EC8\u4F18\u5148\u3002","prices.sync":"\u540C\u6B65\u516C\u5F00\u4EF7\u683C","prices.syncing":"\u6B63\u5728\u540C\u6B65\u2026","prices.synced":"\u516C\u5F00\u76EE\u5F55\u5DF2\u540C\u6B65\uFF1A{count} \u4E2A\u6A21\u578B","prices.sync_failed":"\u4EF7\u683C\u540C\u6B65\u5931\u8D25\uFF1A{error}"};
|
|
18
|
+
const zh={nav:"ClineBot",title:"ClineBot",subtitle:"ClinePass \u8BA2\u9605\u670D\u52A1\uFF0C\u5305\u542B\u52A8\u6001\u5957\u9910\u6A21\u578B\u3002","settings.loading":"\u6B63\u5728\u52A0\u8F7D ClineBot \u8BBE\u7F6E\u2026","settings.retry":"\u91CD\u8BD5","settings.unavailable":"ClineBot \u8BBE\u7F6E\u4E0D\u53EF\u7528\uFF08\u5BBF\u4E3B\u547D\u540D\u7A7A\u95F4\u5C1A\u672A\u5C31\u7EEA\uFF09\u3002","api.timeout":"\u8BF7\u6C42\u8D85\u65F6\uFF08{seconds}\u79D2\uFF09","header.title":"ClineBot (ClinePass) \u6A21\u578B\u670D\u52A1\u5546","header.sub":"\u901A\u8FC7 ClinePass \u8BA2\u9605\u8FDE\u63A5\u4E3B\u6D41\u63A8\u7406\u4E0E\u7F16\u7A0B\u6A21\u578B\u3002\u5B8C\u5168\u517C\u5BB9 OpenAI \u63A5\u53E3\uFF0C\u81EA\u52A8\u8FFD\u8E2A\u6EDA\u52A8\u9650\u989D\u5E76\u5728 DSH \u4E2D\u5373\u65F6\u751F\u6548\u3002","badge.online":"\u670D\u52A1\u5728\u7EBF ({latency} ms)","badge.offline":"\u4E3B\u673A\u65E0\u6CD5\u8FDE\u63A5","badge.key_ok":"\u5BC6\u94A5\u6B63\u5E38 ({source})","badge.key_missing":"\u672A\u914D\u7F6E\u5BC6\u94A5","badge.registered":"\u5DF2\u63A5\u5165 DSH ({count} \u4E2A\u6A21\u578B)","badge.not_registered":"\u672A\u63A5\u5165 DSH","key.title":"\u6388\u6743\u4E0E API \u5BC6\u94A5","key.desc":"\u5BC6\u94A5\u76F4\u63A5\u52A0\u5BC6\u4FDD\u5B58\u5728 DSH \u51ED\u636E\u7BA1\u7406\u5668 (~/.dsh/.credentials.yaml)\uFF0C\u7EDD\u4E0D\u4F1A\u4EE5\u660E\u6587\u5F62\u5F0F\u66B4\u9732\u5728\u666E\u901A\u914D\u7F6E\u6587\u4EF6\u4E2D\u3002","key.placeholder_has":"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022","key.placeholder_empty":"\u7C98\u8D34 ClinePass \u5BC6\u94A5 (cline_...)","key.show":"\u663E\u793A","key.hide":"\u9690\u85CF","key.save":"\u4FDD\u5B58\u5BC6\u94A5","key.saving":"\u6B63\u5728\u4FDD\u5B58\u2026","key.login_fast":"\u6253\u5F00 API \u5BC6\u94A5\u9875 \u2197","key.verifying":"\u6B63\u5728\u8FDE\u63A5 Cline \u9A8C\u8BC1\u5BC6\u94A5\u2026","key.verified":"\u5BC6\u94A5\u6709\u6548\uFF1A{email} ({plan})","key.invalid":"\u5BC6\u94A5\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF1A{error}","key.env_label":"\u51ED\u636E\u73AF\u5883\u53D8\u91CF\u540D\u79F0\uFF1A","key.get_key":"\u524D\u5F80 app.cline.bot \u63A7\u5236\u53F0\u83B7\u53D6\u5BC6\u94A5 \u2197","key.saved_msg":"\u5BC6\u94A5\u5DF2\u6210\u529F\u4FDD\u5B58\u81F3 DSH \u51ED\u636E\u670D\u52A1\u3002\u9A8C\u8BC1\u72B6\u6001\uFF1A{status}","key.empty_err":"\u4FDD\u5B58\u524D\u8BF7\u8F93\u5165 API \u5BC6\u94A5","accounts.title":"\u591A\u8D26\u53F7\u6545\u969C\u8F6C\u79FB\u4E0E\u8F6E\u6362\u6C60 (Failover)","accounts.desc":"\u6DFB\u52A0\u5907\u7528\u8D26\u53F7\u3002\u5F53\u67D0\u4E2A\u8D26\u53F7\u89E6\u53D1 429 \u901F\u7387\u9650\u5236\u6216\u914D\u989D\u8017\u5C3D\u65F6\uFF0CClineBot \u4F1A\u667A\u80FD\u65E0\u7F1D\u5207\u6362\u81F3\u5269\u4F59\u989D\u5EA6\u6700\u9AD8\u7684\u5907\u7528\u8D26\u53F7\u3002","accounts.active_badge":"\u5F53\u524D\u6D3B\u52A8","accounts.pinned_badge":"\u56FA\u5B9A\u4F7F\u7528","accounts.pin_btn":"\u8BBE\u4E3A\u6D3B\u52A8","accounts.add_btn":"+ \u6DFB\u52A0\u8D26\u53F7","accounts.label_placeholder":"\u8D26\u53F7\u6807\u7B7E\uFF08\u4F8B\u5982\uFF1A\u5DE5\u4F5C\u8D26\u53F7\uFF09","accounts.env_placeholder":"CLINEBOT_API_KEY_2","accounts.delete_btn":"\u5220\u9664","accounts.delete_confirm":"\u786E\u5B9A\u8981\u5220\u9664\u8D26\u53F7\u201C{label}\u201D\u5417\uFF1F","accounts.save_btn":"\u4FDD\u5B58\u8D26\u53F7","accounts.cancel_btn":"\u53D6\u6D88","accounts.quota_used":"\u5DF2\u7528 {pct}%","accounts.last_failover":"\u6700\u8FD1\u6545\u969C\u8F6C\u79FB\uFF1A{from} \u2192 {to} ({reason})","accounts.mode_label":"\u8C03\u5EA6\u7B56\u7565","accounts.mode_least_used":"\u4F4E\u5360\u7528\u4F18\u5148 (Least Used)","accounts.mode_round_robin":"\u987A\u5E8F\u8F6E\u8BE2 (Round Robin)","accounts.history_show":"\u5386\u53F2","accounts.history_hide":"\u9690\u85CF\u5386\u53F2","accounts.history_title":"\u8D26\u6237\u4E8B\u4EF6\u5386\u53F2","accounts.history_hint":"\u6700\u8FD1\u7684\u51B7\u5374\u3001\u6062\u590D\u548C\u8DEF\u7531\u51B3\u7B56\u3002\u6700\u65B0\u5728\u524D\uFF0C\u6700\u591A 50 \u6761\u3002","accounts.history_empty":"\u6682\u65E0\u8D26\u6237\u4E8B\u4EF6\u3002","accounts.history_loading":"\u6B63\u5728\u52A0\u8F7D\u5386\u53F2\u2026","accounts.history_until":"\u81F3 {time}","accounts.event_cooldown":"\u51B7\u5374","accounts.event_recover":"\u5DF2\u6062\u590D","accounts.event_route":"\u5DF2\u8DEF\u7531","accounts.routes_title":"\u7279\u5B9A\u6A21\u578B\u4E13\u5C5E\u8DEF\u7531 (Per-Model)","accounts.routes_desc":"\u53EF\u9488\u5BF9\u7279\u5B9A\u6A21\u578B\u6307\u5B9A\u7ED1\u5B9A\u7684\u8D26\u53F7\u51ED\u636E\uFF08\u5982\u5C06\u6602\u8D35\u6A21\u578B\u6307\u5411\u7279\u5B9A\u8D26\u53F7\uFF09\u3002","accounts.route_model_placeholder":"\u6A21\u578B ID (\u4F8B\u5982 cline-pass/deepseek-v4-pro)","accounts.route_account_placeholder":"\u76EE\u6807\u8D26\u53F7","accounts.route_btn_add":"\u6DFB\u52A0\u8DEF\u7531","accounts.actions":"\u8D26\u6237\u64CD\u4F5C","accounts.configured":"\u5DF2\u914D\u7F6E","accounts.missing_key":"\u7F3A\u5C11\u5BC6\u94A5","accounts.source":"\u6765\u6E90\uFF1A{source}","accounts.quota_5h":"5\u5C0F\u65F6 {pct}%","accounts.rename":"\u91CD\u547D\u540D","accounts.rename_save":"\u4FDD\u5B58\u540D\u79F0","accounts.rename_placeholder":"\u8D26\u6237\u540D\u79F0","accounts.clear_key":"\u6E05\u9664\u5DF2\u5B58\u5BC6\u94A5","accounts.clear_confirm":"\u6E05\u9664 {name} \u7684\u5DF2\u5B58\u5BC6\u94A5\uFF1F\u8D26\u6237\u884C\u4F1A\u4FDD\u7559\u3002","accounts.remove_confirm":"\u4ECE\u6C60\u4E2D\u79FB\u9664 {name}\uFF1F","accounts.confirm":"\u786E\u8BA4","accounts.cancel":"\u53D6\u6D88","accounts.unpin":"\u53D6\u6D88\u56FA\u5B9A","accounts.no_accounts":"\u5C1A\u672A\u914D\u7F6E\u8D26\u6237\u3002","accounts.route_remove":"\u79FB\u9664\u8DEF\u7531","quota.title":"ClinePass \u7528\u91CF\u914D\u989D\u4E0E\u9650\u989D\u76D1\u63A7","quota.desc":"\u6765\u81EA ClinePass \u5B98\u65B9\u7684\u5B9E\u65F6\u6EDA\u52A8\u7A97\u53E3\u7528\u91CF\u9650\u989D","quota.account":"\u8D26\u53F7\uFF1A{email} \xB7 \u5957\u9910\uFF1A{plan}","quota.refresh":"\u5237\u65B0\u914D\u989D","quota.refreshing":"\u6B63\u5728\u5237\u65B0\u2026","quota.refreshed_msg":"\u8BA2\u9605\u7528\u91CF\u914D\u989D\u5DF2\u5237\u65B0","quota.window_5h":"\u23F1 5 \u5C0F\u65F6\u6EDA\u52A8\u9650\u989D","quota.window_weekly":"\u6BCF\u5468\u6EDA\u52A8\u7A97\u53E3","quota.used":"\u5DF2\u4F7F\u7528 {pct}%","quota.reset_at":"\u91CD\u7F6E\u65F6\u95F4\uFF1A{time}","quota.window_monthly":"\u6BCF\u6708\u6EDA\u52A8\u7A97\u53E3","quota.countdown":"{time}\u540E\u91CD\u7F6E","quota.resetting":"\u6B63\u5728\u91CD\u7F6E","quota.warning_low":"\u989D\u5EA6\u4F4E\u4E8E 10%\uFF0C\u8BF7\u6C42\u53EF\u80FD\u5F88\u5FEB\u4F1A\u53D7\u5230\u9650\u5236\u3002","quota.forecast_no_usage":"\u5C1A\u672A\u4F7F\u7528","quota.forecast_holds":"\u6309\u5F53\u524D\u901F\u5EA6\u53EF\u575A\u6301\u5230\u91CD\u7F6E","quota.forecast_left":"\u6309\u5F53\u524D\u901F\u5EA6\u7EA6\u5269 {time}","quota.forecast_exhausted":"\u5DF2\u8017\u5C3D","quota.exhausted_5h":"5 \u5C0F\u65F6\u6EDA\u52A8\u9650\u989D\u5DF2\u63A5\u8FD1\u8017\u5C3D\uFF08\u5DF2\u7528 {percentUsed}%\uFF09\u3002\u5728\u989D\u5EA6\u91CD\u7F6E\u524D\u65B0\u8BF7\u6C42\u53EF\u80FD\u4F1A\u88AB\u62D2\u7EDD\u3002","quota.warning_5h":"\u4F4E\u989D\u5EA6\u8B66\u544A\uFF1A5 \u5C0F\u65F6\u9650\u989D\u5DF2\u6D88\u8017 {percentUsed}%\uFF08\u5269\u4F59\u4E0D\u8DB3 10%\uFF09\u3002","quota.warning_weekly":"\u5468\u9650\u989D\u504F\u4F4E\u8B66\u544A\uFF1A\u5DF2\u6D88\u8017 {percentUsed}%\uFF08\u5269\u4F59 {remainingPercent}%\uFF09\u3002","quota.warning_monthly":"\u6708\u9650\u989D\u504F\u4F4E\u8B66\u544A\uFF1A\u5DF2\u6D88\u8017 {percentUsed}%\uFF08\u5269\u4F59 {remainingPercent}%\uFF09\u3002","models.reasoning_tag":"\u6DF1\u5EA6\u601D\u8003","models.effort_low":"\u4F4E","models.effort_medium":"\u4E2D","models.effort_high":"\u9AD8","models.effort_max":"\u6700\u9AD8","models.caps_vision":"\u89C6\u89C9","models.caps_general":"\u901A\u7528","models.caps_coding":"\u7F16\u7A0B","diag.smoke_result":"\u5EF6\u8FDF\uFF1A{latency} ms | \u6A21\u578B\uFF1A{model}\n\u54CD\u5E94\uFF1A{preview}","diag.empty":"(\u7A7A)","ui.error_title":"ClineBot \u754C\u9762\u9519\u8BEF\uFF1A","ui.retry":"\u91CD\u8BD5","update.check_failed":"\u68C0\u67E5\u66F4\u65B0\u5931\u8D25","update.retry":"\u91CD\u8BD5\u68C0\u67E5","models.filter_by_plan":"\u9690\u85CF\u672A\u8BA2\u9605\u6A21\u578B","models.filter_by_plan_hint":"\u5728\u804A\u5929\u6A21\u578B\u9009\u62E9\u5668\u4E2D\u81EA\u52A8\u9690\u85CF\u5F53\u524D ClinePass \u8BA2\u9605\u5957\u9910\u672A\u5305\u542B\u7684\u6A21\u578B\u3002","models.visible_models":"\u804A\u5929\u53EF\u89C1\u6A21\u578B","models.visible_models_hint":"\u9009\u62E9\u5728 DSH \u804A\u5929\u9009\u62E9\u5668\u4E2D\u663E\u793A\u7684\u6A21\u578B\uFF08\u4E3A\u7A7A\u65F6\u663E\u793A\u5168\u90E8\u53EF\u7528\u6A21\u578B\uFF09\u3002","models.in_plan":"\u5957\u9910\u5185","models.out_of_plan":"\u5957\u9910\u5916","models.group_plan":"\u5957\u9910\u5185\u6A21\u578B","models.group_other":"\u5176\u4ED6\u6A21\u578B","models.group_custom":"\u81EA\u5B9A\u4E49\u6A21\u578B","models.plan_only":"\u4EC5\u5957\u9910\u6A21\u578B","models.show_all":"\u663E\u793A\u5168\u90E8","models.clean_stale":"\u6E05\u7406\u8FC7\u671F ({count})","models.models_selected":"\u5DF2\u9009\u62E9 {selected} / {total} \u4E2A\u6A21\u578B\u7528\u4E8E\u804A\u5929","models.title":"DSH \u6A21\u578B\u9009\u62E9\u5217\u8868\uFF08\u5DF2\u542F\u7528 {enabled}/{total}\uFF09","models.desc":"\u6A21\u578B\u76EE\u5F55\u4E0E ClinePass \u5B98\u65B9\u8BA2\u9605\u5B9E\u65F6\u540C\u6B65\u3002\u65B0\u8BA2\u9605\u6A21\u578B\u9ED8\u8BA4\u81EA\u52A8\u542F\u7528\u3002\u53D6\u6D88\u52FE\u9009\u53EF\u5728 DSH \u5BF9\u8BDD\u6A21\u578B\u5217\u8868\u4E2D\u9690\u85CF\u4E0D\u9700\u8981\u7684\u6A21\u578B\u3002\u652F\u6301\u5728\u5BF9\u8BDD\u8F93\u5165\u6846\u4E0A\u65B9\u5207\u6362\u63A8\u7406\u601D\u8003\u5F3A\u5EA6\uFF08Effort\uFF09\u3002","models.reasoning_tooltip":"\u652F\u6301\u5728\u5BF9\u8BDD\u6846\u4E0A\u65B9\u6A21\u578B\u83DC\u5355\u4E2D\u9009\u62E9\u601D\u8003\u5F3A\u5EA6\uFF08Off / Low / Medium / High / Max\uFF09","models.sync":"\u540C\u6B65\u8BA2\u9605\u6A21\u578B","models.syncing":"\u6B63\u5728\u540C\u6B65\u2026","models.synced_msg":"\u5DF2\u6210\u529F\u540C\u6B65\u8BA2\u9605\u6A21\u578B\uFF1A\u5171 {total} \u4E2A\uFF08\u53D1\u73B0 {discovered} \u4E2A\u65B0\u6A21\u578B\uFF09","models.verified":"\u5DF2\u4E0E\u5957\u9910\u540C\u6B65","models.unverified":"\u672A\u540C\u6B65\uFF08\u5907\u7528\u76EE\u5F55\uFF09","models.synced_at":"\u540C\u6B65\u65F6\u95F4\uFF1A{date}","models.search_placeholder":"\u6309\u540D\u79F0\u3001ID \u6216\u63CF\u8FF0\u641C\u7D22\u6A21\u578B\u2026","models.filter_all":"\u5168\u90E8","models.filter_vision":"\u4EC5\u89C6\u89C9","models.filter_coding":"\u4EC5\u7F16\u7A0B","models.filter_recommended":"\u63A8\u8350","models.filter_disabled":"\u5DF2\u7981\u7528","models.all":"\u5168\u90E8\u6A21\u578B","models.vision":"\u4EC5\u89C6\u89C9 (Vision)","models.coding":"\u4EC5\u7F16\u7A0B (Coding)","models.recommended":"\u63A8\u8350\u6A21\u578B","models.th_active":"\u542F\u7528","models.th_name":"\u6A21\u578B\u540D\u79F0","models.th_id":"\u6A21\u578B\u6807\u8BC6\u7B26 (ID)","models.th_ctx":"\u4E0A\u4E0B\u6587\u7A97\u53E3","models.th_caps":"\u7279\u6027\u80FD\u529B","models.th_route":"\u6307\u5B9A\u8DEF\u7531\u8D26\u53F7","models.route_auto":"\u81EA\u52A8 / \u9ED8\u8BA4\u6C60","models.btn_probe":"\u6D4B\u8BD5\u5EF6\u8FDF","models.star":"\u63A8\u8350","models.auto_new":"\u65B0\u4E0A\u7EBF","models.apply_original_all":"\u539F\u5382\u89C4\u683C","models.reset_contexts_all":"\u21BA \u91CD\u7F6E\u4E0A\u4E0B\u6587","models.context_updated_msg":"\u6A21\u578B\u4E0A\u4E0B\u6587\u9650\u5236\u5DF2\u66F4\u65B0\u5E76\u540C\u6B65\u751F\u6548\u81F3 DSH\u3002","models.ctx_default":"\u9ED8\u8BA4 ({ctx} / {max})","models.ctx_original":"\u539F\u5382 ({ctx} / {max} \xB7 {provider})","models.ctx_edit":"\u7F16\u8F91\u9650\u5236","models.ctx_window_label":"\u4E0A\u4E0B\u6587 (\u5982 200000)","models.ctx_max_tokens_label":"\u6700\u5927\u8F93\u51FA (\u5982 16384)","models.ctx_apply_all":"\u5168\u90E8","models.ctx_apply_all_title":"\u5C06\u6B64\u9650\u5236\u6279\u91CF\u5E94\u7528\u5230\u6240\u6709\u6A21\u578B","models.ctx_custom_placeholder":"Token \u6570\u91CF (\u5982 2000000)","models.ctx_save":"\u4FDD\u5B58","models.ctx_cancel":"\u53D6\u6D88","models.custom_ctx_label":"\u4E0A\u4E0B\u6587","models.custom_max_label":"\u6700\u5927\u8F93\u51FA","models.ctx_tag_original":"\u539F\u5382","models.ctx_tag_custom":"\u81EA\u5B9A\u4E49","stats.title":"\u5F53\u524D DSH \u4F1A\u8BDD\u8BF7\u6C42\u9065\u6D4B","stats.desc":"\u672C\u5730\u5B9E\u65F6\u76D1\u63A7\u8BF7\u6C42\u6B21\u6570\u3001\u7F51\u7EDC\u5EF6\u8FDF\u4EE5\u53CA\u901A\u8FC7 ClinePass \u6D88\u8017\u7684\u4F30\u7B97 Token \u6570\u91CF\u3002","stats.requests":"\u6210\u529F / \u603B\u8BF7\u6C42\u6B21\u6570","stats.tokens":"\u4F1A\u8BDD\u7D2F\u8BA1 Token \u4F30\u7B97","stats.reasoning_tokens":"\u6DF1\u5EA6\u601D\u8003 Token \u6D88\u8017","stats.export_csv":"\u5BFC\u51FA CSV","stats.export_json":"\u5BFC\u51FA JSON","stats.latency":"\u6700\u8FD1\u4E00\u6B21\u8BF7\u6C42\u5EF6\u8FDF","stats.last_req":"\u6700\u8FD1\u8BF7\u6C42\u65F6\u95F4","models.custom_add_title":"\u81EA\u5B9A\u4E49\u6A21\u578B","models.custom_id":"\u6A21\u578B ID (\u4F8B\u5982 anthropic/claude-3-7-sonnet)","models.custom_name":"\u663E\u793A\u540D\u79F0 (\u53EF\u9009)","models.custom_btn_add":"\u6DFB\u52A0\u6A21\u578B","models.custom_btn_del":"\u5220\u9664","models.effort_auto":"\u81EA\u52A8\u601D\u8003\u5F3A\u5EA6","stats.errors429":"429 \u9650\u6D41\u4FDD\u62A4\u6B21\u6570","stats.top_models":"Token \u6D88\u8017\u6700\u9AD8\u6A21\u578B","stats.top_accounts":"Token \u6D88\u8017\u6700\u9AD8\u8D26\u6237","stats.cache_saved":"\u7F13\u5B58\u8282\u7701\uFF1A\u2248 ${amount}","stats.reset_btn":"\u91CD\u7F6E\u7EDF\u8BA1","stats.confirm_reset":"\u786E\u8BA4\u91CD\u7F6E\uFF1F","stats.cancel_reset":"\u53D6\u6D88","diag.title":"\u8BCA\u65AD\u5DE5\u5177\u4E0E\u670D\u52A1\u63A5\u5165","diag.desc":"\u914D\u7F6E API \u5BC6\u94A5\u540E\uFF0CClineBot \u4F1A\u81EA\u52A8\u5411 DSH \u6CE8\u518C\u6A21\u578B\u670D\u52A1\u3002\u60A8\u53EF\u4EE5\u5728\u6B64\u8FDB\u884C\u8FDE\u901A\u6027\u6D4B\u8BD5\u3002","diag.smoke_btn":"\u6267\u884C\u8FDE\u901A\u6027\u5192\u70DF\u6D4B\u8BD5 (Ping)","diag.smoke_testing":"\u6B63\u5728\u6D4B\u8BD5\u2026","update.available":"\u53D1\u73B0\u65B0\u7248\u672C v{latestVersion}\uFF08\u5F53\u524D\u7248\u672C v{currentVersion}\uFF09","update.btn":"\u7ACB\u5373\u66F4\u65B0","update.updating":"\u6B63\u5728\u66F4\u65B0\u2026","update.done":"\u66F4\u65B0\u6210\u529F\u81F3 v{version}\uFF01\u8BF7\u91CD\u542F DSH \u670D\u52A1\u751F\u6548\u3002","update.failed":"\u66F4\u65B0\u5931\u8D25\uFF1A{error}","update.checking":"\u6B63\u5728\u68C0\u67E5\u66F4\u65B0\u2026","update.up_to_date":"\u5DF2\u662F\u6700\u65B0\u7248\u672C","sidebar.quota_title":"Cline \u989D\u5EA6","sidebar.show_quota":"\u5728\u4FA7\u8FB9\u680F\u663E\u793A\u989D\u5EA6\u6761","sidebar.show_quota_hint":"\u5728 DSH \u4FA7\u8FB9\u680F\u5E95\u90E8\u5E38\u9A7B\u663E\u793A\u989D\u5EA6 mini \u8FDB\u5EA6\u6307\u793A\u5668\u3002","dashboard.title":"Cline \u989D\u5EA6\u4E0E\u72B6\u6001","dashboard.subtitle":"\u5B9E\u65F6\u8BA2\u9605\u989D\u5EA6\u3001\u6ED1\u52A8\u7A97\u53E3\u9650\u5236\u4E0E\u8D26\u6237\u6C60\u72B6\u6001\u3002","dashboard.close":"\u5173\u95ED\u9762\u677F","dashboard.refresh":"\u5237\u65B0","dashboard.refreshing":"\u6B63\u5728\u5237\u65B0\u2026","dashboard.rolling_window":"5\u5C0F\u65F6\u6ED1\u52A8\u7A97\u53E3","dashboard.weekly_window":"\u6BCF\u5468\u914D\u989D\u7A97\u53E3","dashboard.remaining":"\u5269\u4F59","dashboard.used":"\u5DF2\u7528","dashboard.account_pool":"\u5F53\u524D\u627F\u8F7D\u8D26\u6237","dashboard.credits":"\u70B9\u6570\u4F59\u989D","dashboard.updated_at":"\u66F4\u65B0\u4E8E","composer.cost_title":"ClineBot \u4F1A\u8BDD\u6210\u672C","composer.last_request":"\u6700\u8FD1\u4E00\u6B21\u8BF7\u6C42","composer.free":"\u514D\u8D39","composer.cost_breakdown":"Token \u4E0E\u8D39\u7528\u660E\u7EC6","composer.prompt_tok":"\u63D0\u793A\u8BCD Token","composer.completion_tok":"\u8865\u5168 Token","composer.reasoning_tok":"\u601D\u8003\u63A8\u7406 Token","composer.est_cost":"\u9884\u4F30\u82B1\u8D39","composer.unpriced":"\u65E0\u4EF7\u683C","composer.cache_read":"\u7F13\u5B58\u8BFB\u53D6","composer.cache_write":"\u7F13\u5B58\u5199\u5165","composer.price_source":"\u4EF7\u683C\u6765\u6E90","composer.price_estimate":"\u4F30\u7B97","composer.price_override":"\u8986\u76D6","composer.price_public":"\u516C\u5F00\u76EE\u5F55","discovery.title":"\u53D1\u73B0\u672C\u5730\u5BC6\u94A5","discovery.import_btn":"\u4ECE {source} \u5BFC\u5165","discovery.imported":"\u5DF2\u6210\u529F\u5BFC\u5165\u8D26\u6237\u6C60","discovery.scanning":"\u6B63\u5728\u626B\u63CF\u672C\u5730 Cline \u5BC6\u94A5\u2026","settings.unsaved":"{count} \u9879\u672A\u4FDD\u5B58\u66F4\u6539","settings.saving":"\u6B63\u5728\u4FDD\u5B58\u2026","settings.saved":"\u8BBE\u7F6E\u5DF2\u4FDD\u5B58","settings.save_failed":"\u65E0\u6CD5\u4FDD\u5B58\u8BBE\u7F6E","settings.save":"\u4FDD\u5B58","settings.discard":"\u653E\u5F03","settings.save_invalid_base_url":"Base URL \u5FC5\u987B\u4F7F\u7528 https:// \u6216 localhost\u3002","settings.save_invalid_number":"\u8D85\u65F6\u548C\u8D1F\u8F7D\u9650\u5236\u5FC5\u987B\u662F\u6B63\u6570\u3002","settings.models_group":"\u6A21\u578B\u4E0E\u4E0A\u4E0B\u6587","settings.models_group_hint":"\u5957\u9910\u8FC7\u6EE4\u3001\u53EF\u89C1\u6A21\u578B\u3001\u4E0A\u4E0B\u6587\u7A97\u53E3\u3001\u8F93\u51FA\u4E0A\u9650\u548C\u63A8\u7406\u9ED8\u8BA4\u503C\u3002","settings.integrations":"\u96C6\u6210\u4E0E\u663E\u793A","settings.integrations_desc":"\u804A\u5929\u8BF7\u6C42\u4E4B\u5916\u590D\u7528\u6B64\u670D\u52A1\u5546\u7684\u754C\u9762\u5165\u53E3\u3002","settings.web_search":"\u4F7F\u7528 ClineBot \u63D0\u4F9B\u7F51\u9875\u641C\u7D22","settings.web_search_hint":"\u4E3A DSH web_search \u5DE5\u5177\u4F7F\u7528 ClinePass \u7AEF\u70B9\u3002","settings.sidebar_quota":"\u5728\u4FA7\u8FB9\u680F\u663E\u793A\u914D\u989D","settings.sidebar_quota_hint":"\u5728\u8BBE\u7F6E\u4E0A\u65B9\u663E\u793A\u7D27\u51D1\u7684\u5957\u9910\u4E0E\u6EDA\u52A8\u7A97\u53E3\u914D\u989D\u3002","settings.composer_cost":"\u5728\u8F93\u5165\u6846\u663E\u793A\u4F1A\u8BDD\u6210\u672C","settings.composer_cost_hint":"\u5728\u8F93\u5165\u6846\u65C1\u663E\u793A\u9884\u4F30 token \u6210\u672C\u3002","settings.advanced":"\u9AD8\u7EA7","settings.advanced_desc":"\u8FDE\u63A5\u3001\u8D85\u65F6\u548C\u5B58\u50A8\u8DEF\u5F84\u3002","settings.base_url":"API \u57FA\u7840\u5730\u5740","settings.base_url_hint":"ClinePass API \u6839\u5730\u5740\u3002\u8FDC\u7A0B\u7AEF\u70B9\u5FC5\u987B\u4F7F\u7528 https://\u3002","settings.timeout":"\u8FDE\u63A5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09","settings.timeout_hint":"\u7B49\u5F85\u4E0A\u6E38\u54CD\u5E94\u5934\u7684\u6700\u5927\u65F6\u95F4\u3002","settings.smoke_timeout":"\u70DF\u96FE\u6D4B\u8BD5\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09","settings.smoke_timeout_hint":"\u7B49\u5F85\u8BCA\u65AD\u804A\u5929\u8865\u5168\u7684\u6700\u5927\u65F6\u95F4\u3002","settings.stream_idle":"\u6D41\u7A7A\u95F2\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09","settings.stream_idle_hint":"SSE \u6570\u636E\u5757\u4E4B\u95F4\u7684\u6700\u5927\u95F4\u9694\uFF0C\u8D85\u8FC7\u540E\u89C6\u4E3A\u65AD\u6D41\u3002","settings.max_payload":"\u591A\u6A21\u6001\u8D1F\u8F7D\u4E0A\u9650\uFF08MB\uFF09","settings.max_payload_hint":"\u8BF7\u6C42\u8D85\u8FC7\u9884\u7B97\u65F6\u4F1A\u6DD8\u6C70\u6700\u65E7\u7684\u56FE\u7247\u3002","settings.stats_retention":"\u7EDF\u8BA1\u4FDD\u7559\u5929\u6570","settings.stats_retention_hint":"\u8D85\u8FC7\u8BE5\u5929\u6570\u7684\u65E5/\u5C0F\u65F6/\u4F1A\u8BDD\u9065\u6D4B\u4F1A\u88AB\u6E05\u7406\u30020 \u8868\u793A\u6C38\u4E45\u4FDD\u7559\u3002","settings.daily_budget":"\u6BCF\u65E5\u9884\u7B97\uFF08USD\uFF09","settings.daily_budget_hint":"\u6309\u5F53\u524D UTC \u65E5\u4F30\u7B97\u7684\u82B1\u8D39\u4E0A\u9650\u30020 \u8868\u793A\u5173\u95ED\u3002","settings.monthly_budget":"\u6BCF\u6708\u9884\u7B97\uFF08USD\uFF09","settings.monthly_budget_hint":"\u6309\u5F53\u524D UTC \u6708\u4F30\u7B97\u7684\u82B1\u8D39\u4E0A\u9650\u30020 \u8868\u793A\u5173\u95ED\u3002","settings.stats_path":"\u7EDF\u8BA1\u6587\u4EF6\u8DEF\u5F84","settings.stats_path_hint":"\u6301\u4E45\u5316 JSON \u9065\u6D4B\u8DEF\u5F84\uFF08\u652F\u6301 ~/\uFF09\u3002","settings.models_cache_path":"\u6A21\u578B\u7F13\u5B58\u8DEF\u5F84","settings.models_cache_path_hint":"\u79BB\u7EBF\u6A21\u578B\u76EE\u5F55\u7F13\u5B58\uFF08\u652F\u6301 ~/\uFF09\u3002","providerCard.title":"ClineBot\uFF08ClinePass\uFF09","providerCard.hint":"\u7C98\u8D34 ClinePass API \u5BC6\u94A5\uFF0C\u6216\u524D\u5F80\u201C\u8BBE\u7F6E \u2192 ClineBot\u201D\u7BA1\u7406\u5B8C\u6574\u8D26\u6237\u6C60\u3002","providerCard.keyPresent":"\u5BC6\u94A5\u5DF2\u914D\u7F6E","providerCard.keyMissing":"\u7F3A\u5C11\u5BC6\u94A5","providerCard.routeActive":"\u8DEF\u7531\u5DF2\u542F\u7528","providerCard.placeholder":"\u7C98\u8D34 API \u5BC6\u94A5","providerCard.replace":"\u66FF\u6362 API \u5BC6\u94A5","providerCard.save":"\u4FDD\u5B58\u5BC6\u94A5","providerCard.saving":"\u6B63\u5728\u4FDD\u5B58\u2026","providerCard.saved":"\u5BC6\u94A5\u5DF2\u4FDD\u5B58\u5E76\u9A8C\u8BC1","providerCard.failed":"\u65E0\u6CD5\u4FDD\u5B58\u5BC6\u94A5","providerCard.show":"\u663E\u793A","providerCard.hide":"\u9690\u85CF","providerCard.openSettings":"\u524D\u5F80\u201C\u8BBE\u7F6E \u2192 ClineBot\u201D\u7BA1\u7406\u8D26\u6237\u3001\u6A21\u578B\u548C\u914D\u989D\u3002","dashboard.tab_quota":"\u914D\u989D","dashboard.tab_analytics":"\u5206\u6790","dashboard.auto_account":"\u81EA\u52A8\uFF08\u6700\u5C11\u4F7F\u7528\uFF09","analytics.title":"\u7528\u91CF\u5206\u6790","analytics.requests":"\u8BF7\u6C42\u6570","analytics.tokens":"Token","analytics.cost":"\u9884\u4F30\u6210\u672C","analytics.success":"\u6210\u529F\u7387","analytics.ttft":"\u5E73\u5747\u9996\u5B57\u5EF6\u8FDF","analytics.loading":"\u6B63\u5728\u52A0\u8F7D\u5206\u6790\u2026","analytics.no_data":"\u6682\u65E0\u5206\u6790\u6570\u636E\uFF0C\u8BF7\u5148\u53D1\u9001\u51E0\u6761\u6D88\u606F\u3002","analytics.unpriced":"{count} \u4E2A\u8BF7\u6C42\u6CA1\u6709\u4EF7\u683C\u4F30\u7B97\uFF0C\u672A\u8BA1\u5165\u6210\u672C\u5408\u8BA1\u3002","analytics.group_model":"\u6A21\u578B","analytics.group_day":"\u65E5\u671F","analytics.group_account":"\u8D26\u6237","analytics.group_session":"\u4F1A\u8BDD","analytics.cache_saved":"\u7F13\u5B58\u8282\u7701","analytics.heatmap":"\u8BF7\u6C42\u70ED\u529B\u56FE","analytics.heatmap_hint":"\u6700\u8FD1 7 \u5929\u6BCF\u5C0F\u65F6\u7684\u8BF7\u6C42\u6570\u3002","analytics.heatmap_less":"\u5C11","analytics.heatmap_more":"\u591A","analytics.heatmap_empty":"\u6682\u65E0\u6309\u5C0F\u65F6\u6570\u636E\u3002\u53D1\u9001\u4E00\u6761\u6D88\u606F\u540E\u5F00\u59CB\u8BB0\u5F55\u3002","analytics.input":"\u8F93\u5165","analytics.output":"\u8F93\u51FA","analytics.reasoning":"\u63A8\u7406","analytics.cache_read":"\u7F13\u5B58\u8BFB\u53D6","analytics.cache_write":"\u7F13\u5B58\u5199\u5165","analytics.avg_ttft":"\u5E73\u5747\u9996\u5B57\u5EF6\u8FDF","analytics.avg_latency":"\u5E73\u5747\u5EF6\u8FDF","analytics.errors":"\u9519\u8BEF","analytics.p50_latency":"P50 \u5EF6\u8FDF","analytics.p95_latency":"P95 \u5EF6\u8FDF","analytics.heatmap_selected":"{day} {hour}:00 \u2014 {count} \u6B21\u8BF7\u6C42\u3002\u518D\u6B21\u70B9\u51FB\u53EF\u6E05\u9664\u3002","analytics.no_key":"\u5C1A\u672A\u914D\u7F6E ClinePass \u5BC6\u94A5\u3002","analytics.no_key_hint":"\u6253\u5F00\u201C\u8BBE\u7F6E \u2192 ClineBot\u201D\u7C98\u8D34\u5BC6\u94A5\uFF0C\u6216\u5BFC\u5165\u5DF2\u53D1\u73B0\u7684\u672C\u5730\u5BC6\u94A5\u3002","analytics.health_error":"ClinePass \u7AEF\u70B9\u4E0D\u53EF\u8FBE\u3002","analytics.not_registered":"ClineBot \u63D0\u4F9B\u5546\u672A\u5728 DSH \u4E2D\u6CE8\u518C\u3002","analytics.not_registered_hint":"\u6253\u5F00\u201C\u8BBE\u7F6E \u2192 ClineBot\u201D\u68C0\u67E5\u8BCA\u65AD\u3002","analytics.stale":"\u6B63\u5728\u663E\u793A\u4E0A\u6B21\u6210\u529F\u83B7\u53D6\u7684\u6570\u636E\u3002","analytics.range":"\u8303\u56F4","analytics.range_7":"7 \u5929","analytics.range_30":"30 \u5929","analytics.range_90":"90 \u5929","analytics.heatmap_all_models":"\u6240\u6709\u6A21\u578B","analytics.heatmap_peak":"\u5CF0\u503C","analytics.heatmap_by_hour":"\u6309\u5C0F\u65F6","analytics.heatmap_model":"\u70ED\u529B\u56FE\u6A21\u578B","quota.no_windows":"\u672A\u8FD4\u56DE\u914D\u989D\u7A97\u53E3\u3002","prices.title":"\u4EF7\u683C\u8986\u76D6","prices.desc":"\u6BCF 100 \u4E07 token \u7684\u7F8E\u5143\u4EF7\u683C\u3002\u7559\u7A7A\u4F7F\u7528\u672C\u5730\u4F30\u7B97\u3002","prices.show":"\u663E\u793A\u4EF7\u683C","prices.hide":"\u9690\u85CF\u4EF7\u683C","prices.model":"\u6A21\u578B","prices.input":"\u8F93\u5165","prices.output":"\u8F93\u51FA","prices.cache_read":"\u7F13\u5B58\u8BFB\u53D6","prices.cache_write":"\u7F13\u5B58\u5199\u5165","prices.auto":"\u81EA\u52A8","prices.empty":"\u6682\u65E0\u6A21\u578B\u3002","prices.hint":"\u8FD9\u4E9B\u4EF7\u683C\u53EA\u5F71\u54CD\u8F93\u5165\u6846\u6210\u672C\u548C\u672C\u5730\u5206\u6790\uFF0C\u4E0D\u662F\u8D26\u5355\u3002","prices.public_hint":"\u540C\u6B65\u4F7F\u7528\u516C\u5F00\u7684 models.dev \u76EE\u5F55\u4F5C\u4E3A\u4F30\u7B97\uFF1B\u624B\u52A8\u8986\u76D6\u59CB\u7EC8\u4F18\u5148\u3002","prices.sync":"\u540C\u6B65\u516C\u5F00\u4EF7\u683C","prices.syncing":"\u6B63\u5728\u540C\u6B65\u2026","prices.synced":"\u516C\u5F00\u76EE\u5F55\u5DF2\u540C\u6B65\uFF1A{count} \u4E2A\u6A21\u578B","prices.sync_failed":"\u4EF7\u683C\u540C\u6B65\u5931\u8D25\uFF1A{error}"};
|
|
19
19
|
|
|
20
20
|
// --- src/client/primitives.js ---
|
|
21
21
|
let primitives=null;try{primitives=require("@deepseek-ai/dsh-client-ui-primitives")||null}catch(_){primitives=null}const Switch=primitives&&primitives.Switch||null;const Modal=primitives&&primitives.Modal||null;function BooleanControl(props){const{checked,disabled,label,onChange}=props;if(Switch){return React.createElement(Switch,{checked:!!checked,disabled:!!disabled,label,onChange:next=>{if(typeof onChange==="function")onChange(next)}})}return React.createElement("input",{type:"checkbox",style:{cursor:disabled?"not-allowed":"pointer",width:"16px",height:"16px",flex:"none"},checked:!!checked,disabled:!!disabled,"aria-label":label,onChange:e=>{if(typeof onChange==="function")onChange(e.target.checked)}})}
|
|
@@ -413,8 +413,14 @@ function UpdateBanner({updateState,handleTriggerUpdate,handleCheckUpdate,t}){con
|
|
|
413
413
|
// --- src/client/components/key-section.js ---
|
|
414
414
|
function KeySection({keyPresent,apiKeyInput,setApiKeyInput,showKey,setShowKey,busy,draft,handleSaveKey,handleFastLogin,verifyStatus,t}){return React.createElement("section",{className:"cb-section-card"},React.createElement("div",{className:"cb-section-title"},t("key.title")),React.createElement("div",{className:"cb-section-desc"},t("key.desc")),React.createElement("div",{className:"cb-input-group"},React.createElement("input",{className:"cb-input",type:showKey?"text":"password",placeholder:keyPresent?t("key.placeholder_has"):t("key.placeholder_empty"),value:apiKeyInput,onChange:event=>setApiKeyInput(event.target.value)}),React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>setShowKey(value=>!value)},showKey?t("key.hide"):t("key.show")),React.createElement("button",{type:"button",className:"cb-btn cb-btn-primary",disabled:!!busy||!String(apiKeyInput||"").trim(),onClick:handleSaveKey},busy==="save-key"?t("key.saving"):t("key.save")),React.createElement("button",{type:"button",className:"cb-btn",disabled:!!busy,onClick:handleFastLogin},t("key.login_fast"))),verifyStatus&&verifyStatus.state==="verifying"?React.createElement("div",{className:"cb-hint"},t("key.verifying")):null,verifyStatus&&verifyStatus.state==="valid"?React.createElement("div",{className:"cb-alert-ok cb-key-status"},t("key.verified",{email:verifyStatus.email,plan:verifyStatus.plan})):null,verifyStatus&&verifyStatus.state==="invalid"?React.createElement("div",{className:"cb-alert-bad cb-key-status"},t("key.invalid",{error:verifyStatus.error})):null,React.createElement("div",{className:"cb-key-meta"},React.createElement("span",null,t("key.env_label")),React.createElement("code",{className:"cb-mono"},draft.apiKeyEnv||"CLINEBOT_API_KEY"),React.createElement("span",{className:"cb-muted"},"\xB7"),React.createElement("a",{href:"https://app.cline.bot/settings/api-keys",target:"_blank",rel:"noreferrer",className:"cb-link"},t("key.get_key"))))}
|
|
415
415
|
|
|
416
|
+
// --- src/client/components/account-history.js ---
|
|
417
|
+
function AccountHistory({open,events=[],loading,accounts=[],t}){if(!open)return null;return React.createElement("div",{className:"cb-account-history"},React.createElement("div",{className:"cb-account-routes-head"},React.createElement(CbIcon,{name:"route",size:14}),` ${t("accounts.history_title")}`),React.createElement("div",{className:"cb-section-desc"},t("accounts.history_hint")),loading?React.createElement("div",{className:"cb-hint"},t("accounts.history_loading")):events.length===0?React.createElement("div",{className:"cb-hint"},t("accounts.history_empty")):React.createElement("div",{className:"cb-model-list"},events.map((event,index)=>{const account=accounts.find(entry=>(entry.apiKeyEnv||entry.id)===event.accountId);const until=event.until?` \xB7 ${t("accounts.history_until",{time:new Date(event.until).toLocaleTimeString()})}`:"";return React.createElement("div",{className:"cb-model-row",key:`${event.at}-${index}`},React.createElement("span",{className:"cb-mono"},new Date(event.at).toLocaleString()),React.createElement("span",{className:"cb-muted"},`${account?.label||event.accountId||"\u2014"} \xB7 ${t(`accounts.event_${event.type}`)} \xB7 ${event.reason||"\u2014"}${until}`))})))}
|
|
418
|
+
|
|
419
|
+
// --- src/client/components/account-routes.js ---
|
|
420
|
+
function AccountRoutes({accounts=[],modelRoutes={},onUpdateModelRoutes,busy,t}){const[routeModel,setRouteModel]=React.useState("");const[routeAccount,setRouteAccount]=React.useState("");return React.createElement("div",{className:"cb-account-routes"},React.createElement("div",{className:"cb-account-routes-head"},React.createElement(CbIcon,{name:"route",size:14}),` ${t("accounts.routes_title")}`),React.createElement("div",{className:"cb-section-desc"},t("accounts.routes_desc")),React.createElement("div",{className:"cb-account-route-add"},React.createElement("input",{type:"text",className:"cb-input",placeholder:t("accounts.route_model_placeholder"),value:routeModel,onChange:event=>setRouteModel(event.target.value)}),React.createElement("select",{className:"cb-select",value:routeAccount,onChange:event=>setRouteAccount(event.target.value)},React.createElement("option",{value:""},t("accounts.route_account_placeholder")),accounts.map(account=>{const key=account.apiKeyEnv||account.id;return React.createElement("option",{key,value:key},`${account.label||key} (${key})`)})),React.createElement("button",{type:"button",className:"cb-btn",disabled:!routeModel.trim()||!routeAccount.trim()||!!busy,onClick:()=>{if(typeof onUpdateModelRoutes!=="function")return;onUpdateModelRoutes({...modelRoutes||{},[routeModel.trim()]:routeAccount.trim()});setRouteModel("");setRouteAccount("")}},t("accounts.route_btn_add"))),Object.keys(modelRoutes||{}).length>0?React.createElement("div",{className:"cb-account-route-list"},Object.entries(modelRoutes||{}).map(([modelId,accountKey])=>React.createElement("div",{className:"cb-account-route-row",key:modelId},React.createElement("span",{className:"cb-mono"},modelId),React.createElement(CbIcon,{name:"chevron",size:12}),React.createElement("strong",null,accountKey),React.createElement("button",{type:"button",className:"cb-icon-btn","aria-label":t("accounts.route_remove"),disabled:!!busy,onClick:()=>{if(typeof onUpdateModelRoutes!=="function")return;const next={...modelRoutes||{}};delete next[modelId];onUpdateModelRoutes(next)}},React.createElement(CbIcon,{name:"close",size:13}))))):null)}
|
|
421
|
+
|
|
416
422
|
// --- src/client/components/accounts-section.js ---
|
|
417
|
-
function AccountsSection({status,busy,handlePinAccount,handleAddAccount,handleDeleteAccount,handleRenameAccount,handleClearKey,accountMode,handleUpdateAccountMode,modelRoutes={},onUpdateModelRoutes,t}){const[showAdd,setShowAdd]=React.useState(false);const[newLabel,setNewLabel]=React.useState("");const[newEnv,setNewEnv]=React.useState("");const[newKey,setNewKey]=React.useState("");const[showKey,setShowKey]=React.useState(false);const[discoveredKeys,setDiscoveredKeys]=React.useState([]);const[usageByAccount,setUsageByAccount]=React.useState({});const[menuFor,setMenuFor]=React.useState("");const[renameFor,setRenameFor]=React.useState("");const[renameValue,setRenameValue]=React.useState("");const[confirmAction,setConfirmAction]=React.useState(null);const[routeModel,setRouteModel]=React.useState("");const[routeAccount,setRouteAccount]=React.useState("");React.useEffect(()=>{let active=true;fetchWithTimeout(`${ROUTE_PREFIX}/keys/discovered`,{cache:"no-store"}).then(response=>response.json()).then(data=>{if(active&&data?.ok&&Array.isArray(data.discovered))setDiscoveredKeys(data.discovered)}).catch(()=>{});return()=>{active=false}},[]);const accountSignature=(status?.accounts||[]).map(account=>account.apiKeyEnv||account.id).join("|");React.useEffect(()=>{let active=true;fetchWithTimeout(`${ROUTE_PREFIX}/accounts/usage`,{cache:"no-store"}).then(response=>response.json()).then(data=>{if(!active||!data?.ok||!Array.isArray(data.accounts))return;const next={};for(const entry of data.accounts)next[entry.apiKeyEnv]=entry;setUsageByAccount(next)}).catch(()=>{});return()=>{active=false}},[accountSignature,status?.activeAccount,busy]);function nextEnvName(){let max=1;for(const account of status.accounts||[]){const match=String(account.apiKeyEnv||"").match(/^CLINEBOT_API_KEY_(\d+)$/);if(match)max=Math.max(max,Number(match[1])+1)}return`CLINEBOT_API_KEY_${max}`}function openAdd(){setNewEnv(nextEnvName());setShowAdd(true)}async function submitAdd(event){if(event?.preventDefault)event.preventDefault();if(!newKey.trim()||typeof handleAddAccount!=="function")return;await handleAddAccount({label:newLabel.trim()||newEnv.trim(),apiKeyEnv:newEnv.trim(),apiKey:newKey.trim()});setNewLabel("");setNewEnv("");setNewKey("");setShowAdd(false)}async function importDiscovered(item){if(typeof handleAddAccount!=="function")return;await handleAddAccount({label:item.source||nextEnvName(),apiKeyEnv:nextEnvName(),id:item.id});setDiscoveredKeys(previous=>previous.filter(entry=>entry.id!==item.id))}function openRename(account){setMenuFor("");setRenameFor(account.apiKeyEnv);setRenameValue(account.label||"")}async function submitRename(account){if(typeof handleRenameAccount==="function"){await handleRenameAccount(account.apiKeyEnv,renameValue.trim()||account.apiKeyEnv)}setRenameFor("");setRenameValue("")}async function runConfirm(){if(!confirmAction)return;if(confirmAction.type==="delete"&&typeof handleDeleteAccount==="function"){await handleDeleteAccount(confirmAction.env,true)}if(confirmAction.type==="clear"&&typeof handleClearKey==="function"){await handleClearKey(confirmAction.env)}setConfirmAction(null)}function dotClass(account,live){if(!account.present)return"cb-status-dot cb-status-dot-warn";if(live?.error)return"cb-status-dot cb-status-dot-bad";const percent=live?.usage?.windows?.fiveHour?.percentUsed;if(typeof percent==="number"&&percent>=95)return"cb-status-dot cb-status-dot-bad";if(account.isPinned||status.activeAccount===account.apiKeyEnv)return"cb-status-dot cb-status-dot-ok";return"cb-status-dot cb-status-dot-idle"}const accounts=status.accounts||[];return React.createElement("section",{className:"cb-section-card cb-accounts"},React.createElement("div",{className:"cb-section-head"},React.createElement("div",{className:"cb-section-title"},t("accounts.title")),!showAdd?React.createElement("button",{type:"button",className:"cb-btn cb-btn-primary",disabled:!!busy,onClick:openAdd},React.createElement(CbIcon,{name:"plus",size:13}),` ${t("accounts.add_btn")}`):null),React.createElement("div",{className:"cb-section-desc"},t("accounts.desc")),discoveredKeys.length>0?React.createElement("div",{className:"cb-discovered"},React.createElement("div",{className:"cb-discovered-head"},React.createElement(CbIcon,{name:"key",size:14}),` ${t("discovery.title")}`),discoveredKeys.map(item=>React.createElement("div",{className:"cb-discovered-row",key:item.id},React.createElement("span",{className:"cb-mono"},item.maskedKey||item.source),React.createElement("span",{className:"cb-muted"},item.source||""),React.createElement("button",{type:"button",className:"cb-btn",disabled:!!busy,onClick:()=>importDiscovered(item)},React.createElement(CbIcon,{name:"download",size:12}),` ${t("discovery.import_btn",{source:item.source||""})}`)))):null,React.createElement("div",{className:"cb-account-mode"},React.createElement("span",{className:"cb-muted"},t("accounts.mode_label")),React.createElement("div",{className:"cb-seg"},React.createElement("button",{type:"button",className:(accountMode||"least_used")==="least_used"?"cb-seg-item cb-seg-item-active":"cb-seg-item",disabled:!!busy,onClick:()=>handleUpdateAccountMode&&handleUpdateAccountMode("least_used")},t("accounts.mode_least_used")),React.createElement("button",{type:"button",className:accountMode==="round_robin"?"cb-seg-item cb-seg-item-active":"cb-seg-item",disabled:!!busy,onClick:()=>handleUpdateAccountMode&&handleUpdateAccountMode("round_robin")},t("accounts.mode_round_robin")))),showAdd?React.createElement("form",{className:"cb-account-add",onSubmit:submitAdd},React.createElement("div",{className:"cb-account-add-row"},React.createElement("input",{type:"text",className:"cb-input",placeholder:t("accounts.label_placeholder"),value:newLabel,onChange:event=>setNewLabel(event.target.value)}),React.createElement("input",{type:"text",className:"cb-input",placeholder:t("accounts.env_placeholder"),value:newEnv,onChange:event=>setNewEnv(event.target.value)})),React.createElement("div",{className:"cb-account-add-row"},React.createElement("input",{type:showKey?"text":"password",className:"cb-input",placeholder:t("key.placeholder_empty"),value:newKey,onChange:event=>setNewKey(event.target.value)}),React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>setShowKey(value=>!value)},showKey?t("key.hide"):t("key.show"))),React.createElement("div",{className:"cb-account-add-actions"},React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>setShowAdd(false)},t("accounts.cancel_btn")),React.createElement("button",{type:"submit",className:"cb-btn cb-btn-primary",disabled:!newKey.trim()||!!busy},busy==="add-account"?t("key.saving"):t("accounts.save_btn")))):null,accounts.length===0?React.createElement("div",{className:"cb-empty"},t("accounts.no_accounts")):React.createElement("div",{className:"cb-account-list"},accounts.map(account=>{const envName=account.apiKeyEnv||account.id||"";const live=usageByAccount[envName];const isActive=status.activeAccount===envName||!status.activeAccount&&account.id==="default";const isPrimary=account.id==="default"||envName===status.config?.apiKeyEnv;const five=live?.usage?.windows?.fiveHour;const percent=typeof five?.percentUsed==="number"?five.percentUsed:typeof account.percentUsed==="number"?account.percentUsed:null;const plan=live?.usage?.plan||"";const email=live?.usage?.user?.email||"";return React.createElement("div",{className:"cb-account-item",key:envName||account.id},React.createElement("div",{className:"cb-account-row"},React.createElement("span",{className:dotClass(account,live),"aria-hidden":"true"}),React.createElement("div",{className:"cb-account-main"},React.createElement("div",{className:"cb-account-name"},account.label||envName,React.createElement("span",{className:"cb-account-env cb-mono"},envName)),React.createElement("div",{className:"cb-account-meta"},[plan,email].filter(Boolean).join(" \xB7 ")||t("accounts.source",{source:account.source||"\u2014"}))),React.createElement("div",{className:"cb-account-badges"},isActive?React.createElement("span",{className:"cb-badge cb-badge-ok"},t("accounts.active_badge")):null,account.isPinned?React.createElement("span",{className:"cb-badge"},t("accounts.pinned_badge")):null,account.present?React.createElement("span",{className:"cb-badge"},t("accounts.configured")):React.createElement("span",{className:"cb-badge cb-badge-warn"},t("accounts.missing_key"))),React.createElement("div",{className:"cb-account-quota"},percent===null?null:React.createElement(React.Fragment,null,React.createElement("span",{className:"cb-muted"},t("accounts.quota_5h",{pct:Math.round(percent)})),React.createElement("span",{className:"cb-meter"},React.createElement("span",{className:percent>=95?"cb-meter-fill cb-meter-fill-warn":"cb-meter-fill",style:{width:`${Math.max(0,Math.min(100,percent))}%`}})))),React.createElement("button",{type:"button",className:"cb-icon-btn","aria-label":t("accounts.actions"),"aria-expanded":menuFor===envName,disabled:!!busy,onClick:()=>setMenuFor(current=>current===envName?"":envName)},React.createElement(CbIcon,{name:"kebab",size:16}))),menuFor===envName?React.createElement("div",{className:"cb-account-menu"},isActive?React.createElement("button",{type:"button",className:"cb-menu-item",onClick:()=>{setMenuFor("");handlePinAccount("")}},t("accounts.unpin")):account.present?React.createElement("button",{type:"button",className:"cb-menu-item",onClick:()=>{setMenuFor("");handlePinAccount(envName)}},t("accounts.pin_btn")):null,!isPrimary?React.createElement("button",{type:"button",className:"cb-menu-item",onClick:()=>openRename(account)},t("accounts.rename")):null,account.present?React.createElement("button",{type:"button",className:"cb-menu-item",onClick:()=>{setMenuFor("");setConfirmAction({type:"clear",env:envName})}},t("accounts.clear_key")):null,!isPrimary?React.createElement("button",{type:"button",className:"cb-menu-item cb-menu-item-danger",onClick:()=>{setMenuFor("");setConfirmAction({type:"delete",env:envName})}},t("accounts.delete_btn")):null):null,renameFor===envName?React.createElement("div",{className:"cb-account-inline"},React.createElement("input",{type:"text",className:"cb-input",value:renameValue,onChange:event=>setRenameValue(event.target.value),placeholder:t("accounts.rename_placeholder")}),React.createElement("button",{type:"button",className:"cb-btn cb-btn-primary",disabled:!!busy,onClick:()=>submitRename(account)},t("accounts.rename_save")),React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>setRenameFor("")},t("accounts.cancel"))):null,confirmAction&&confirmAction.env===envName?React.createElement("div",{className:"cb-account-inline cb-account-danger"},React.createElement("span",{className:"cb-account-danger-text"},confirmAction.type==="delete"?t("accounts.remove_confirm",{name:account.label||envName}):t("accounts.clear_confirm",{name:account.label||envName})),React.createElement("button",{type:"button",className:"cb-btn cb-btn-danger",disabled:!!busy,onClick:runConfirm},t("accounts.confirm")),React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>setConfirmAction(null)},t("accounts.cancel"))):null)})),React.createElement("div",{className:"cb-account-routes"},React.createElement("div",{className:"cb-account-routes-head"},React.createElement(CbIcon,{name:"route",size:14}),` ${t("accounts.routes_title")}`),React.createElement("div",{className:"cb-section-desc"},t("accounts.routes_desc")),React.createElement("div",{className:"cb-account-route-add"},React.createElement("input",{type:"text",className:"cb-input",placeholder:t("accounts.route_model_placeholder"),value:routeModel,onChange:event=>setRouteModel(event.target.value)}),React.createElement("select",{className:"cb-select",value:routeAccount,onChange:event=>setRouteAccount(event.target.value)},React.createElement("option",{value:""},t("accounts.route_account_placeholder")),accounts.map(account=>{const key=account.apiKeyEnv||account.id;return React.createElement("option",{key,value:key},`${account.label||key} (${key})`)})),React.createElement("button",{type:"button",className:"cb-btn",disabled:!routeModel.trim()||!routeAccount.trim()||!!busy,onClick:()=>{if(typeof onUpdateModelRoutes!=="function")return;onUpdateModelRoutes({...modelRoutes||{},[routeModel.trim()]:routeAccount.trim()});setRouteModel("");setRouteAccount("")}},t("accounts.route_btn_add"))),Object.keys(modelRoutes||{}).length>0?React.createElement("div",{className:"cb-account-route-list"},Object.entries(modelRoutes||{}).map(([modelId,accountKey])=>React.createElement("div",{className:"cb-account-route-row",key:modelId},React.createElement("span",{className:"cb-mono"},modelId),React.createElement(CbIcon,{name:"chevron",size:12}),React.createElement("strong",null,accountKey),React.createElement("button",{type:"button",className:"cb-icon-btn","aria-label":t("accounts.route_remove"),disabled:!!busy,onClick:()=>{if(typeof onUpdateModelRoutes!=="function")return;const next={...modelRoutes||{}};delete next[modelId];onUpdateModelRoutes(next)}},React.createElement(CbIcon,{name:"close",size:13}))))):null))}
|
|
423
|
+
function AccountsSection({status,busy,handlePinAccount,handleAddAccount,handleDeleteAccount,handleRenameAccount,handleClearKey,accountMode,handleUpdateAccountMode,modelRoutes={},onUpdateModelRoutes,t}){const[showAdd,setShowAdd]=React.useState(false);const[newLabel,setNewLabel]=React.useState("");const[newEnv,setNewEnv]=React.useState("");const[newKey,setNewKey]=React.useState("");const[showKey,setShowKey]=React.useState(false);const[discoveredKeys,setDiscoveredKeys]=React.useState([]);const[usageByAccount,setUsageByAccount]=React.useState({});const[menuFor,setMenuFor]=React.useState("");const[renameFor,setRenameFor]=React.useState("");const[renameValue,setRenameValue]=React.useState("");const[confirmAction,setConfirmAction]=React.useState(null);const[historyOpen,setHistoryOpen]=React.useState(false);const[history,setHistory]=React.useState([]);const[historyLoading,setHistoryLoading]=React.useState(false);React.useEffect(()=>{let active=true;fetchWithTimeout(`${ROUTE_PREFIX}/keys/discovered`,{cache:"no-store"}).then(response=>response.json()).then(data=>{if(active&&data?.ok&&Array.isArray(data.discovered))setDiscoveredKeys(data.discovered)}).catch(()=>{});return()=>{active=false}},[]);const accountSignature=(status?.accounts||[]).map(account=>account.apiKeyEnv||account.id).join("|");React.useEffect(()=>{let active=true;fetchWithTimeout(`${ROUTE_PREFIX}/accounts/usage`,{cache:"no-store"}).then(response=>response.json()).then(data=>{if(!active||!data?.ok||!Array.isArray(data.accounts))return;const next={};for(const entry of data.accounts)next[entry.apiKeyEnv]=entry;setUsageByAccount(next)}).catch(()=>{});return()=>{active=false}},[accountSignature,status?.activeAccount,busy]);function toggleHistory(){if(historyOpen){setHistoryOpen(false);return}setHistoryOpen(true);setHistoryLoading(true);fetchWithTimeout(`${ROUTE_PREFIX}/accounts/history?limit=50`,{cache:"no-store"}).then(response=>response.json()).then(data=>{if(data?.ok&&Array.isArray(data.events))setHistory(data.events)}).catch(()=>{}).finally(()=>setHistoryLoading(false))}function nextEnvName(){let max=1;for(const account of status.accounts||[]){const match=String(account.apiKeyEnv||"").match(/^CLINEBOT_API_KEY_(\d+)$/);if(match)max=Math.max(max,Number(match[1])+1)}return`CLINEBOT_API_KEY_${max}`}function openAdd(){setNewEnv(nextEnvName());setShowAdd(true)}async function submitAdd(event){if(event?.preventDefault)event.preventDefault();if(!newKey.trim()||typeof handleAddAccount!=="function")return;await handleAddAccount({label:newLabel.trim()||newEnv.trim(),apiKeyEnv:newEnv.trim(),apiKey:newKey.trim()});setNewLabel("");setNewEnv("");setNewKey("");setShowAdd(false)}async function importDiscovered(item){if(typeof handleAddAccount!=="function")return;await handleAddAccount({label:item.source||nextEnvName(),apiKeyEnv:nextEnvName(),id:item.id});setDiscoveredKeys(previous=>previous.filter(entry=>entry.id!==item.id))}function openRename(account){setMenuFor("");setRenameFor(account.apiKeyEnv);setRenameValue(account.label||"")}async function submitRename(account){if(typeof handleRenameAccount==="function"){await handleRenameAccount(account.apiKeyEnv,renameValue.trim()||account.apiKeyEnv)}setRenameFor("");setRenameValue("")}async function runConfirm(){if(!confirmAction)return;if(confirmAction.type==="delete"&&typeof handleDeleteAccount==="function"){await handleDeleteAccount(confirmAction.env,true)}if(confirmAction.type==="clear"&&typeof handleClearKey==="function"){await handleClearKey(confirmAction.env)}setConfirmAction(null)}function dotClass(account,live){if(!account.present)return"cb-status-dot cb-status-dot-warn";if(live?.error)return"cb-status-dot cb-status-dot-bad";const percent=live?.usage?.windows?.fiveHour?.percentUsed;if(typeof percent==="number"&&percent>=95)return"cb-status-dot cb-status-dot-bad";if(account.isPinned||status.activeAccount===account.apiKeyEnv)return"cb-status-dot cb-status-dot-ok";return"cb-status-dot cb-status-dot-idle"}const accounts=status.accounts||[];return React.createElement("section",{className:"cb-section-card cb-accounts"},React.createElement("div",{className:"cb-section-head"},React.createElement("div",{className:"cb-section-title"},t("accounts.title")),React.createElement("button",{type:"button",className:"cb-btn",onClick:toggleHistory},historyOpen?t("accounts.history_hide"):t("accounts.history_show")),!showAdd?React.createElement("button",{type:"button",className:"cb-btn cb-btn-primary",disabled:!!busy,onClick:openAdd},React.createElement(CbIcon,{name:"plus",size:13}),` ${t("accounts.add_btn")}`):null),React.createElement("div",{className:"cb-section-desc"},t("accounts.desc")),discoveredKeys.length>0?React.createElement("div",{className:"cb-discovered"},React.createElement("div",{className:"cb-discovered-head"},React.createElement(CbIcon,{name:"key",size:14}),` ${t("discovery.title")}`),discoveredKeys.map(item=>React.createElement("div",{className:"cb-discovered-row",key:item.id},React.createElement("span",{className:"cb-mono"},item.maskedKey||item.source),React.createElement("span",{className:"cb-muted"},item.source||""),React.createElement("button",{type:"button",className:"cb-btn",disabled:!!busy,onClick:()=>importDiscovered(item)},React.createElement(CbIcon,{name:"download",size:12}),` ${t("discovery.import_btn",{source:item.source||""})}`)))):null,React.createElement("div",{className:"cb-account-mode"},React.createElement("span",{className:"cb-muted"},t("accounts.mode_label")),React.createElement("div",{className:"cb-seg"},React.createElement("button",{type:"button",className:(accountMode||"least_used")==="least_used"?"cb-seg-item cb-seg-item-active":"cb-seg-item",disabled:!!busy,onClick:()=>handleUpdateAccountMode&&handleUpdateAccountMode("least_used")},t("accounts.mode_least_used")),React.createElement("button",{type:"button",className:accountMode==="round_robin"?"cb-seg-item cb-seg-item-active":"cb-seg-item",disabled:!!busy,onClick:()=>handleUpdateAccountMode&&handleUpdateAccountMode("round_robin")},t("accounts.mode_round_robin")))),showAdd?React.createElement("form",{className:"cb-account-add",onSubmit:submitAdd},React.createElement("div",{className:"cb-account-add-row"},React.createElement("input",{type:"text",className:"cb-input",placeholder:t("accounts.label_placeholder"),value:newLabel,onChange:event=>setNewLabel(event.target.value)}),React.createElement("input",{type:"text",className:"cb-input",placeholder:t("accounts.env_placeholder"),value:newEnv,onChange:event=>setNewEnv(event.target.value)})),React.createElement("div",{className:"cb-account-add-row"},React.createElement("input",{type:showKey?"text":"password",className:"cb-input",placeholder:t("key.placeholder_empty"),value:newKey,onChange:event=>setNewKey(event.target.value)}),React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>setShowKey(value=>!value)},showKey?t("key.hide"):t("key.show"))),React.createElement("div",{className:"cb-account-add-actions"},React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>setShowAdd(false)},t("accounts.cancel_btn")),React.createElement("button",{type:"submit",className:"cb-btn cb-btn-primary",disabled:!newKey.trim()||!!busy},busy==="add-account"?t("key.saving"):t("accounts.save_btn")))):null,accounts.length===0?React.createElement("div",{className:"cb-empty"},t("accounts.no_accounts")):React.createElement("div",{className:"cb-account-list"},accounts.map(account=>{const envName=account.apiKeyEnv||account.id||"";const live=usageByAccount[envName];const isActive=status.activeAccount===envName||!status.activeAccount&&account.id==="default";const isPrimary=account.id==="default"||envName===status.config?.apiKeyEnv;const five=live?.usage?.windows?.fiveHour;const percent=typeof five?.percentUsed==="number"?five.percentUsed:typeof account.percentUsed==="number"?account.percentUsed:null;const plan=live?.usage?.plan||"";const email=live?.usage?.user?.email||"";return React.createElement("div",{className:"cb-account-item",key:envName||account.id},React.createElement("div",{className:"cb-account-row"},React.createElement("span",{className:dotClass(account,live),"aria-hidden":"true"}),React.createElement("div",{className:"cb-account-main"},React.createElement("div",{className:"cb-account-name"},account.label||envName,React.createElement("span",{className:"cb-account-env cb-mono"},envName)),React.createElement("div",{className:"cb-account-meta"},[plan,email].filter(Boolean).join(" \xB7 ")||t("accounts.source",{source:account.source||"\u2014"}))),React.createElement("div",{className:"cb-account-badges"},isActive?React.createElement("span",{className:"cb-badge cb-badge-ok"},t("accounts.active_badge")):null,account.isPinned?React.createElement("span",{className:"cb-badge"},t("accounts.pinned_badge")):null,account.present?React.createElement("span",{className:"cb-badge"},t("accounts.configured")):React.createElement("span",{className:"cb-badge cb-badge-warn"},t("accounts.missing_key"))),React.createElement("div",{className:"cb-account-quota"},percent===null?null:React.createElement(React.Fragment,null,React.createElement("span",{className:"cb-muted"},t("accounts.quota_5h",{pct:Math.round(percent)})),React.createElement("span",{className:"cb-meter"},React.createElement("span",{className:percent>=95?"cb-meter-fill cb-meter-fill-warn":"cb-meter-fill",style:{width:`${Math.max(0,Math.min(100,percent))}%`}})))),React.createElement("button",{type:"button",className:"cb-icon-btn","aria-label":t("accounts.actions"),"aria-expanded":menuFor===envName,disabled:!!busy,onClick:()=>setMenuFor(current=>current===envName?"":envName)},React.createElement(CbIcon,{name:"kebab",size:16}))),menuFor===envName?React.createElement("div",{className:"cb-account-menu"},isActive?React.createElement("button",{type:"button",className:"cb-menu-item",onClick:()=>{setMenuFor("");handlePinAccount("")}},t("accounts.unpin")):account.present?React.createElement("button",{type:"button",className:"cb-menu-item",onClick:()=>{setMenuFor("");handlePinAccount(envName)}},t("accounts.pin_btn")):null,!isPrimary?React.createElement("button",{type:"button",className:"cb-menu-item",onClick:()=>openRename(account)},t("accounts.rename")):null,account.present?React.createElement("button",{type:"button",className:"cb-menu-item",onClick:()=>{setMenuFor("");setConfirmAction({type:"clear",env:envName})}},t("accounts.clear_key")):null,!isPrimary?React.createElement("button",{type:"button",className:"cb-menu-item cb-menu-item-danger",onClick:()=>{setMenuFor("");setConfirmAction({type:"delete",env:envName})}},t("accounts.delete_btn")):null):null,renameFor===envName?React.createElement("div",{className:"cb-account-inline"},React.createElement("input",{type:"text",className:"cb-input",value:renameValue,onChange:event=>setRenameValue(event.target.value),placeholder:t("accounts.rename_placeholder")}),React.createElement("button",{type:"button",className:"cb-btn cb-btn-primary",disabled:!!busy,onClick:()=>submitRename(account)},t("accounts.rename_save")),React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>setRenameFor("")},t("accounts.cancel"))):null,confirmAction&&confirmAction.env===envName?React.createElement("div",{className:"cb-account-inline cb-account-danger"},React.createElement("span",{className:"cb-account-danger-text"},confirmAction.type==="delete"?t("accounts.remove_confirm",{name:account.label||envName}):t("accounts.clear_confirm",{name:account.label||envName})),React.createElement("button",{type:"button",className:"cb-btn cb-btn-danger",disabled:!!busy,onClick:runConfirm},t("accounts.confirm")),React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>setConfirmAction(null)},t("accounts.cancel"))):null)})),React.createElement(AccountRoutes,{accounts,modelRoutes,onUpdateModelRoutes,busy,t}),React.createElement(AccountHistory,{open:historyOpen,events:history,loading:historyLoading,accounts,t}))}
|
|
418
424
|
|
|
419
425
|
// --- src/client/components/quota-section.js ---
|
|
420
426
|
function QuotaSection({keyPresent,status,usage,busy,handleRefreshQuota,showSidebarQuota,onToggleSidebarQuota,t}){const isLowQuota=Boolean(usage?.windows?.weekly&&typeof usage.windows.weekly.remainingPercent==="number"&&usage.windows.weekly.remainingPercent<=10||usage?.windows?.monthly&&typeof usage.windows.monthly.remainingPercent==="number"&&usage.windows.monthly.remainingPercent<=10||usage?.windows?.fiveHour&&typeof usage.windows.fiveHour.remainingPercent==="number"&&usage.windows.fiveHour.remainingPercent<=10);return React.createElement(React.Fragment,null,status.quotaWarning?React.createElement("div",{className:status.quotaWarning.level==="exhausted"?"cb-banner-exhausted":"cb-banner-warning"},React.createElement("span",{className:"cb-banner-icon"},React.createElement(CbIcon,{name:status.quotaWarning.level==="exhausted"?"error":"warning",size:16})),React.createElement("div",{className:"cb-banner-body"},React.createElement("div",null,status.quotaWarning.code&&t(status.quotaWarning.code,status.quotaWarning.params)||status.quotaWarning.message),status.quotaWarning.resetsAt?React.createElement("div",{className:"cb-banner-sub"},t("quota.reset_at",{time:new Date(status.quotaWarning.resetsAt).toLocaleTimeString()})):null)):isLowQuota?React.createElement("div",{className:"cb-banner-warning"},React.createElement("span",{className:"cb-banner-icon"},React.createElement(CbIcon,{name:"warning",size:16})),React.createElement("div",{className:"cb-banner-body"},t("quota.warning_low"))):null,keyPresent?React.createElement("section",{className:"cb-section-card"},React.createElement("div",{className:"cb-section-head"},React.createElement("div",{className:"cb-section-title"},t("quota.title")),React.createElement("button",{type:"button",className:"cb-btn",disabled:!!busy,onClick:handleRefreshQuota},busy==="refresh-quota"?t("quota.refreshing"):t("quota.refresh"))),React.createElement("div",{className:"cb-section-desc"},usage?.user?.email?t("quota.account",{email:usage.user.email,plan:usage.plan||"ClinePass"}):t("quota.desc")),React.createElement("div",{className:"cb-quota-grid"},usage?.windows?.fiveHour?React.createElement(ProgressBar,{label:t("quota.window_5h"),percentUsed:usage.windows.fiveHour.percentUsed||0,remainingPercent:usage.windows.fiveHour.remainingPercent||100,resetsAt:usage.windows.fiveHour.resetsAt,windowMs:5*60*60*1e3,t}):null,React.createElement(ProgressBar,{label:t("quota.window_weekly"),percentUsed:usage?.windows?.weekly?.percentUsed||0,remainingPercent:usage?.windows?.weekly?.remainingPercent||100,resetsAt:usage?.windows?.weekly?.resetsAt,windowMs:7*24*60*60*1e3,t}),usage?.windows?.monthly?React.createElement(ProgressBar,{label:t("quota.window_monthly"),percentUsed:usage.windows.monthly.percentUsed||0,remainingPercent:usage.windows.monthly.remainingPercent||100,resetsAt:usage.windows.monthly.resetsAt,windowMs:30*24*60*60*1e3,t}):null),React.createElement(SettingRow,{title:t("sidebar.show_quota"),description:t("sidebar.show_quota_hint"),control:React.createElement(BooleanControl,{checked:showSidebarQuota!==false,label:t("sidebar.show_quota"),onChange:next=>onToggleSidebarQuota&&onToggleSidebarQuota(next)})})):null)}
|
|
@@ -432,7 +438,7 @@ function ProviderCard(props){const row=props?.provider||{};const providerId=type
|
|
|
432
438
|
function ModelsSection({modelsList,disabledSet,enabledCount,keyPresent,busy,handleSyncPlanModels,handleUpdateModelContext,handleSetModelsFilter,handleToggleModel,planSynced,planSyncedAt,defaultModelWarning,customModels,modelReasoningDefaults,visibleModels,filterModelsByPlan,onConfigUpdate,onUpdateReasoningEffort,t}){const syncDateStr=planSyncedAt?new Date(planSyncedAt).toLocaleDateString():"";const[search,setSearch]=React.useState("");const[viewFilter,setViewFilter]=React.useState("all");const[editingModelId,setEditingModelId]=React.useState(null);const[customCtxVal,setCustomCtxVal]=React.useState("");const[customMaxVal,setCustomMaxVal]=React.useState("");function formatCtx(num){num=Number(num)||2e5;if(num>=1e6){const m=num/1e6;return`${m%1===0?m:m.toFixed(1)}M`}if(num>=1e3){const k=num/1e3;return`${k%1===0?k:k.toFixed(1)}K`}return String(num)}const filteredModels=modelsList.filter(m=>{if(viewFilter==="vision"){const hasVision=Boolean(m.input?.includes("image")||m.input?.includes("vision"));if(!hasVision)return false}else if(viewFilter==="coding"){if(m.category!=="coding")return false}else if(viewFilter==="recommended"){if(!m.recommended)return false}else if(viewFilter==="disabled"){if(!disabledSet.has(m.id))return false}if(search.trim()){const q=search.trim().toLowerCase();const name=String(m.name||"").toLowerCase();const id=String(m.id||"").toLowerCase();const desc=String(m.description||"").toLowerCase();if(!name.includes(q)&&!id.includes(q)&&!desc.includes(q))return false}return true});const groupFor=m=>m.isCustom?"custom":m.inPlan?"plan":"other";const orderedModels=[...filteredModels].sort((a,b)=>{const order={plan:0,other:1,custom:2};return order[groupFor(a)]-order[groupFor(b)]});let lastGroup="";return React.createElement("div",{className:"cb-section-card"},defaultModelWarning?React.createElement("div",{className:"cb-alert-bad cb-inline-alert",style:{marginBottom:"12px"}},React.createElement(CbIcon,{name:"warning",size:14}),` ${defaultModelWarning}`):null,React.createElement(VisibleModelsSub,{modelsList,visibleModels,filterModelsByPlan,onConfigUpdate,busy,t}),React.createElement("div",{className:"cb-section-title"},React.createElement("span",null,t("models.title",{enabled:enabledCount,total:modelsList.length}),planSynced?React.createElement("span",{className:"cb-badge cb-badge-ok",style:{marginLeft:"8px",fontSize:"11px"}},t("models.verified")):React.createElement("span",{className:"cb-badge cb-badge-warn",style:{marginLeft:"8px",fontSize:"11px"}},t("models.unverified")),syncDateStr?React.createElement("span",{style:{marginLeft:"8px",fontSize:"11px",color:"var(--dsw-alias-label-secondary)"}},t("models.synced_at",{date:syncDateStr})):null),React.createElement("div",{className:"cb-row"},React.createElement("button",{type:"button",className:"cb-btn",disabled:!!busy||!keyPresent,onClick:handleSyncPlanModels},busy==="sync-models"?t("models.syncing"):t("models.sync")),React.createElement("button",{type:"button",className:"cb-btn",disabled:!!busy,title:t("models.apply_original_all"),onClick:()=>typeof handleUpdateModelContext==="function"&&handleUpdateModelContext({mode:"all-original"})},t("models.apply_original_all")),React.createElement("button",{type:"button",className:"cb-btn",disabled:!!busy,title:t("models.reset_contexts_all"),onClick:()=>typeof handleUpdateModelContext==="function"&&handleUpdateModelContext({mode:"all-default"})},t("models.reset_contexts_all")),React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>handleSetModelsFilter("all")},t("models.all")),React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>handleSetModelsFilter("vision")},t("models.vision")),React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>handleSetModelsFilter("coding")},t("models.coding")),React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>handleSetModelsFilter("recommended")},t("models.recommended")))),React.createElement("div",{className:"cb-section-desc"},t("models.desc")),React.createElement("div",{style:{display:"flex",gap:"8px",alignItems:"center",flexWrap:"wrap",marginBottom:"12px"}},React.createElement("input",{type:"text",className:"cb-input",style:{flex:"1 1 200px"},placeholder:t("models.search_placeholder"),value:search,onChange:e=>setSearch(e.target.value)}),React.createElement("div",{className:"cb-row",style:{gap:"4px"}},React.createElement("button",{type:"button",className:`cb-btn ${viewFilter==="all"?"cb-btn-active":""}`,style:{padding:"4px 8px",fontSize:"11px"},onClick:()=>setViewFilter("all")},t("models.filter_all")),React.createElement("button",{type:"button",className:`cb-btn ${viewFilter==="vision"?"cb-btn-active":""}`,style:{padding:"4px 8px",fontSize:"11px"},onClick:()=>setViewFilter("vision")},t("models.filter_vision")),React.createElement("button",{type:"button",className:`cb-btn ${viewFilter==="coding"?"cb-btn-active":""}`,style:{padding:"4px 8px",fontSize:"11px"},onClick:()=>setViewFilter("coding")},t("models.filter_coding")),React.createElement("button",{type:"button",className:`cb-btn ${viewFilter==="recommended"?"cb-btn-active":""}`,style:{padding:"4px 8px",fontSize:"11px"},onClick:()=>setViewFilter("recommended")},t("models.filter_recommended")),React.createElement("button",{type:"button",className:`cb-btn ${viewFilter==="disabled"?"cb-btn-active":""}`,style:{padding:"4px 8px",fontSize:"11px"},onClick:()=>setViewFilter("disabled")},t("models.filter_disabled")))),React.createElement("table",{className:"cb-table"},React.createElement("thead",null,React.createElement("tr",null,React.createElement("th",{style:{width:"40px"}},t("models.th_active")),React.createElement("th",null,t("models.th_name")),React.createElement("th",null,t("models.th_id")),React.createElement("th",null,t("models.th_ctx")),React.createElement("th",null,t("models.th_caps")))),React.createElement("tbody",null,orderedModels.map(m=>{const groupKey=groupFor(m);const showGroup=groupKey!==lastGroup;lastGroup=groupKey;const isEnabled=!disabledSet.has(m.id);const defaultCtx=m.defaultContextLength||m.clineContextLength||2e5;const origCtx=m.originalContextLength||2e5;const defaultMax=m.defaultMaxTokens||8192;const origMax=m.originalMaxTokens||defaultMax;const origProv=m.originalProvider||"Original";const isOverridden=Boolean(m.isContextOverridden);const isOriginal=isOverridden&&(m.contextLength===origCtx&&(m.maxTokens||defaultMax)===origMax);const row=React.createElement("tr",{key:m.id},React.createElement("td",null,React.createElement("input",{type:"checkbox",checked:isEnabled,onChange:()=>handleToggleModel(m.id)})),React.createElement("td",null,React.createElement("strong",null,m.name),m.recommended?React.createElement("span",{className:"cb-badge cb-badge-ok",style:{marginLeft:"6px"}},t("models.star")):null,m.isCustom?React.createElement("span",{className:"cb-badge",style:{marginLeft:"6px",opacity:.8}},t("models.auto_new")):null),React.createElement("td",null,React.createElement("code",null,m.id)),React.createElement("td",null,editingModelId===m.id?React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:"6px",background:"var(--dsw-alias-interactive-bg-hover, color-mix(in srgb, currentColor 4%, transparent))",padding:"6px",borderRadius:"6px",border:"1px solid var(--dsw-alias-border-l2)"}},React.createElement("div",{style:{display:"flex",gap:"4px",flexWrap:"wrap"}},React.createElement("button",{type:"button",className:`cb-btn ${!isOverridden?"cb-btn-active":""}`,style:{padding:"2px 6px",fontSize:"11px"},onClick:()=>{if(typeof handleUpdateModelContext==="function"){handleUpdateModelContext({modelId:m.id,reset:true})}setEditingModelId(null)}},t("models.ctx_default",{ctx:formatCtx(defaultCtx),max:formatCtx(defaultMax)})),React.createElement("button",{type:"button",className:`cb-btn ${isOriginal?"cb-btn-active":""}`,style:{padding:"2px 6px",fontSize:"11px"},onClick:()=>{if(typeof handleUpdateModelContext==="function"){handleUpdateModelContext({modelId:m.id,contextLength:origCtx,maxTokens:origMax})}setEditingModelId(null)}},t("models.ctx_original",{ctx:formatCtx(origCtx),max:formatCtx(origMax),provider:origProv}))),React.createElement("div",{style:{display:"flex",gap:"4px",alignItems:"center",flexWrap:"wrap"}},React.createElement("input",{type:"number",className:"cb-input",style:{height:"24px",fontSize:"11px",padding:"0 6px",width:"90px"},placeholder:t("models.ctx_window_label"),title:t("models.ctx_window_label"),value:customCtxVal,onChange:e=>setCustomCtxVal(e.target.value)}),React.createElement("input",{type:"number",className:"cb-input",style:{height:"24px",fontSize:"11px",padding:"0 6px",width:"75px"},placeholder:t("models.ctx_max_tokens_label"),title:t("models.ctx_max_tokens_label"),value:customMaxVal,onChange:e=>setCustomMaxVal(e.target.value)}),React.createElement("button",{type:"button",className:"cb-btn cb-btn-primary",style:{padding:"2px 6px",fontSize:"11px"},onClick:()=>{const cVal=parseInt(customCtxVal,10);const mVal=parseInt(customMaxVal,10);if(typeof handleUpdateModelContext==="function"){const payload={modelId:m.id};if(cVal>0)payload.contextLength=cVal;if(mVal>0)payload.maxTokens=mVal;handleUpdateModelContext(payload)}setEditingModelId(null)}},t("models.ctx_save")),React.createElement("button",{type:"button",className:"cb-btn",title:t("models.ctx_apply_all_title"),style:{padding:"2px 5px",fontSize:"11px"},onClick:()=>{const cVal=parseInt(customCtxVal,10);const mVal=parseInt(customMaxVal,10);if(typeof handleUpdateModelContext==="function"){const payload={mode:"apply-all"};if(cVal>0)payload.contextLength=cVal;if(mVal>0)payload.maxTokens=mVal;handleUpdateModelContext(payload)}setEditingModelId(null)}},t("models.ctx_apply_all")),React.createElement("button",{type:"button",className:"cb-btn",style:{padding:"2px 6px",fontSize:"11px"},onClick:()=>setEditingModelId(null)},t("models.ctx_cancel")))):React.createElement("div",{style:{display:"flex",alignItems:"center",gap:"6px"}},React.createElement("span",{style:{fontWeight:"600",cursor:"pointer"},onClick:()=>{setCustomCtxVal(String(m.contextLength||defaultCtx));setCustomMaxVal(String(m.maxTokens||defaultMax));setEditingModelId(m.id)}},`${formatCtx(m.contextLength||defaultCtx)} / ${formatCtx(m.maxTokens||defaultMax)}`),isOverridden?React.createElement("span",{className:isOriginal?"cb-badge cb-badge-ok":"cb-badge cb-badge-warn",style:{fontSize:"10px",padding:"1px 6px",cursor:"pointer"},onClick:()=>{setCustomCtxVal(String(m.contextLength||defaultCtx));setCustomMaxVal(String(m.maxTokens||defaultMax));setEditingModelId(m.id)}},isOriginal?t("models.ctx_tag_original"):t("models.ctx_tag_custom")):null,React.createElement("button",{type:"button",className:"cb-btn",style:{padding:"2px 6px",fontSize:"11px",lineHeight:"1",border:"none",background:"transparent"},title:t("models.ctx_edit"),onClick:()=>{setCustomCtxVal(String(m.contextLength||defaultCtx));setCustomMaxVal(String(m.maxTokens||defaultMax));setEditingModelId(m.id)}},React.createElement(CbIcon,{name:"edit",size:13})))),React.createElement("td",null,(()=>{const hasReasoning=Boolean(Array.isArray(m.reasoningEfforts)&&m.reasoningEfforts.length>0||m.reasoningEfforts&&typeof m.reasoningEfforts==="object"&&Object.keys(m.reasoningEfforts).length>0||m.reasoning||m.category==="reasoning");const category=m.category&&m.category!=="reasoning"?m.category:hasReasoning?null:"general";const categoryLabel=category==="general"?t("models.caps_general"):category==="coding"?t("models.caps_coding"):category;const hasVision=Boolean(m.input?.includes("image")||m.input?.includes("vision"));return React.createElement(React.Fragment,null,category?React.createElement("span",{className:"cb-badge"},categoryLabel):null,hasVision?React.createElement("span",{className:"cb-badge",style:{marginLeft:category?"4px":"0"}},t("models.caps_vision")):null,hasReasoning?React.createElement(React.Fragment,null,React.createElement("span",{className:"cb-badge cb-badge-ok",style:{marginLeft:category||hasVision?"4px":"0"},title:t("models.reasoning_tooltip")},t("models.reasoning_tag")),React.createElement("select",{className:"cb-select",style:{marginLeft:"4px",fontSize:"10px",padding:"1px 3px"},value:modelReasoningDefaults&&modelReasoningDefaults[m.id]||"",disabled:!!busy,onChange:e=>{if(typeof onUpdateReasoningEffort==="function"){onUpdateReasoningEffort(m.id,e.target.value)}}},React.createElement("option",{value:""},t("models.effort_auto")),React.createElement("option",{value:"low"},t("models.effort_low")),React.createElement("option",{value:"medium"},t("models.effort_medium")),React.createElement("option",{value:"high"},t("models.effort_high")),React.createElement("option",{value:"max"},t("models.effort_max")))):null)})()));const groupCount=orderedModels.filter(entry=>groupFor(entry)===groupKey).length;return React.createElement(React.Fragment,{key:`group-wrap-${m.id}`},showGroup?React.createElement("tr",{className:"cb-model-group"},React.createElement("td",{colSpan:5},`${t(`models.group_${groupKey}`)} \xB7 ${groupCount}`)):null,row)}))),typeof CustomModelsSub==="function"?React.createElement(CustomModelsSub,{customModels:customModels||[],onConfigUpdate,busy,t}):null)}
|
|
433
439
|
|
|
434
440
|
// --- src/client/components/stats-section.js ---
|
|
435
|
-
function StatsSection({status,onResetStats,busy,t}){const[confirmReset,setConfirmReset]=React.useState(false);const s=status?.statsSummary||{};const totals=s.totals||{};const sess=status?.sessionStats||{};const requests=typeof totals.requests==="number"?totals.requests:typeof s.requests==="number"?s.requests:0;const totalTokens=typeof totals.totalTokens==="number"?totals.totalTokens:typeof s.totalTokens==="number"?s.totalTokens:0;const errors429=typeof totals.rateLimited429==="number"?totals.rateLimited429:typeof s.errors429==="number"?s.errors429:typeof totals.failed==="number"?totals.failed:0;const reasoning=Number(totals.reasoningTokens||sess.reasoningTokensEst||0);const topModels=Object.entries(s.byModel||{}).sort((a,b)=>{const bTokens=Number(b[1]?.totalTokens??b[1]?.tokens??0);const aTokens=Number(a[1]?.totalTokens??a[1]?.tokens??0);return bTokens-aTokens}).slice(0,5);return React.createElement("section",{className:"cb-section-card"},React.createElement("div",{className:"cb-section-head"},React.createElement("div",{className:"cb-section-title"},t("stats.title")),React.createElement("div",{className:"cb-section-actions"},React.createElement("a",{href:"/dsh-clinebot/stats/export?format=json",download:"clinebot-stats.json",className:"cb-btn cb-btn-link"},React.createElement(CbIcon,{name:"download",size:13}),` ${t("stats.export_json")}`),React.createElement("a",{href:"/dsh-clinebot/stats/export",download:"clinebot-stats.csv",className:"cb-btn cb-btn-link"},React.createElement(CbIcon,{name:"download",size:13}),` ${t("stats.export_csv")}`),confirmReset?React.createElement(React.Fragment,null,React.createElement("button",{type:"button",className:"cb-btn cb-btn-warn",disabled:!!busy,onClick:()=>{setConfirmReset(false);if(typeof onResetStats==="function")onResetStats()}},t("stats.confirm_reset")),React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>setConfirmReset(false)},t("stats.cancel_reset"))):React.createElement("button",{type:"button",className:"cb-btn",disabled:!!busy,onClick:()=>setConfirmReset(true)},t("stats.reset_btn")))),React.createElement("div",{className:"cb-section-desc"},t("stats.desc")),React.createElement("div",{className:"cb-stats-grid"},React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},`${requests} (${sess.successfulRequests||0} sess)`),React.createElement("div",{className:"cb-stat-lbl"},t("stats.requests"))),React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},Number(totalTokens).toLocaleString()),React.createElement("div",{className:"cb-stat-lbl"},reasoning>0?`${t("stats.tokens")} (${reasoning.toLocaleString()} reasoning)`:t("stats.tokens"))),React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},`${errors429}`),React.createElement("div",{className:"cb-stat-lbl"},t("stats.errors429"))),React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},sess.lastLatencyMs?`${sess.lastLatencyMs} ms`:"\u2014"),React.createElement("div",{className:"cb-stat-lbl"},t("stats.latency")))),topModels.length>0?React.createElement("div",{className:"cb-model-list"},React.createElement("div",{className:"cb-model-list-title"},t("stats.top_models")),topModels.map(([modelId,data])=>React.createElement("div",{className:"cb-model-row",key:modelId},React.createElement("span",{className:"cb-mono"},modelId),React.createElement("span",{className:"cb-muted"},`${data.requests||0} reqs \xB7 ${Number(data.totalTokens??data.tokens??0).toLocaleString()} tok${data.reasoningTokens?` (${Number(data.reasoningTokens).toLocaleString()} reasoning)`:""}`)))):null)}
|
|
441
|
+
function StatsSection({status,onResetStats,busy,t}){const[confirmReset,setConfirmReset]=React.useState(false);const s=status?.statsSummary||{};const totals=s.totals||{};const sess=status?.sessionStats||{};const requests=typeof totals.requests==="number"?totals.requests:typeof s.requests==="number"?s.requests:0;const totalTokens=typeof totals.totalTokens==="number"?totals.totalTokens:typeof s.totalTokens==="number"?s.totalTokens:0;const errors429=typeof totals.rateLimited429==="number"?totals.rateLimited429:typeof s.errors429==="number"?s.errors429:typeof totals.failed==="number"?totals.failed:0;const reasoning=Number(totals.reasoningTokens||sess.reasoningTokensEst||0);const topModels=Object.entries(s.byModel||{}).sort((a,b)=>{const bTokens=Number(b[1]?.totalTokens??b[1]?.tokens??0);const aTokens=Number(a[1]?.totalTokens??a[1]?.tokens??0);return bTokens-aTokens}).slice(0,5);const topAccounts=Object.entries(s.byAccount||{}).sort((a,b)=>Number(b[1]?.totalTokens||0)-Number(a[1]?.totalTokens||0)).slice(0,5);return React.createElement("section",{className:"cb-section-card"},React.createElement("div",{className:"cb-section-head"},React.createElement("div",{className:"cb-section-title"},t("stats.title")),React.createElement("div",{className:"cb-section-actions"},React.createElement("a",{href:"/dsh-clinebot/stats/export?format=json",download:"clinebot-stats.json",className:"cb-btn cb-btn-link"},React.createElement(CbIcon,{name:"download",size:13}),` ${t("stats.export_json")}`),React.createElement("a",{href:"/dsh-clinebot/stats/export",download:"clinebot-stats.csv",className:"cb-btn cb-btn-link"},React.createElement(CbIcon,{name:"download",size:13}),` ${t("stats.export_csv")}`),confirmReset?React.createElement(React.Fragment,null,React.createElement("button",{type:"button",className:"cb-btn cb-btn-warn",disabled:!!busy,onClick:()=>{setConfirmReset(false);if(typeof onResetStats==="function")onResetStats()}},t("stats.confirm_reset")),React.createElement("button",{type:"button",className:"cb-btn",onClick:()=>setConfirmReset(false)},t("stats.cancel_reset"))):React.createElement("button",{type:"button",className:"cb-btn",disabled:!!busy,onClick:()=>setConfirmReset(true)},t("stats.reset_btn")))),React.createElement("div",{className:"cb-section-desc"},t("stats.desc")),React.createElement("div",{className:"cb-stats-grid"},React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},`${requests} (${sess.successfulRequests||0} sess)`),React.createElement("div",{className:"cb-stat-lbl"},t("stats.requests"))),React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},Number(totalTokens).toLocaleString()),React.createElement("div",{className:"cb-stat-lbl"},reasoning>0?`${t("stats.tokens")} (${reasoning.toLocaleString()} reasoning)`:t("stats.tokens"))),React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},`${errors429}`),React.createElement("div",{className:"cb-stat-lbl"},t("stats.errors429"))),React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},sess.lastLatencyMs?`${sess.lastLatencyMs} ms`:"\u2014"),React.createElement("div",{className:"cb-stat-lbl"},t("stats.latency")))),topModels.length>0?React.createElement("div",{className:"cb-model-list"},React.createElement("div",{className:"cb-model-list-title"},t("stats.top_models")),topModels.map(([modelId,data])=>React.createElement("div",{className:"cb-model-row",key:modelId},React.createElement("span",{className:"cb-mono"},modelId),React.createElement("span",{className:"cb-muted"},`${data.requests||0} reqs \xB7 ${Number(data.totalTokens??data.tokens??0).toLocaleString()} tok${data.reasoningTokens?` (${Number(data.reasoningTokens).toLocaleString()} reasoning)`:""}`)))):null,topAccounts.length>0?React.createElement("div",{className:"cb-model-list"},React.createElement("div",{className:"cb-model-list-title"},t("stats.top_accounts")),topAccounts.map(([accountId,data])=>React.createElement("div",{className:"cb-model-row",key:accountId},React.createElement("span",{className:"cb-mono"},accountId),React.createElement("span",{className:"cb-muted"},`${data.requests||0} reqs \xB7 ${Number(data.totalTokens||0).toLocaleString()} tok \xB7 \u2248 $${Number(data.costUsd||0).toFixed(4)}`)))):null,Number(totals.cacheSavingsUsd||0)>0?React.createElement("div",{className:"cb-hint"},t("stats.cache_saved",{amount:Number(totals.cacheSavingsUsd||0).toFixed(4)})):null)}
|
|
436
442
|
|
|
437
443
|
// --- src/client/components/diag-section.js ---
|
|
438
444
|
function DiagSection({keyPresent,busy,smokeResult,handleSmoke,t}){return React.createElement("section",{className:"cb-section-card"},React.createElement("div",{className:"cb-section-title"},t("diag.title")),React.createElement("div",{className:"cb-section-desc"},t("diag.desc")),React.createElement("div",{className:"cb-row cb-row-actions"},React.createElement("button",{type:"button",className:"cb-btn cb-btn-primary",disabled:!!busy||!keyPresent,onClick:handleSmoke},busy==="smoke"?t("diag.smoke_testing"):t("diag.smoke_btn"))),smokeResult?React.createElement("div",{className:"cb-preview"},t("diag.smoke_result",{latency:smokeResult.latencyMs,model:smokeResult.model,preview:smokeResult.preview||t("diag.empty")})):null)}
|
|
@@ -450,7 +456,7 @@ function heatWeekdayLabels(){return Array.from({length:7},(_,index)=>{const date
|
|
|
450
456
|
function QuotaPanelView({accountTabs,selectedUsage,accountName,accountEmail,accountPlan,accountBadges=[],activeAcc,routingMode,enabledCount,t}){return React.createElement(React.Fragment,null,accountTabs,selectedUsage?.ok===false?React.createElement("div",{className:"cb-alert-bad"},selectedUsage.error||t("analytics.no_data")):null,React.createElement("div",{className:"cb-dialogCard"},React.createElement("div",{className:"cb-dialogRow"},React.createElement("div",{className:"cb-dialogIdentity"},React.createElement("div",{className:"cb-row-title"},accountName),React.createElement("div",{className:"cb-row-desc"},[accountEmail,accountPlan].filter(Boolean).join(" \xB7 "))),React.createElement("div",{className:"cb-dialogBadges"},accountBadges.map((badge,index)=>React.createElement("span",{className:badge.className,key:`account-badge-${index}`},badge.label)),React.createElement("span",{className:"cb-badge cb-badge-ok"},t("badge.registered",{count:enabledCount})))),React.createElement("div",{className:"cb-dialogTiles"},React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-lbl"},t("dashboard.account_pool")),React.createElement("div",{className:"cb-stat-val"},activeAcc)),React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-lbl"},t("accounts.mode_label")),React.createElement("div",{className:"cb-stat-val"},routingMode)))),React.createElement("div",{className:"cb-dialogCard"},React.createElement("div",{className:"cb-section-title"},t("quota.title")),React.createElement("div",{className:"cb-dialogWindows"},selectedUsage?.windows?.fiveHour?React.createElement(ProgressBar,{label:t("quota.window_5h"),percentUsed:selectedUsage.windows.fiveHour.percentUsed||0,remainingPercent:selectedUsage.windows.fiveHour.remainingPercent||100,resetsAt:selectedUsage.windows.fiveHour.resetsAt,windowMs:5*60*60*1e3,t}):null,selectedUsage?.windows?.weekly?React.createElement(ProgressBar,{label:t("quota.window_weekly"),percentUsed:selectedUsage.windows.weekly.percentUsed||0,remainingPercent:selectedUsage.windows.weekly.remainingPercent||100,resetsAt:selectedUsage.windows.weekly.resetsAt,windowMs:7*24*60*60*1e3,t}):null,selectedUsage?.windows?.monthly?React.createElement(ProgressBar,{label:t("quota.window_monthly"),percentUsed:selectedUsage.windows.monthly.percentUsed||0,remainingPercent:selectedUsage.windows.monthly.remainingPercent||100,resetsAt:selectedUsage.windows.monthly.resetsAt,windowMs:30*24*60*60*1e3,t}):null,!selectedUsage?.windows||!selectedUsage.windows.fiveHour&&!selectedUsage.windows.weekly&&!selectedUsage.windows.monthly?React.createElement("div",{className:"cb-hint"},t("quota.no_windows")):null)))}
|
|
451
457
|
|
|
452
458
|
// --- src/client/components/dashboard-dialog.js ---
|
|
453
|
-
function DashboardDialog(props){const t=props?.t||makeT(en,en);const panel=props?.panel===true;const open=panel||props?.open!==false;const onClose=typeof props?.onClose==="function"?props.onClose:()=>{};const[data,setData]=React.useState(null);const[status,setStatus]=React.useState(null);const[analytics,setAnalytics]=React.useState(null);const[tab,setTab]=React.useState("quota");const[groupBy,setGroupBy]=React.useState("model");const[loading,setLoading]=React.useState(false);const[analyticsLoading,setAnalyticsLoading]=React.useState(false);const[updatedAt,setUpdatedAt]=React.useState("");const[err,setErr]=React.useState("");const[accountsUsage,setAccountsUsage]=React.useState(null);const[selectedAccount,setSelectedAccount]=React.useState("");const[analyticsDays,setAnalyticsDays]=React.useState(7);const[heatmapModel,setHeatmapModel]=React.useState("");const[selectedHeat,setSelectedHeat]=React.useState(null);const loadData=React.useCallback(async()=>{setLoading(true);setErr("");try{const[uRes,sRes,aRes]=await Promise.all([fetchWithTimeout(`${ROUTE_PREFIX}/usage`,{cache:"no-store"}),fetchWithTimeout(`${ROUTE_PREFIX}/status`,{cache:"no-store"}),fetchWithTimeout(`${ROUTE_PREFIX}/accounts/usage`,{cache:"no-store"})]);const uJson=await uRes.json().catch(()=>({}));const sJson=await sRes.json().catch(()=>({}));const aJson=await aRes.json().catch(()=>({}));if(uJson?.ok)setData(uJson);if(sJson?.ok)setStatus(sJson);if(aJson?.ok){setAccountsUsage(aJson);setSelectedAccount(current=>{const accounts=Array.isArray(aJson.accounts)?aJson.accounts:[];if(accounts.some(entry=>entry.apiKeyEnv===current))return current;return aJson.activeAccount||accounts[0]?.apiKeyEnv||""})}setUpdatedAt(new Date().toLocaleTimeString())}catch(e){setErr(String(e?.message||e))}finally{setLoading(false)}},[]);const loadAnalytics=React.useCallback(async(group=groupBy,days=analyticsDays,model=heatmapModel)=>{setAnalyticsLoading(true);setErr("");try{const from=Date.now()-days*864e5;const query=[`groupBy=${encodeURIComponent(group)}`,"limit=20",`from=${from}`,`heatmapDays=${days}`,...model?[`heatmapModel=${encodeURIComponent(model)}`]:[]].join("&");const res=await fetchWithTimeout(`${ROUTE_PREFIX}/analytics?${query}`,{cache:"no-store"});const json=await res.json().catch(()=>({}));if(!res.ok||!json.ok)throw new Error(json.error||`HTTP ${res.status}`);setAnalytics(json)}catch(e){setErr(String(e?.message||e))}finally{setAnalyticsLoading(false)}},[groupBy,analyticsDays,heatmapModel]);React.useEffect(()=>{if(open)loadData()},[open,loadData]);React.useEffect(()=>{if(open&&(panel||tab==="analytics"))loadAnalytics(groupBy,analyticsDays,heatmapModel)},[open,panel,tab,groupBy,analyticsDays,heatmapModel,loadAnalytics]);React.useEffect(()=>{if(!open)return void 0;let timer=null;const tick=()=>{loadData();if(panel||tab==="analytics")loadAnalytics(groupBy,analyticsDays,heatmapModel);timer=setTimeout(tick,12e4)};timer=setTimeout(tick,12e4);return()=>{if(timer)clearTimeout(timer)}},[open,panel,tab,groupBy,analyticsDays,heatmapModel,loadData,loadAnalytics]);if(!open)return null;function fmtTokens(value){const n=Number(value)||0;if(n>=1e6)return`${(n/1e6).toFixed(1)}M`;if(n>=1e3)return`${(n/1e3).toFixed(1)}k`;return String(n)}function fmtCost(value){const n=Number(value)||0;if(n===0)return"$0";if(n<1e-4)return"< $0.0001";return`$${n.toFixed(4)}`}function fmtMs(value){const n=Number(value)||0;return n>0?`${Math.round(n)} ms`:"\u2014"}const plan=data?.plan||status?.config?.defaultModel||"ClinePass";const email=data?.user?.email||"";const activeAcc=status?.config?.activeAccount||t("dashboard.auto_account");const enabledCount=status?.config?.enabledModels?.length||0;const routingMode=status?.config?.accountMode==="round_robin"?t("accounts.mode_round_robin"):t("accounts.mode_least_used");const accountEntries=Array.isArray(accountsUsage?.accounts)?accountsUsage.accounts:[];const selectedEntry=accountEntries.find(entry=>entry.apiKeyEnv===selectedAccount)||accountEntries.find(entry=>entry.active)||accountEntries[0];const selectedUsage=selectedEntry?.usage||data;const accountPlan=selectedUsage?.plan||plan;const accountEmail=selectedUsage?.user?.email||email;const accountName=selectedEntry?.label||selectedEntry?.apiKeyEnv||accountPlan;const accountBadges=[];if(selectedEntry){accountBadges.push(selectedEntry.configured===false?{className:"cb-badge cb-badge-warn",label:t("accounts.missing_key")}:{className:"cb-badge cb-badge-ok",label:t("accounts.configured")});if(selectedEntry.active)accountBadges.push({className:"cb-badge cb-badge-ok",label:t("accounts.active_badge")});if(selectedEntry.isPinned)accountBadges.push({className:"cb-badge",label:t("accounts.pinned_badge")})}const accountTabs=accountEntries.length>1?React.createElement("div",{className:"cb-account-tabs"},accountEntries.map(entry=>React.createElement("button",{type:"button",key:entry.apiKeyEnv||entry.id,className:entry.apiKeyEnv===selectedEntry?.apiKeyEnv?"cb-account-tab cb-account-tab-active":"cb-account-tab",onClick:()=>setSelectedAccount(entry.apiKeyEnv)},entry.label||entry.apiKeyEnv||entry.id))):null;const noticeItems=[];if(status?.key&&status.key.present===false){noticeItems.push(React.createElement("div",{className:"cb-notice",key:"no-key"},React.createElement("div",{className:"cb-notice-title"},t("analytics.no_key")),React.createElement("div",{className:"cb-notice-hint"},t("analytics.no_key_hint"))))}if(status?.health&&status.health.ok===false){noticeItems.push(React.createElement("div",{className:"cb-notice cb-notice-error",key:"health"},React.createElement("div",{className:"cb-notice-title"},t("analytics.health_error")),React.createElement("div",{className:"cb-notice-hint"},status.health.error||"")))}if(status&&status.isRegistered===false){noticeItems.push(React.createElement("div",{className:"cb-notice",key:"not-registered"},React.createElement("div",{className:"cb-notice-title"},t("analytics.not_registered")),React.createElement("div",{className:"cb-notice-hint"},t("analytics.not_registered_hint"))))}const notices=noticeItems.length?React.createElement(React.Fragment,null,...noticeItems):null;const errorNotice=err?React.createElement("div",{className:data?"cb-hint":"cb-alert-bad"},data?`${t("analytics.stale")} ${err}`:err):null;const totals=analytics?.totals;const secondaryTiles=totals?[[t("analytics.input"),fmtTokens(totals.promptTokens)],[t("analytics.output"),fmtTokens(totals.completionTokens)],[t("analytics.reasoning"),fmtTokens(totals.reasoningTokens)],[t("analytics.cache_read"),fmtTokens(totals.cacheReadTokens)],[t("analytics.cache_write"),fmtTokens(totals.cacheWriteTokens)],[t("analytics.avg_ttft"),fmtMs(totals.avgTtftMs)],[t("analytics.avg_latency"),fmtMs(totals.avgLatencyMs)],[t("analytics.p50_latency"),fmtMs(totals.p50LatencyMs)],[t("analytics.p95_latency"),fmtMs(totals.p95LatencyMs)],[t("analytics.errors"),String(Math.round(Number(totals.failed)||0))]]:[];const tabs=React.createElement("div",{className:"cb-tabs"},React.createElement("button",{type:"button",className:tab==="quota"?"cb-tab cb-tab-active":"cb-tab",onClick:()=>setTab("quota")},t("dashboard.tab_quota")),React.createElement("button",{type:"button",className:tab==="analytics"?"cb-tab cb-tab-active":"cb-tab",onClick:()=>setTab("analytics")},t("dashboard.tab_analytics")));const quotaBody=React.createElement(QuotaPanelView,{accountTabs,selectedUsage,accountName,accountEmail,accountPlan,accountBadges,activeAcc,routingMode,enabledCount,t});const analyticsBody=React.createElement("div",{className:"cb-dialogCard"},React.createElement("div",{className:"cb-dialogRow"},React.createElement("div",{className:"cb-section-title"},t("analytics.title")),React.createElement("div",{className:"cb-analytics-controls"},React.createElement("select",{className:"cb-heat-select",value:String(analyticsDays),title:t("analytics.range"),onChange:event=>setAnalyticsDays(Number(event.target.value))},React.createElement("option",{value:"7"},t("analytics.range_7")),React.createElement("option",{value:"30"},t("analytics.range_30")),React.createElement("option",{value:"90"},t("analytics.range_90"))),React.createElement("div",{className:"cb-seg"},["model","day","session"].map(group=>React.createElement("button",{key:group,type:"button",className:groupBy===group?"cb-seg-item cb-seg-item-active":"cb-seg-item",onClick:()=>setGroupBy(group)},t(`analytics.group_${group}`)))))),React.createElement("div",{className:"cb-row",style:{gap:"6px",marginBottom:"8px"}},React.createElement("a",{href:"/dsh-clinebot/stats/export?format=json",download:"clinebot-stats.json",className:"cb-btn cb-btn-link"},t("stats.export_json")),React.createElement("a",{href:"/dsh-clinebot/stats/export",download:"clinebot-stats.csv",className:"cb-btn cb-btn-link"},t("stats.export_csv"))),analytics?.totals?React.createElement("div",{className:"cb-dialogTiles"},React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},String(analytics.totals.requests||0)),React.createElement("div",{className:"cb-stat-lbl"},t("analytics.requests"))),React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},fmtTokens(analytics.totals.totalTokens)),React.createElement("div",{className:"cb-stat-lbl"},t("analytics.tokens"))),React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},`\u2248 ${fmtCost(analytics.totals.costUsd)}`),React.createElement("div",{className:"cb-stat-lbl"},t("analytics.cost"))),React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},`${Math.round(analytics.totals.successRate||0)}%`),React.createElement("div",{className:"cb-stat-lbl"},t("analytics.success")))):null,secondaryTiles.length?React.createElement("div",{className:"cb-dialogTiles"},secondaryTiles.map(([label,value])=>React.createElement("div",{className:"cb-stat-box",key:label},React.createElement("div",{className:"cb-stat-val"},value),React.createElement("div",{className:"cb-stat-lbl"},label)))):null,analyticsLoading?React.createElement("div",{className:"cb-hint"},t("analytics.loading")):analytics?.groups?.length?React.createElement("div",{className:"cb-analytics-list"},React.createElement("div",{className:"cb-analytics-head"},React.createElement("span",null,analytics.groupBy||groupBy),React.createElement("span",null,t("analytics.requests")),React.createElement("span",null,t("analytics.tokens")),React.createElement("span",null,t("analytics.cost")),React.createElement("span",null,t("analytics.ttft"))),analytics.groups.map(group=>React.createElement("div",{className:"cb-analytics-row",key:group.key},React.createElement("span",{className:"cb-mono"},group.label||group.key),React.createElement("span",null,String(group.requests||0)),React.createElement("span",null,fmtTokens(group.totalTokens)),React.createElement("span",null,`\u2248 ${fmtCost(group.costUsd)}`),React.createElement("span",null,group.avgTtftMs?`${Math.round(group.avgTtftMs)} ms`:"\u2014")))):React.createElement("div",{className:"cb-empty"},t("analytics.no_data")),analytics?.unpriced>0?React.createElement("div",{className:"cb-hint"},t("analytics.unpriced",{count:analytics.unpriced})):null);const heatmapCard=React.createElement("div",{className:"cb-dialogCard"},React.createElement("div",{className:"cb-section-title"},t("analytics.heatmap")),React.createElement("div",{className:"cb-section-desc"},t("analytics.heatmap_hint")),React.createElement(HeatmapView,{heatmap:analytics?.heatmap,model:heatmapModel,onModelChange:setHeatmapModel,selected:selectedHeat,onSelect:cell=>setSelectedHeat(current=>current&¤t.day===cell.day&¤t.hour===cell.hour?null:cell),t}),selectedHeat?React.createElement("div",{className:"cb-hint"},t("analytics.heatmap_selected",{day:selectedHeat.label,hour:String(selectedHeat.hour).padStart(2,"0"),count:selectedHeat.value})):null);const panelBody=React.createElement("div",{className:"cb-panel"},React.createElement("div",{className:"cb-panel-head"},React.createElement("div",{className:"cb-panel-identity"},React.createElement("div",{className:"cb-panel-title"},t("dashboard.title")),React.createElement("div",{className:"cb-panel-sub"},t("dashboard.subtitle"))),React.createElement("div",{className:"cb-panel-actions"},updatedAt?React.createElement("span",{className:"cb-panel-updated"},`${t("dashboard.updated_at")} ${updatedAt}`):null,React.createElement("button",{type:"button",className:"cb-btn",disabled:loading||analyticsLoading,onClick:()=>{loadData();loadAnalytics(groupBy)}},loading||analyticsLoading?t("dashboard.refreshing"):t("dashboard.refresh")),React.createElement("button",{type:"button",className:"cb-panel-close","aria-label":t("dashboard.close"),title:t("dashboard.close"),onClick:onClose},"\xD7"))),notices,errorNotice,quotaBody,analyticsBody,heatmapCard);const body=React.createElement("div",{className:"cb-dialog"},React.createElement("div",{className:"cb-dialogMeta"},updatedAt?React.createElement("span",null,`${t("dashboard.updated_at")} ${updatedAt}`):null,React.createElement("span",{className:"cb-settings-spacer"}),React.createElement("button",{type:"button",className:"cb-btn",disabled:loading||analyticsLoading,onClick:()=>tab==="quota"?loadData():loadAnalytics(groupBy)},loading||analyticsLoading?t("dashboard.refreshing"):t("dashboard.refresh"))),tabs,notices,errorNotice,tab==="quota"?quotaBody:React.createElement(React.Fragment,null,analyticsBody,heatmapCard));if(panel)return panelBody;if(Modal){return React.createElement(Modal,{open:true,onClose,title:t("dashboard.title"),description:t("dashboard.subtitle"),closeLabel:t("dashboard.close"),children:body})}return React.createElement("div",{className:"cb-dialogFallback",role:"dialog","aria-label":t("dashboard.title"),onClick:onClose},React.createElement("div",{className:"cb-dialogFallbackPanel",onClick:event=>event.stopPropagation()},React.createElement("div",{className:"cb-dialogMeta"},React.createElement("span",{className:"cb-row-title"},t("dashboard.title")),React.createElement("span",{className:"cb-settings-spacer"}),React.createElement("button",{type:"button",className:"cb-btn","aria-label":t("dashboard.close"),onClick:onClose},t("dashboard.close"))),body))}
|
|
459
|
+
function DashboardDialog(props){const t=props?.t||makeT(en,en);const panel=props?.panel===true;const open=panel||props?.open!==false;const onClose=typeof props?.onClose==="function"?props.onClose:()=>{};const[data,setData]=React.useState(null);const[status,setStatus]=React.useState(null);const[analytics,setAnalytics]=React.useState(null);const[tab,setTab]=React.useState("quota");const[groupBy,setGroupBy]=React.useState("model");const[loading,setLoading]=React.useState(false);const[analyticsLoading,setAnalyticsLoading]=React.useState(false);const[updatedAt,setUpdatedAt]=React.useState("");const[err,setErr]=React.useState("");const[accountsUsage,setAccountsUsage]=React.useState(null);const[selectedAccount,setSelectedAccount]=React.useState("");const[analyticsDays,setAnalyticsDays]=React.useState(7);const[heatmapModel,setHeatmapModel]=React.useState("");const[selectedHeat,setSelectedHeat]=React.useState(null);const loadData=React.useCallback(async()=>{setLoading(true);setErr("");try{const[uRes,sRes,aRes]=await Promise.all([fetchWithTimeout(`${ROUTE_PREFIX}/usage`,{cache:"no-store"}),fetchWithTimeout(`${ROUTE_PREFIX}/status`,{cache:"no-store"}),fetchWithTimeout(`${ROUTE_PREFIX}/accounts/usage`,{cache:"no-store"})]);const uJson=await uRes.json().catch(()=>({}));const sJson=await sRes.json().catch(()=>({}));const aJson=await aRes.json().catch(()=>({}));if(uJson?.ok)setData(uJson);if(sJson?.ok)setStatus(sJson);if(aJson?.ok){setAccountsUsage(aJson);setSelectedAccount(current=>{const accounts=Array.isArray(aJson.accounts)?aJson.accounts:[];if(accounts.some(entry=>entry.apiKeyEnv===current))return current;return aJson.activeAccount||accounts[0]?.apiKeyEnv||""})}setUpdatedAt(new Date().toLocaleTimeString())}catch(e){setErr(String(e?.message||e))}finally{setLoading(false)}},[]);const loadAnalytics=React.useCallback(async(group=groupBy,days=analyticsDays,model=heatmapModel)=>{setAnalyticsLoading(true);setErr("");try{const from=Date.now()-days*864e5;const query=[`groupBy=${encodeURIComponent(group)}`,"limit=20",`from=${from}`,`heatmapDays=${days}`,...model?[`heatmapModel=${encodeURIComponent(model)}`]:[]].join("&");const res=await fetchWithTimeout(`${ROUTE_PREFIX}/analytics?${query}`,{cache:"no-store"});const json=await res.json().catch(()=>({}));if(!res.ok||!json.ok)throw new Error(json.error||`HTTP ${res.status}`);setAnalytics(json)}catch(e){setErr(String(e?.message||e))}finally{setAnalyticsLoading(false)}},[groupBy,analyticsDays,heatmapModel]);React.useEffect(()=>{if(open)loadData()},[open,loadData]);React.useEffect(()=>{if(open&&(panel||tab==="analytics"))loadAnalytics(groupBy,analyticsDays,heatmapModel)},[open,panel,tab,groupBy,analyticsDays,heatmapModel,loadAnalytics]);React.useEffect(()=>{if(!open)return void 0;let timer=null;const tick=()=>{loadData();if(panel||tab==="analytics")loadAnalytics(groupBy,analyticsDays,heatmapModel);timer=setTimeout(tick,12e4)};timer=setTimeout(tick,12e4);return()=>{if(timer)clearTimeout(timer)}},[open,panel,tab,groupBy,analyticsDays,heatmapModel,loadData,loadAnalytics]);if(!open)return null;function fmtTokens(value){const n=Number(value)||0;if(n>=1e6)return`${(n/1e6).toFixed(1)}M`;if(n>=1e3)return`${(n/1e3).toFixed(1)}k`;return String(n)}function fmtCost(value){const n=Number(value)||0;if(n===0)return"$0";if(n<1e-4)return"< $0.0001";return`$${n.toFixed(4)}`}function fmtMs(value){const n=Number(value)||0;return n>0?`${Math.round(n)} ms`:"\u2014"}const plan=data?.plan||status?.config?.defaultModel||"ClinePass";const email=data?.user?.email||"";const activeAcc=status?.config?.activeAccount||t("dashboard.auto_account");const enabledCount=status?.config?.enabledModels?.length||0;const routingMode=status?.config?.accountMode==="round_robin"?t("accounts.mode_round_robin"):t("accounts.mode_least_used");const accountEntries=Array.isArray(accountsUsage?.accounts)?accountsUsage.accounts:[];const selectedEntry=accountEntries.find(entry=>entry.apiKeyEnv===selectedAccount)||accountEntries.find(entry=>entry.active)||accountEntries[0];const selectedUsage=selectedEntry?.usage||data;const accountPlan=selectedUsage?.plan||plan;const accountEmail=selectedUsage?.user?.email||email;const accountName=selectedEntry?.label||selectedEntry?.apiKeyEnv||accountPlan;const accountBadges=[];if(selectedEntry){accountBadges.push(selectedEntry.configured===false?{className:"cb-badge cb-badge-warn",label:t("accounts.missing_key")}:{className:"cb-badge cb-badge-ok",label:t("accounts.configured")});if(selectedEntry.active)accountBadges.push({className:"cb-badge cb-badge-ok",label:t("accounts.active_badge")});if(selectedEntry.isPinned)accountBadges.push({className:"cb-badge",label:t("accounts.pinned_badge")})}const accountTabs=accountEntries.length>1?React.createElement("div",{className:"cb-account-tabs"},accountEntries.map(entry=>React.createElement("button",{type:"button",key:entry.apiKeyEnv||entry.id,className:entry.apiKeyEnv===selectedEntry?.apiKeyEnv?"cb-account-tab cb-account-tab-active":"cb-account-tab",onClick:()=>setSelectedAccount(entry.apiKeyEnv)},entry.label||entry.apiKeyEnv||entry.id))):null;const noticeItems=[];if(status?.key&&status.key.present===false){noticeItems.push(React.createElement("div",{className:"cb-notice",key:"no-key"},React.createElement("div",{className:"cb-notice-title"},t("analytics.no_key")),React.createElement("div",{className:"cb-notice-hint"},t("analytics.no_key_hint"))))}if(status?.health&&status.health.ok===false){noticeItems.push(React.createElement("div",{className:"cb-notice cb-notice-error",key:"health"},React.createElement("div",{className:"cb-notice-title"},t("analytics.health_error")),React.createElement("div",{className:"cb-notice-hint"},status.health.error||"")))}if(status&&status.isRegistered===false){noticeItems.push(React.createElement("div",{className:"cb-notice",key:"not-registered"},React.createElement("div",{className:"cb-notice-title"},t("analytics.not_registered")),React.createElement("div",{className:"cb-notice-hint"},t("analytics.not_registered_hint"))))}const notices=noticeItems.length?React.createElement(React.Fragment,null,...noticeItems):null;const errorNotice=err?React.createElement("div",{className:data?"cb-hint":"cb-alert-bad"},data?`${t("analytics.stale")} ${err}`:err):null;const totals=analytics?.totals;const secondaryTiles=totals?[[t("analytics.input"),fmtTokens(totals.promptTokens)],[t("analytics.output"),fmtTokens(totals.completionTokens)],[t("analytics.reasoning"),fmtTokens(totals.reasoningTokens)],[t("analytics.cache_read"),fmtTokens(totals.cacheReadTokens)],[t("analytics.cache_write"),fmtTokens(totals.cacheWriteTokens)],[t("analytics.cache_saved"),`\u2248 ${fmtCost(totals.cacheSavingsUsd)}`],[t("analytics.avg_ttft"),fmtMs(totals.avgTtftMs)],[t("analytics.avg_latency"),fmtMs(totals.avgLatencyMs)],[t("analytics.p50_latency"),fmtMs(totals.p50LatencyMs)],[t("analytics.p95_latency"),fmtMs(totals.p95LatencyMs)],[t("analytics.errors"),String(Math.round(Number(totals.failed)||0))]]:[];const tabs=React.createElement("div",{className:"cb-tabs"},React.createElement("button",{type:"button",className:tab==="quota"?"cb-tab cb-tab-active":"cb-tab",onClick:()=>setTab("quota")},t("dashboard.tab_quota")),React.createElement("button",{type:"button",className:tab==="analytics"?"cb-tab cb-tab-active":"cb-tab",onClick:()=>setTab("analytics")},t("dashboard.tab_analytics")));const quotaBody=React.createElement(QuotaPanelView,{accountTabs,selectedUsage,accountName,accountEmail,accountPlan,accountBadges,activeAcc,routingMode,enabledCount,t});const analyticsBody=React.createElement("div",{className:"cb-dialogCard"},React.createElement("div",{className:"cb-dialogRow"},React.createElement("div",{className:"cb-section-title"},t("analytics.title")),React.createElement("div",{className:"cb-analytics-controls"},React.createElement("select",{className:"cb-heat-select",value:String(analyticsDays),title:t("analytics.range"),onChange:event=>setAnalyticsDays(Number(event.target.value))},React.createElement("option",{value:"7"},t("analytics.range_7")),React.createElement("option",{value:"30"},t("analytics.range_30")),React.createElement("option",{value:"90"},t("analytics.range_90"))),React.createElement("div",{className:"cb-seg"},["model","day","account","session"].map(group=>React.createElement("button",{key:group,type:"button",className:groupBy===group?"cb-seg-item cb-seg-item-active":"cb-seg-item",onClick:()=>setGroupBy(group)},t(`analytics.group_${group}`)))))),React.createElement("div",{className:"cb-row",style:{gap:"6px",marginBottom:"8px"}},React.createElement("a",{href:"/dsh-clinebot/stats/export?format=json",download:"clinebot-stats.json",className:"cb-btn cb-btn-link"},t("stats.export_json")),React.createElement("a",{href:"/dsh-clinebot/stats/export",download:"clinebot-stats.csv",className:"cb-btn cb-btn-link"},t("stats.export_csv"))),analytics?.totals?React.createElement("div",{className:"cb-dialogTiles"},React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},String(analytics.totals.requests||0)),React.createElement("div",{className:"cb-stat-lbl"},t("analytics.requests"))),React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},fmtTokens(analytics.totals.totalTokens)),React.createElement("div",{className:"cb-stat-lbl"},t("analytics.tokens"))),React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},`\u2248 ${fmtCost(analytics.totals.costUsd)}`),React.createElement("div",{className:"cb-stat-lbl"},t("analytics.cost"))),React.createElement("div",{className:"cb-stat-box"},React.createElement("div",{className:"cb-stat-val"},`${Math.round(analytics.totals.successRate||0)}%`),React.createElement("div",{className:"cb-stat-lbl"},t("analytics.success")))):null,secondaryTiles.length?React.createElement("div",{className:"cb-dialogTiles"},secondaryTiles.map(([label,value])=>React.createElement("div",{className:"cb-stat-box",key:label},React.createElement("div",{className:"cb-stat-val"},value),React.createElement("div",{className:"cb-stat-lbl"},label)))):null,analyticsLoading?React.createElement("div",{className:"cb-hint"},t("analytics.loading")):analytics?.groups?.length?React.createElement("div",{className:"cb-analytics-list"},React.createElement("div",{className:"cb-analytics-head"},React.createElement("span",null,analytics.groupBy||groupBy),React.createElement("span",null,t("analytics.requests")),React.createElement("span",null,t("analytics.tokens")),React.createElement("span",null,t("analytics.cost")),React.createElement("span",null,t("analytics.ttft"))),analytics.groups.map(group=>React.createElement("div",{className:"cb-analytics-row",key:group.key},React.createElement("span",{className:"cb-mono"},group.label||group.key),React.createElement("span",null,String(group.requests||0)),React.createElement("span",null,fmtTokens(group.totalTokens)),React.createElement("span",null,`\u2248 ${fmtCost(group.costUsd)}`),React.createElement("span",null,group.avgTtftMs?`${Math.round(group.avgTtftMs)} ms`:"\u2014")))):React.createElement("div",{className:"cb-empty"},t("analytics.no_data")),analytics?.unpriced>0?React.createElement("div",{className:"cb-hint"},t("analytics.unpriced",{count:analytics.unpriced})):null);const heatmapCard=React.createElement("div",{className:"cb-dialogCard"},React.createElement("div",{className:"cb-section-title"},t("analytics.heatmap")),React.createElement("div",{className:"cb-section-desc"},t("analytics.heatmap_hint")),React.createElement(HeatmapView,{heatmap:analytics?.heatmap,model:heatmapModel,onModelChange:setHeatmapModel,selected:selectedHeat,onSelect:cell=>setSelectedHeat(current=>current&¤t.day===cell.day&¤t.hour===cell.hour?null:cell),t}),selectedHeat?React.createElement("div",{className:"cb-hint"},t("analytics.heatmap_selected",{day:selectedHeat.label,hour:String(selectedHeat.hour).padStart(2,"0"),count:selectedHeat.value})):null);const panelBody=React.createElement("div",{className:"cb-panel"},React.createElement("div",{className:"cb-panel-head"},React.createElement("div",{className:"cb-panel-identity"},React.createElement("div",{className:"cb-panel-title"},t("dashboard.title")),React.createElement("div",{className:"cb-panel-sub"},t("dashboard.subtitle"))),React.createElement("div",{className:"cb-panel-actions"},updatedAt?React.createElement("span",{className:"cb-panel-updated"},`${t("dashboard.updated_at")} ${updatedAt}`):null,React.createElement("button",{type:"button",className:"cb-btn",disabled:loading||analyticsLoading,onClick:()=>{loadData();loadAnalytics(groupBy)}},loading||analyticsLoading?t("dashboard.refreshing"):t("dashboard.refresh")),React.createElement("button",{type:"button",className:"cb-panel-close","aria-label":t("dashboard.close"),title:t("dashboard.close"),onClick:onClose},"\xD7"))),notices,errorNotice,quotaBody,analyticsBody,heatmapCard);const body=React.createElement("div",{className:"cb-dialog"},React.createElement("div",{className:"cb-dialogMeta"},updatedAt?React.createElement("span",null,`${t("dashboard.updated_at")} ${updatedAt}`):null,React.createElement("span",{className:"cb-settings-spacer"}),React.createElement("button",{type:"button",className:"cb-btn",disabled:loading||analyticsLoading,onClick:()=>tab==="quota"?loadData():loadAnalytics(groupBy)},loading||analyticsLoading?t("dashboard.refreshing"):t("dashboard.refresh"))),tabs,notices,errorNotice,tab==="quota"?quotaBody:React.createElement(React.Fragment,null,analyticsBody,heatmapCard));if(panel)return panelBody;if(Modal){return React.createElement(Modal,{open:true,onClose,title:t("dashboard.title"),description:t("dashboard.subtitle"),closeLabel:t("dashboard.close"),children:body})}return React.createElement("div",{className:"cb-dialogFallback",role:"dialog","aria-label":t("dashboard.title"),onClick:onClose},React.createElement("div",{className:"cb-dialogFallbackPanel",onClick:event=>event.stopPropagation()},React.createElement("div",{className:"cb-dialogMeta"},React.createElement("span",{className:"cb-row-title"},t("dashboard.title")),React.createElement("span",{className:"cb-settings-spacer"}),React.createElement("button",{type:"button",className:"cb-btn","aria-label":t("dashboard.close"),onClick:onClose},t("dashboard.close"))),body))}
|
|
454
460
|
|
|
455
461
|
// --- src/client/components/composer-cost.js ---
|
|
456
462
|
function formatTokens(count){const n=Number(count)||0;if(n>=1e6)return(n/1e6).toFixed(1)+"M";if(n>=1e3)return(n/1e3).toFixed(1)+"k";return String(n)}function findPrice(modelId,prices){if(!modelId||!Array.isArray(prices))return null;const exact=prices.find(entry=>entry&&entry.id===modelId);if(exact)return exact;return prices.find(entry=>entry&&entry.id&&(modelId===entry.id||modelId.endsWith(`/${entry.id}`)||modelId.includes(entry.id)))||null}function calculateCost(tokens,modelId,prices){const pTok=Number(tokens?.promptTokens||tokens?.inputTokens||0);const cTok=Number(tokens?.completionTokens||tokens?.outputTokens||0);const rTok=Number(tokens?.reasoningTokens||0);const cacheRead=Number(tokens?.cacheReadTokens||tokens?.cacheRead||0);const cacheWrite=Number(tokens?.cacheWriteTokens||tokens?.cacheWrite||0);const total=pTok+cTok+rTok;const price=findPrice(modelId,prices);if(!price)return{cost:0,priced:false,source:null,pTok,cTok,rTok,cacheRead,cacheWrite,total};const uncachedInput=Math.max(0,pTok-cacheRead-cacheWrite);const cost=(uncachedInput*Number(price.input||0)+cTok*Number(price.output||0)+cacheRead*Number(price.cacheRead||0)+cacheWrite*Number(price.cacheWrite||0))/1e6;return{cost,priced:true,source:price.source||"estimate",pTok,cTok,rTok,cacheRead,cacheWrite,total}}const COST_ANCHOR='[data-slot="conversation.composer.dock"]';const STATS_ROOT="[data-composer-stats]";const STATS_USAGE='[data-composer-stat="usage"]';const STATS_ANY="[data-composer-stat]";const DIALOG_TRIGGER='button[aria-haspopup="dialog"]';const MISSES_BEFORE_WARNING=3;function ComposerCostDock(props){if(typeof props?.useProjection!=="function")return null;return React.createElement(ComposerCostEntry,props)}function ComposerCostEntry(props){const t=props?.t||makeT(en,en);const configSnapshot=useConfigFormsSnapshot(props?.ctx);const[stats,setStats]=React.useState(null);const[prices,setPrices]=React.useState([]);const[sessionStats,setSessionStats]=React.useState(null);const markerRef=React.useRef(null);const projectedTokens=props.useProjection("tokenUsage");const selection=props.useProjection("modelSelection");const sessionId=String(props?.sessionId||"");const priceOverridesKey=JSON.stringify(configSnapshot?.value?.priceOverrides||{});React.useEffect(()=>{let active=true;fetchWithTimeout(`${ROUTE_PREFIX}/prices`,{cache:"no-store"}).then(response=>response.json()).then(data=>{if(active&&data?.ok&&Array.isArray(data.models))setPrices(data.models)}).catch(()=>{});return()=>{active=false}},[priceOverridesKey]);React.useEffect(()=>{if(!sessionId){setSessionStats(null);return void 0}let active=true;const loadSession=async()=>{try{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/stats/session?sessionId=${encodeURIComponent(sessionId)}`,{cache:"no-store"});const data=await res.json().catch(()=>null);if(active&&data?.ok)setSessionStats(data.session||null)}catch{}};loadSession();let timer=null;const schedule=()=>{timer=setTimeout(async()=>{await loadSession();if(active)schedule()},1e4)};schedule();return()=>{active=false;if(timer)clearTimeout(timer)}},[sessionId,projectedTokens]);React.useEffect(()=>{if(projectedTokens||sessionId)return void 0;let active=true;const loadStats=async()=>{try{const res=await fetchWithTimeout(`${ROUTE_PREFIX}/stats`,{cache:"no-store"});if(!res.ok)return;const data=await res.json().catch(()=>null);const totals=data?.stats?.totals||data?.stats?.summary||null;if(active&&totals)setStats(totals)}catch{}};loadStats();const timer=setTimeout(loadStats,3e4);return()=>{active=false;clearTimeout(timer)}},[projectedTokens,sessionId]);const selected=selection?.lastUsed??selection?.next??(selection?.provider||selection?.model?selection:null);const provider=String(selected?.provider||"");const modelId=String(selected?.model||selected?.id||"");const isClineBot=(provider===""||provider==="clinebot"||provider==="llm-clinebot")&&(provider==="clinebot"||provider==="llm-clinebot"||modelId.startsWith("cline-pass/"));const session=sessionStats&&Number(sessionStats.requests||0)>0?sessionStats:null;const sessionTokens=session?{promptTokens:Number(session.promptTokens||0),completionTokens:Number(session.completionTokens||0),reasoningTokens:Number(session.reasoningTokens||0),cacheReadTokens:Number(session.cacheReadTokens||0),cacheWriteTokens:Number(session.cacheWriteTokens||0)}:null;const rawTokens=sessionTokens||(sessionId?null:projectedTokens||{promptTokens:stats?.promptTokens||0,completionTokens:stats?.completionTokens||0,reasoningTokens:stats?.reasoningTokens||0,cacheReadTokens:stats?.cacheReadTokens||0,cacheWriteTokens:stats?.cacheWriteTokens||0});const cacheRead=Number(rawTokens?.cacheReadTokens||0);const cacheWrite=Number(rawTokens?.cacheWriteTokens||0);const uncachedInput=Number(rawTokens?.uncachedInputTokens);const promptTokens=rawTokens?Number.isFinite(uncachedInput)?uncachedInput+cacheRead+cacheWrite:Number(rawTokens.promptTokens||rawTokens.inputTokens||0):0;const tokens={promptTokens,completionTokens:Number(rawTokens?.completionTokens||rawTokens?.outputTokens||0),reasoningTokens:Number(rawTokens?.reasoningTokens||0),cacheReadTokens:cacheRead,cacheWriteTokens:cacheWrite};const calculated=calculateCost(tokens,session?.model||modelId,prices);const cost=session?Number(session.costUsd||0):calculated.cost;const priced=session?Number(session.pricedRequests||0)>0:calculated.priced;const source=session?findPrice(session.model||modelId,prices)?.source||"estimate":calculated.source;const{pTok,cTok,rTok}=calculated;const total=session?Number(session.totalTokens||pTok+cTok):calculated.total;const last=session?.last||null;const lastCost=last?Number(last.costUsd||0):0;const lastPriced=Boolean(last?.priced);const costValue=priced?cost===0?t("composer.free"):cost<1e-4?"< $0.0001":`$${cost.toFixed(4)}`:t("composer.unpriced");const costDisplay=priced&&source!=="override"&&cost>0?`\u2248 ${costValue}`:costValue;const lastCostValue=last?lastPriced?lastCost===0?t("composer.free"):lastCost<1e-4?"< $0.0001":`$${lastCost.toFixed(4)}`:t("composer.unpriced"):"";const title=[`${t("composer.cost_title")} ${costDisplay}`,...last?[`${t("composer.last_request")} ${lastCostValue}`]:[],`${t("composer.prompt_tok")} ${formatTokens(pTok)}`,`${t("composer.completion_tok")} ${formatTokens(cTok)}`,...rTok>0?[`${t("composer.reasoning_tok")} ${formatTokens(rTok)}`]:[],...cacheRead>0?[`${t("composer.cache_read")} ${formatTokens(cacheRead)}`]:[],...cacheWrite>0?[`${t("composer.cache_write")} ${formatTokens(cacheWrite)}`]:[],...priced?[`${t("composer.price_source")} ${source==="override"?t("composer.price_override"):source==="public"?t("composer.price_public"):t("composer.price_estimate")}`]:[]].join(" \xB7 ");const inject=isClineBot&&priced&&total>0;React.useEffect(()=>{if(!inject||typeof document==="undefined")return void 0;const marker=markerRef.current;if(!marker)return void 0;const scope=marker.closest(COST_ANCHOR);if(!scope)return void 0;let host=null;let root=null;let valueNode=null;let misses=0;const apply=()=>{if(!root||root.parentNode===null){const usageAnchor=scope.querySelector(STATS_USAGE)||scope.querySelector(STATS_ANY);const statsRoot=usageAnchor||scope.querySelector(STATS_ROOT)||scope;const triggers=statsRoot.querySelectorAll(DIALOG_TRIGGER);const button=triggers[triggers.length-1]||usageAnchor;if(!button){misses+=1;if(misses===MISSES_BEFORE_WARNING){console.warn("[dsh-clinebot] no native token-usage pill found for the session cost")}return}misses=0;host=button;root=document.createElement("span");root.setAttribute("data-clinebot-session-cost","");root.style.display="inline-flex";root.style.alignItems="center";const separator=document.createElement("span");separator.textContent="\xB7";separator.setAttribute("aria-hidden","true");separator.style.color="var(--dsw-alias-separator-primary)";separator.style.margin="0 6px 0 0";valueNode=document.createElement("span");valueNode.style.fontWeight="500";root.appendChild(separator);root.appendChild(valueNode);button.appendChild(root)}if(root.parentNode!==host)host.appendChild(root);if(valueNode.textContent!==costDisplay)valueNode.textContent=costDisplay;if(root.title!==title)root.title=title};apply();const observer=typeof window!=="undefined"&&typeof window.MutationObserver==="function"?new window.MutationObserver(apply):null;observer?.observe(document.body,{childList:true});let timer=null;const tick=()=>{apply();timer=setTimeout(tick,2e3)};timer=setTimeout(tick,2e3);return()=>{observer?.disconnect();if(timer)clearTimeout(timer);if(root?.parentNode)root.parentNode.removeChild(root)}},[inject,costDisplay,title]);if(configSnapshot?.value?.showComposerCost===false)return null;if(total===0&&!stats)return null;if(!inject)return null;return React.createElement("span",{ref:markerRef,style:{display:"none"},"data-clinebot-cost-anchor":""})}
|
package/lib/config.js
CHANGED
|
@@ -62,6 +62,8 @@ export const Config = z.object({
|
|
|
62
62
|
.description('Public model price catalog URL used by the optional price sync.').volatile(),
|
|
63
63
|
priceCatalogPath: z.string().default('~/.dsh/clinebot-prices.json')
|
|
64
64
|
.description('Local cache for the public price catalog.').volatile(),
|
|
65
|
+
eventLogPath: z.string().default('~/.dsh/clinebot-events.json')
|
|
66
|
+
.description('Local ring buffer for account cooldown, recovery and routing events.').volatile(),
|
|
65
67
|
enabledModels: z.array(z.string()).default([])
|
|
66
68
|
.description('Deprecated: preserved for backwards compatibility with earlier versions.'),
|
|
67
69
|
dynamicModels: z.array(z.object({
|
|
@@ -250,6 +252,7 @@ export function publicConfig(cfg) {
|
|
|
250
252
|
statsPath: String(cfg?.statsPath || '~/.dsh/clinebot-stats.json'),
|
|
251
253
|
priceCatalogUrl: String(cfg?.priceCatalogUrl || 'https://models.dev/api.json'),
|
|
252
254
|
priceCatalogPath: String(cfg?.priceCatalogPath || '~/.dsh/clinebot-prices.json'),
|
|
255
|
+
eventLogPath: String(cfg?.eventLogPath || '~/.dsh/clinebot-events.json'),
|
|
253
256
|
statsRetentionDays: Number.isFinite(Number(cfg?.statsRetentionDays))
|
|
254
257
|
? Math.max(0, Math.min(3650, Number(cfg.statsRetentionDays)))
|
|
255
258
|
: 95,
|
package/lib/index.js
CHANGED
|
@@ -23,6 +23,8 @@ import { resolveAccountPool } from './account-pool.js'
|
|
|
23
23
|
import { loadStats, flushStats, resolvePath, switchStatsStorage, setStatsRetentionDays } from './stats-storage.js'
|
|
24
24
|
import { loadPublicPriceCache } from './price-sync.js'
|
|
25
25
|
import { setPublicPriceCatalog } from './model-prices.js'
|
|
26
|
+
import { loadAccountEvents, flushAccountEvents, recordAccountEvent } from './account-events.js'
|
|
27
|
+
import { setAccountEventSink } from './session-router.js'
|
|
26
28
|
import { service, migrateLegacyPiAiProvider } from './services.js'
|
|
27
29
|
import { applyTuiSettings } from './tui-settings.js'
|
|
28
30
|
|
|
@@ -95,12 +97,21 @@ export function apply(ctx, config) {
|
|
|
95
97
|
let activeStatsPath = resolvePath(publicConfig(live()).statsPath)
|
|
96
98
|
setStatsRetentionDays(publicConfig(live()).statsRetentionDays)
|
|
97
99
|
setPublicPriceCatalog(loadPublicPriceCache(publicConfig(live()).priceCatalogPath))
|
|
100
|
+
let activeEventPath = resolvePath(publicConfig(live()).eventLogPath)
|
|
101
|
+
loadAccountEvents(activeEventPath)
|
|
102
|
+
setAccountEventSink(recordAccountEvent)
|
|
98
103
|
loadStats(activeStatsPath)
|
|
99
104
|
|
|
100
105
|
const syncStatsStorage = (cfg) => {
|
|
101
106
|
const pub = publicConfig(cfg)
|
|
102
107
|
setStatsRetentionDays(pub.statsRetentionDays)
|
|
103
108
|
setPublicPriceCatalog(loadPublicPriceCache(pub.priceCatalogPath))
|
|
109
|
+
const nextEventPath = resolvePath(pub.eventLogPath)
|
|
110
|
+
if (nextEventPath !== activeEventPath) {
|
|
111
|
+
flushAccountEvents()
|
|
112
|
+
activeEventPath = nextEventPath
|
|
113
|
+
loadAccountEvents(activeEventPath)
|
|
114
|
+
}
|
|
104
115
|
const nextPath = resolvePath(pub.statsPath)
|
|
105
116
|
if (nextPath !== activeStatsPath) {
|
|
106
117
|
switchStatsStorage(nextPath)
|
|
@@ -253,6 +264,8 @@ export function apply(ctx, config) {
|
|
|
253
264
|
ctx.effect(() => {
|
|
254
265
|
return () => {
|
|
255
266
|
flushStats(activeStatsPath)
|
|
267
|
+
flushAccountEvents()
|
|
268
|
+
setAccountEventSink(null)
|
|
256
269
|
}
|
|
257
270
|
}, 'dsh-clinebot: stats-storage')
|
|
258
271
|
}
|
package/lib/model-prices.js
CHANGED
|
@@ -95,7 +95,7 @@ export function estimateCost({
|
|
|
95
95
|
}) {
|
|
96
96
|
const price = resolveModelPrice(model, overrides)
|
|
97
97
|
if (!price) {
|
|
98
|
-
return { usd: 0, priced: false, breakdown: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } }
|
|
98
|
+
return { usd: 0, priced: false, cacheSavingsUsd: 0, breakdown: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } }
|
|
99
99
|
}
|
|
100
100
|
const prompt = Math.max(0, Number(promptTokens) || 0)
|
|
101
101
|
const cacheRead = Math.max(0, Number(cacheReadTokens) || 0)
|
|
@@ -109,7 +109,10 @@ export function estimateCost({
|
|
|
109
109
|
cacheWrite: price.cacheWrite === null ? 0 : (cacheWrite * price.cacheWrite) / 1_000_000,
|
|
110
110
|
}
|
|
111
111
|
const usd = breakdown.input + breakdown.output + breakdown.cacheRead + breakdown.cacheWrite
|
|
112
|
-
|
|
112
|
+
const cacheSavingsUsd = price.cacheRead === null
|
|
113
|
+
? 0
|
|
114
|
+
: (Math.max(0, price.input - price.cacheRead) * cacheRead) / 1_000_000
|
|
115
|
+
return { usd, priced: true, cacheSavingsUsd, breakdown }
|
|
113
116
|
}
|
|
114
117
|
|
|
115
118
|
/**
|
package/lib/routes/accounts.js
CHANGED
|
@@ -4,6 +4,7 @@ import { assertTrustedSettingsRequest } from '../access.js'
|
|
|
4
4
|
import { publicConfig, plainConfig, Config } from '../config.js'
|
|
5
5
|
import { resolveActiveAccountKey } from '../provider-sync.js'
|
|
6
6
|
import { resolveAccountPool } from '../account-pool.js'
|
|
7
|
+
import { getAccountEvents } from '../account-events.js'
|
|
7
8
|
import {
|
|
8
9
|
clearUsageCache,
|
|
9
10
|
clearProbeCache,
|
|
@@ -416,6 +417,22 @@ export function registerAccountsRoutes(ctx, { live, syncProviderState, getSettin
|
|
|
416
417
|
},
|
|
417
418
|
}), 'dsh-clinebot: /accounts/usage')
|
|
418
419
|
|
|
420
|
+
// GET /dsh-clinebot/accounts/history — recent cooldown/recovery/route events
|
|
421
|
+
ctx.effect(() => ctx.webServer.register({
|
|
422
|
+
kind: 'exact',
|
|
423
|
+
path: '/dsh-clinebot/accounts/history',
|
|
424
|
+
handler: async (req, res) => {
|
|
425
|
+
if (req.method !== 'GET') return writeJson(res, 405, { ok: false, error: 'GET only' })
|
|
426
|
+
if (!assertTrustedSettingsRequest(req, res, ctx)) return
|
|
427
|
+
try {
|
|
428
|
+
const limit = Number(new URL(req.url, 'http://localhost').searchParams.get('limit')) || 50
|
|
429
|
+
writeJson(res, 200, { ok: true, events: getAccountEvents(limit) })
|
|
430
|
+
} catch (err) {
|
|
431
|
+
writeJson(res, 500, { ok: false, error: String(err?.message || err) })
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
}), 'dsh-clinebot: /accounts/history')
|
|
435
|
+
|
|
419
436
|
// POST /dsh-clinebot/accounts/rename — rename a pooled account
|
|
420
437
|
ctx.effect(() => ctx.webServer.register({
|
|
421
438
|
kind: 'exact',
|
package/lib/session-router.js
CHANGED
|
@@ -14,6 +14,26 @@ const sessions = new Map()
|
|
|
14
14
|
// In-memory cooldowns: accountId -> { until: timestamp, reason: string }
|
|
15
15
|
const cooldowns = new Map()
|
|
16
16
|
|
|
17
|
+
// Optional best-effort sink for account health/rotation history.
|
|
18
|
+
let accountEventSink = null
|
|
19
|
+
|
|
20
|
+
export function setAccountEventSink(sink) {
|
|
21
|
+
accountEventSink = typeof sink === 'function' ? sink : null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function emitAccountEvent(event) {
|
|
25
|
+
if (!accountEventSink) return
|
|
26
|
+
try {
|
|
27
|
+
accountEventSink({ ...event, at: Date.now() })
|
|
28
|
+
} catch {
|
|
29
|
+
/* history must never break routing */
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function accountIdOf(account) {
|
|
34
|
+
return account?.apiKeyEnv || account?.id || ''
|
|
35
|
+
}
|
|
36
|
+
|
|
17
37
|
export const MAX_SESSIONS = 1000
|
|
18
38
|
export const SESSION_TTL_MS = 24 * 60 * 60 * 1000 // 24 hours
|
|
19
39
|
|
|
@@ -62,6 +82,7 @@ export function isAccountInCooldown(accountId) {
|
|
|
62
82
|
|
|
63
83
|
if (Date.now() >= entry.until) {
|
|
64
84
|
cooldowns.delete(accountId)
|
|
85
|
+
emitAccountEvent({ type: 'recover', accountId, reason: entry.reason })
|
|
65
86
|
return false
|
|
66
87
|
}
|
|
67
88
|
return true
|
|
@@ -88,6 +109,7 @@ export function markAccountCooldown(accountId, durationMs = 60000, resetsAt = nu
|
|
|
88
109
|
}
|
|
89
110
|
|
|
90
111
|
cooldowns.set(accountId, { until, reason })
|
|
112
|
+
emitAccountEvent({ type: 'cooldown', accountId, reason, until })
|
|
91
113
|
}
|
|
92
114
|
|
|
93
115
|
/**
|
|
@@ -96,8 +118,9 @@ export function markAccountCooldown(accountId, durationMs = 60000, resetsAt = nu
|
|
|
96
118
|
* @param {string} accountId
|
|
97
119
|
*/
|
|
98
120
|
export function clearAccountCooldown(accountId) {
|
|
99
|
-
if (accountId) {
|
|
121
|
+
if (accountId && cooldowns.has(accountId)) {
|
|
100
122
|
cooldowns.delete(accountId)
|
|
123
|
+
emitAccountEvent({ type: 'recover', accountId, reason: 'manual' })
|
|
101
124
|
}
|
|
102
125
|
}
|
|
103
126
|
|
|
@@ -277,6 +300,14 @@ export function resolveSessionAccount(sessionId, accounts = [], defaultActiveId
|
|
|
277
300
|
assignedAt: existing ? existing.assignedAt : now,
|
|
278
301
|
lastUsedAt: now
|
|
279
302
|
})
|
|
303
|
+
if (!existing || reassigned) {
|
|
304
|
+
emitAccountEvent({
|
|
305
|
+
type: 'route',
|
|
306
|
+
accountId: accountIdOf(pinnedAvailable),
|
|
307
|
+
reason: reassigned ? 'pinned_override' : 'pinned_active',
|
|
308
|
+
sessionId,
|
|
309
|
+
})
|
|
310
|
+
}
|
|
280
311
|
return {
|
|
281
312
|
account: pinnedAvailable,
|
|
282
313
|
isReassigned: reassigned,
|
|
@@ -292,6 +323,9 @@ export function resolveSessionAccount(sessionId, accounts = [], defaultActiveId
|
|
|
292
323
|
assignedAt: existing ? existing.assignedAt : now,
|
|
293
324
|
lastUsedAt: now
|
|
294
325
|
})
|
|
326
|
+
if (!existing || reassigned) {
|
|
327
|
+
emitAccountEvent({ type: 'route', accountId: accountIdOf(routedAccount), reason: 'model_route', sessionId })
|
|
328
|
+
}
|
|
295
329
|
return {
|
|
296
330
|
account: routedAccount,
|
|
297
331
|
isReassigned: reassigned,
|
|
@@ -319,10 +353,12 @@ export function resolveSessionAccount(sessionId, accounts = [], defaultActiveId
|
|
|
319
353
|
})
|
|
320
354
|
}
|
|
321
355
|
|
|
356
|
+
const reason = existing ? 'mid_session_failover' : (pinnedAccount ? 'pinned_in_cooldown_fallback' : (accountMode === 'round_robin' ? 'round_robin_assigned' : 'new_session_assigned'))
|
|
357
|
+
if (chosen) emitAccountEvent({ type: 'route', accountId: accountIdOf(chosen), reason, sessionId })
|
|
322
358
|
return {
|
|
323
359
|
account: chosen,
|
|
324
360
|
isReassigned: Boolean(existing),
|
|
325
|
-
reason
|
|
361
|
+
reason
|
|
326
362
|
}
|
|
327
363
|
}
|
|
328
364
|
|
package/lib/stats-storage.js
CHANGED
|
@@ -47,6 +47,7 @@ function emptyBucket() {
|
|
|
47
47
|
cacheWriteTokens: 0,
|
|
48
48
|
totalTokens: 0,
|
|
49
49
|
costUsd: 0,
|
|
50
|
+
cacheSavingsUsd: 0,
|
|
50
51
|
pricedRequests: 0,
|
|
51
52
|
unpricedRequests: 0,
|
|
52
53
|
ttftMsSum: 0,
|
|
@@ -218,6 +219,7 @@ function addToBucket(bucket, entry) {
|
|
|
218
219
|
bucket.cacheWriteTokens += entry.cacheWriteTokens
|
|
219
220
|
bucket.totalTokens += entry.totalTokens
|
|
220
221
|
bucket.costUsd += entry.costUsd
|
|
222
|
+
bucket.cacheSavingsUsd = Number(bucket.cacheSavingsUsd || 0) + Number(entry.cacheSavingsUsd || 0)
|
|
221
223
|
if (entry.priced) bucket.pricedRequests += 1
|
|
222
224
|
else bucket.unpricedRequests += 1
|
|
223
225
|
bucket.ttftMsSum += entry.ttftMs
|
|
@@ -286,6 +288,7 @@ function backfillHourlyFromSessions() {
|
|
|
286
288
|
hour.cacheWriteTokens += Number(bucket.cacheWriteTokens || 0)
|
|
287
289
|
hour.totalTokens += Number(bucket.totalTokens || 0)
|
|
288
290
|
hour.costUsd += Number(bucket.costUsd || 0)
|
|
291
|
+
hour.cacheSavingsUsd = Number(hour.cacheSavingsUsd || 0) + Number(bucket.cacheSavingsUsd || 0)
|
|
289
292
|
hour.pricedRequests += Number(bucket.pricedRequests || 0)
|
|
290
293
|
hour.unpricedRequests += Number(bucket.unpricedRequests || 0)
|
|
291
294
|
hour.ttftMsSum += Number(bucket.ttftMsSum || 0)
|
|
@@ -299,7 +302,6 @@ function backfillHourlyFromSessions() {
|
|
|
299
302
|
inMemoryStats.byHourModel[key][model] = Number(inMemoryStats.byHourModel[key][model] || 0) + requests
|
|
300
303
|
}
|
|
301
304
|
}
|
|
302
|
-
|
|
303
305
|
/**
|
|
304
306
|
* Record one completion event.
|
|
305
307
|
* @param {object} entry
|
|
@@ -316,6 +318,7 @@ export function recordUsage({
|
|
|
316
318
|
cacheWriteTokens = 0,
|
|
317
319
|
totalTokens = 0,
|
|
318
320
|
costUsd = 0,
|
|
321
|
+
cacheSavingsUsd = 0,
|
|
319
322
|
priced = false,
|
|
320
323
|
ttftMs = 0,
|
|
321
324
|
latencyMs = 0,
|
|
@@ -341,13 +344,13 @@ export function recordUsage({
|
|
|
341
344
|
cacheWriteTokens: Math.max(0, Number(cacheWriteTokens) || 0),
|
|
342
345
|
totalTokens: Math.max(safePrompt + safeComp, Number(totalTokens) || 0),
|
|
343
346
|
costUsd: Math.max(0, Number(costUsd) || 0),
|
|
347
|
+
cacheSavingsUsd: Math.max(0, Number(cacheSavingsUsd) || 0),
|
|
344
348
|
priced: Boolean(priced),
|
|
345
349
|
ttftMs: Math.max(0, Number(ttftMs) || 0),
|
|
346
350
|
latencyMs: Math.max(0, Number(latencyMs) || 0),
|
|
347
351
|
isError: Boolean(isError),
|
|
348
352
|
is429: Boolean(is429),
|
|
349
353
|
}
|
|
350
|
-
|
|
351
354
|
addToBucket(inMemoryStats.totals, entry)
|
|
352
355
|
incrementBucket(inMemoryStats.byDay, dayKey, entry)
|
|
353
356
|
incrementBucket(inMemoryStats.byMonth, monthKey, entry)
|
|
@@ -369,7 +372,6 @@ export function recordUsage({
|
|
|
369
372
|
}
|
|
370
373
|
queueSaveStats(statsPath)
|
|
371
374
|
}
|
|
372
|
-
|
|
373
375
|
export function getStatsSummary() {
|
|
374
376
|
return JSON.parse(JSON.stringify(inMemoryStats))
|
|
375
377
|
}
|
|
@@ -500,6 +502,7 @@ export function getAnalytics(query = {}) {
|
|
|
500
502
|
cacheWriteTokens: Number(bucket.cacheWriteTokens || 0),
|
|
501
503
|
totalTokens: Number(bucket.totalTokens || 0),
|
|
502
504
|
costUsd: Number(bucket.costUsd || 0),
|
|
505
|
+
cacheSavingsUsd: Number(bucket.cacheSavingsUsd || 0),
|
|
503
506
|
pricedRequests: Number(bucket.pricedRequests || 0),
|
|
504
507
|
unpricedRequests: Number(bucket.unpricedRequests || 0),
|
|
505
508
|
successRate: requests > 0 ? (successful / requests) * 100 : 0,
|
|
@@ -531,6 +534,7 @@ export function getAnalytics(query = {}) {
|
|
|
531
534
|
totals.cacheWriteTokens += group.cacheWriteTokens
|
|
532
535
|
totals.totalTokens += group.totalTokens
|
|
533
536
|
totals.costUsd += group.costUsd
|
|
537
|
+
totals.cacheSavingsUsd += group.cacheSavingsUsd
|
|
534
538
|
totals.pricedRequests += group.pricedRequests
|
|
535
539
|
totals.unpricedRequests += group.unpricedRequests
|
|
536
540
|
totals.ttftMsSum += group.ttftMsSum
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-clinebot",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.11",
|
|
4
4
|
"description": "DeepSeek Harness companion for ClineBot / ClinePass: dynamic subscription models sync, quota exhaustion warnings, session metrics, dedicated settings page, live usage limits, and /cline slash-command.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|