@goodandready/dsh-clinebot 0.3.1 → 0.3.3
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 +28 -0
- package/docs/design/DESIGN.md +10 -2
- package/lib/client.js +362 -94
- package/lib/cline-client.js +42 -1
- package/lib/index.js +358 -48
- package/lib/models.js +83 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -14,14 +14,164 @@ window.__ModuleLoader__.load({
|
|
|
14
14
|
title: 'ClineBot',
|
|
15
15
|
subtitle: 'ClinePass subscription ($9.99/mo) with curated & custom models.',
|
|
16
16
|
'settings.loading': 'Loading ClineBot settings…',
|
|
17
|
+
'settings.retry': 'Retry',
|
|
17
18
|
'settings.unavailable': 'ClineBot settings unavailable (host namespace is not ready).',
|
|
19
|
+
'header.title': 'ClineBot (ClinePass) Provider',
|
|
20
|
+
'header.sub': 'Connect models under ClinePass subscription ($9.99/mo). OpenAI API compatible, automated rolling limit tracking, and instant auto-registration in DSH.',
|
|
21
|
+
'badge.online': 'Host online ({latency} ms)',
|
|
22
|
+
'badge.offline': 'Host unreachable',
|
|
23
|
+
'badge.key_ok': 'Key ✓ ({source})',
|
|
24
|
+
'badge.key_missing': 'Key missing',
|
|
25
|
+
'badge.registered': 'DSH Registered ({count} models)',
|
|
26
|
+
'badge.not_registered': 'Not registered',
|
|
27
|
+
'key.title': '🔑 Authorization & API Key',
|
|
28
|
+
'key.desc': 'The key is saved directly to DSH Credentials storage (~/.dsh/.credentials.yaml) and never exposed in plaintext configuration files.',
|
|
29
|
+
'key.placeholder_has': '••••••••••••••••••••••••',
|
|
30
|
+
'key.placeholder_empty': 'Paste ClinePass key (cline_...)',
|
|
31
|
+
'key.show': 'Show',
|
|
32
|
+
'key.hide': 'Hide',
|
|
33
|
+
'key.save': 'Save Key',
|
|
34
|
+
'key.saving': 'Saving…',
|
|
35
|
+
'key.login_fast': '🚀 Quick Web Login',
|
|
36
|
+
'key.login_waiting': 'Waiting for authorization in browser…',
|
|
37
|
+
'key.env_label': 'Credential environment name: ',
|
|
38
|
+
'key.get_key': 'Get key in app.cline.bot console ↗',
|
|
39
|
+
'key.saved_msg': 'Key saved to DSH Credentials. Validation: {status}',
|
|
40
|
+
'key.empty_err': 'Please enter an API key before saving',
|
|
41
|
+
'accounts.title': '👥 Multi-Account Failover Pool',
|
|
42
|
+
'accounts.desc': 'Add extra accounts. When an account hits 429 rate limits, ClineBot switches to the next available account automatically.',
|
|
43
|
+
'accounts.active_badge': 'Active',
|
|
44
|
+
'accounts.pinned_badge': 'Pinned',
|
|
45
|
+
'accounts.pin_btn': 'Pin Active',
|
|
46
|
+
'accounts.add_btn': '+ Add Account',
|
|
47
|
+
'accounts.label_placeholder': 'Account Label (e.g. Work)',
|
|
48
|
+
'accounts.env_placeholder': 'CLINEBOT_API_KEY_2',
|
|
49
|
+
'quota.title': '📊 ClinePass Quota & Rate Limits',
|
|
50
|
+
'quota.desc': 'Official rolling window request limits from ClinePass',
|
|
51
|
+
'quota.account': 'Account: {email} · Plan: {plan}',
|
|
52
|
+
'quota.refresh': 'Refresh Quotas',
|
|
53
|
+
'quota.refreshing': 'Refreshing…',
|
|
54
|
+
'quota.refreshed_msg': 'Subscription quotas refreshed',
|
|
55
|
+
'quota.window_5h': '⏱ 5-Hour Rolling Limit',
|
|
56
|
+
'quota.window_weekly': '📅 Weekly Window',
|
|
57
|
+
'quota.used': '{pct}% used',
|
|
58
|
+
'quota.remaining': 'Remaining: {pct}%',
|
|
59
|
+
'quota.reset_at': 'Reset: {time}',
|
|
60
|
+
'models.title': '🎯 Model Picker in DSH ({enabled} of {total} active)',
|
|
61
|
+
'models.desc': 'Catalog synchronizes directly from ClinePass subscription. New plan models are enabled automatically. Uncheck models to hide them from DSH chat picker.',
|
|
62
|
+
'models.sync': '🔄 Check Plan Models',
|
|
63
|
+
'models.syncing': 'Checking…',
|
|
64
|
+
'models.synced_msg': 'Synchronized subscription models: {total} ({discovered} discovered from ClinePass)',
|
|
65
|
+
'models.all': 'All',
|
|
66
|
+
'models.vision': 'Vision Only',
|
|
67
|
+
'models.coding': 'Coding Only',
|
|
68
|
+
'models.recommended': 'Recommended',
|
|
69
|
+
'models.th_active': 'On',
|
|
70
|
+
'models.th_name': 'Model Name',
|
|
71
|
+
'models.th_id': 'Model ID',
|
|
72
|
+
'models.th_ctx': 'Context',
|
|
73
|
+
'models.th_caps': 'Capabilities',
|
|
74
|
+
'models.star': 'Star',
|
|
75
|
+
'models.auto_new': 'New',
|
|
76
|
+
'stats.title': '📈 Current DSH Session Telemetry',
|
|
77
|
+
'stats.desc': 'Local monitoring of requests, network latency, and estimated token usage via ClinePass.',
|
|
78
|
+
'stats.requests': 'Successful / Total Requests',
|
|
79
|
+
'stats.tokens': 'Estimated Session Tokens',
|
|
80
|
+
'stats.latency': 'Last Request Latency',
|
|
81
|
+
'stats.last_req': 'Last Request Time',
|
|
82
|
+
'diag.title': '⚡ Diagnostics & Auto-Registration',
|
|
83
|
+
'diag.desc': 'ClineBot registers into DSH Models automatically when key is configured. You can run connection smoke test or force re-sync.',
|
|
84
|
+
'diag.smoke_btn': 'Run Smoke Test (Ping)',
|
|
85
|
+
'diag.smoke_testing': 'Testing…',
|
|
86
|
+
'diag.smoke_ok': 'Smoke test passed! Latency: {latency} ms',
|
|
87
|
+
'diag.resync_btn': 'Force Re-sync to DSH',
|
|
88
|
+
'diag.resyncing': 'Syncing…',
|
|
89
|
+
'diag.resynced_msg': 'Provider re-synced to DSH Models ({count} models)',
|
|
90
|
+
'diag.unregister_btn': 'Unregister from DSH',
|
|
91
|
+
'diag.unregistering': 'Removing…',
|
|
92
|
+
'diag.unregistered_msg': 'Provider removed from DSH Models',
|
|
18
93
|
}
|
|
19
94
|
|
|
20
95
|
const ru = {
|
|
21
96
|
title: 'ClineBot',
|
|
22
97
|
subtitle: 'Подписка ClinePass ($9.99/мес) с каталогом проверенных моделей.',
|
|
23
98
|
'settings.loading': 'Загрузка настроек ClineBot…',
|
|
99
|
+
'settings.retry': 'Повторить попытку',
|
|
24
100
|
'settings.unavailable': 'Настройки ClineBot недоступны (пространство хоста ещё не готово).',
|
|
101
|
+
'header.title': 'Провайдер ClineBot (ClinePass)',
|
|
102
|
+
'header.sub': 'Подключение моделей по подписке ClinePass ($9.99/мес). OpenAI API совместимый, автоматическое отслеживание лимитов и моментальная авторегистрация в DSH.',
|
|
103
|
+
'badge.online': 'Сервер доступен ({latency} мс)',
|
|
104
|
+
'badge.offline': 'Недоступен',
|
|
105
|
+
'badge.key_ok': 'Ключ ✓ ({source})',
|
|
106
|
+
'badge.key_missing': 'Ключ отсутствует',
|
|
107
|
+
'badge.registered': 'Зарегистрирован в DSH ({count} мод.)',
|
|
108
|
+
'badge.not_registered': 'Не зарегистрирован',
|
|
109
|
+
'key.title': '🔑 Авторизация и API-ключ',
|
|
110
|
+
'key.desc': 'Ключ сохраняется в системное хранилище DSH Credentials (~/.dsh/.credentials.yaml) и никогда не попадает в открытые файлы настроек.',
|
|
111
|
+
'key.placeholder_has': '••••••••••••••••••••••••',
|
|
112
|
+
'key.placeholder_empty': 'Вставьте ключ ClinePass (cline_...)',
|
|
113
|
+
'key.show': 'Показать',
|
|
114
|
+
'key.hide': 'Скрыть',
|
|
115
|
+
'key.save': 'Сохранить ключ',
|
|
116
|
+
'key.saving': 'Сохранение…',
|
|
117
|
+
'key.login_fast': '🚀 Быстрый вход через браузер',
|
|
118
|
+
'key.login_waiting': 'Ожидание авторизации в браузере…',
|
|
119
|
+
'key.env_label': 'Переменная учётных данных: ',
|
|
120
|
+
'key.get_key': 'Получить ключ в консоли app.cline.bot ↗',
|
|
121
|
+
'key.saved_msg': 'Ключ сохранён в DSH Credentials. Валидация: {status}',
|
|
122
|
+
'key.empty_err': 'Введите API-ключ перед сохранением',
|
|
123
|
+
'accounts.title': '👥 Пул аккаунтов и авторотация (Failover)',
|
|
124
|
+
'accounts.desc': 'Добавьте запасные аккаунты. При исчерпании лимитов (429) ClineBot автоматически переключит запрос на следующий доступный аккаунт.',
|
|
125
|
+
'accounts.active_badge': 'Активен',
|
|
126
|
+
'accounts.pinned_badge': 'Закреплен',
|
|
127
|
+
'accounts.pin_btn': 'Сделать активным',
|
|
128
|
+
'accounts.add_btn': '+ Добавить аккаунт',
|
|
129
|
+
'accounts.label_placeholder': 'Название (например, Work)',
|
|
130
|
+
'accounts.env_placeholder': 'CLINEBOT_API_KEY_2',
|
|
131
|
+
'quota.title': '📊 Остаток лимитов подписки (ClinePass Quota)',
|
|
132
|
+
'quota.desc': 'Официальные лимиты скользящих окон запросов ClinePass',
|
|
133
|
+
'quota.account': 'Аккаунт: {email} · Тариф: {plan}',
|
|
134
|
+
'quota.refresh': 'Обновить квоты',
|
|
135
|
+
'quota.refreshing': 'Обновление…',
|
|
136
|
+
'quota.refreshed_msg': 'Лимиты подписки обновлены',
|
|
137
|
+
'quota.window_5h': '⏱ 5-часовое скользящее окно (Rolling Limit)',
|
|
138
|
+
'quota.window_weekly': '📅 Недельное окно (Weekly Window)',
|
|
139
|
+
'quota.used': '{pct}% использовано',
|
|
140
|
+
'quota.remaining': 'Осталось: {pct}%',
|
|
141
|
+
'quota.reset_at': 'Сброс: {time}',
|
|
142
|
+
'models.title': '🎯 Модели в пикере DSH ({enabled} из {total} включено)',
|
|
143
|
+
'models.desc': 'Список синхронизируется напрямую из подписки ClinePass. Новые модели появляются автоматически. Снимите галочку, чтобы скрыть ненужную модель из чата.',
|
|
144
|
+
'models.sync': '🔄 Проверить модели плана',
|
|
145
|
+
'models.syncing': 'Проверка…',
|
|
146
|
+
'models.synced_msg': 'Синхронизировано моделей подписки: {total} ({discovered} получено напрямую из плана)',
|
|
147
|
+
'models.all': 'Все',
|
|
148
|
+
'models.vision': 'Только Vision',
|
|
149
|
+
'models.coding': 'Только Кодинг',
|
|
150
|
+
'models.recommended': 'Рекомендованные',
|
|
151
|
+
'models.th_active': 'Вкл',
|
|
152
|
+
'models.th_name': 'Название модели',
|
|
153
|
+
'models.th_id': 'Model ID',
|
|
154
|
+
'models.th_ctx': 'Контекст',
|
|
155
|
+
'models.th_caps': 'Возможности',
|
|
156
|
+
'models.star': 'Star',
|
|
157
|
+
'models.auto_new': 'Новая',
|
|
158
|
+
'stats.title': '📈 Статистика текущей сессии DSH',
|
|
159
|
+
'stats.desc': 'Локальный мониторинг запросов, задержек сети и ориентировочного расхода токенов через ClinePass.',
|
|
160
|
+
'stats.requests': 'Успешных запросов / Всего',
|
|
161
|
+
'stats.tokens': 'Оценка токенов сессии',
|
|
162
|
+
'stats.latency': 'Последняя задержка (Latency)',
|
|
163
|
+
'stats.last_req': 'Время последнего запроса',
|
|
164
|
+
'diag.title': '⚡ Диагностика и авторегистрация в DSH',
|
|
165
|
+
'diag.desc': 'ClineBot автоматически регистрируется в DSH Models при наличии ключа. Вы можете запустить проверку связи или принудительно пересинхронизировать модели.',
|
|
166
|
+
'diag.smoke_btn': 'Запустить Smoke Test (Ping)',
|
|
167
|
+
'diag.smoke_testing': 'Тестирование…',
|
|
168
|
+
'diag.smoke_ok': 'Smoke тест пройден! Задержка: {latency} мс',
|
|
169
|
+
'diag.resync_btn': 'Принудительно обновить в DSH',
|
|
170
|
+
'diag.resyncing': 'Обновление…',
|
|
171
|
+
'diag.resynced_msg': 'Провайдер обновлен в DSH Models ({count} моделей)',
|
|
172
|
+
'diag.unregister_btn': 'Удалить из DSH Models',
|
|
173
|
+
'diag.unregistering': 'Удаление…',
|
|
174
|
+
'diag.unregistered_msg': 'Провайдер удален из DSH Models',
|
|
25
175
|
}
|
|
26
176
|
|
|
27
177
|
function makeT(dict, fallback) {
|
|
@@ -85,7 +235,7 @@ window.__ModuleLoader__.load({
|
|
|
85
235
|
.cb-btn-primary:hover:not(:disabled){background:var(--dsw-alias-label-primary) !important;color:var(--dsw-alias-bg-layer-3) !important;opacity:0.88;visibility:visible !important}
|
|
86
236
|
.cb-btn-danger{color:var(--dsw-alias-state-error-primary);border-color:rgba(239,68,68,0.3)}
|
|
87
237
|
.cb-btn-danger:hover:not(:disabled){background:rgba(239,68,68,0.12) !important;border-color:rgba(239,68,68,0.5)}
|
|
88
|
-
.cb-btn
|
|
238
|
+
.cb-btn-disabled{opacity:0.5;cursor:not-allowed}
|
|
89
239
|
|
|
90
240
|
.cb-bar-container{display:flex;flex-direction:column;gap:6px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2)}
|
|
91
241
|
.cb-bar-head{display:flex;justify-content:space-between;font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary)}
|
|
@@ -111,7 +261,7 @@ window.__ModuleLoader__.load({
|
|
|
111
261
|
document.head.appendChild(style)
|
|
112
262
|
}
|
|
113
263
|
|
|
114
|
-
function ProgressBar({ label, percentUsed, remainingPercent, resetsAt }) {
|
|
264
|
+
function ProgressBar({ label, percentUsed, remainingPercent, resetsAt, t }) {
|
|
115
265
|
const used = Math.max(0, Math.min(100, percentUsed || 0))
|
|
116
266
|
let fillColor = 'var(--dsw-alias-state-success-primary)'
|
|
117
267
|
if (used > 75) fillColor = 'var(--dsw-alias-state-warning-primary)'
|
|
@@ -126,7 +276,7 @@ window.__ModuleLoader__.load({
|
|
|
126
276
|
'div',
|
|
127
277
|
{ className: 'cb-bar-head' },
|
|
128
278
|
React.createElement('span', null, label),
|
|
129
|
-
React.createElement('span', { style: { fontWeight: 600 } },
|
|
279
|
+
React.createElement('span', { style: { fontWeight: 600 } }, t('quota.used', { pct: used }))
|
|
130
280
|
),
|
|
131
281
|
React.createElement(
|
|
132
282
|
'div',
|
|
@@ -139,8 +289,8 @@ window.__ModuleLoader__.load({
|
|
|
139
289
|
React.createElement(
|
|
140
290
|
'div',
|
|
141
291
|
{ className: 'cb-bar-meta' },
|
|
142
|
-
React.createElement('span', null,
|
|
143
|
-
React.createElement('span', null,
|
|
292
|
+
React.createElement('span', null, t('quota.remaining', { pct: remainingPercent ?? (100 - used) })),
|
|
293
|
+
React.createElement('span', null, t('quota.reset_at', { time: resetStr }))
|
|
144
294
|
)
|
|
145
295
|
)
|
|
146
296
|
}
|
|
@@ -192,7 +342,7 @@ window.__ModuleLoader__.load({
|
|
|
192
342
|
// Save Key handler
|
|
193
343
|
async function handleSaveKey() {
|
|
194
344
|
if (!apiKeyInput.trim()) {
|
|
195
|
-
setErr('
|
|
345
|
+
setErr(t('key.empty_err'))
|
|
196
346
|
return
|
|
197
347
|
}
|
|
198
348
|
setBusy('save-key')
|
|
@@ -209,7 +359,7 @@ window.__ModuleLoader__.load({
|
|
|
209
359
|
})
|
|
210
360
|
const data = await res.json().catch(() => ({}))
|
|
211
361
|
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`)
|
|
212
|
-
setMsg(
|
|
362
|
+
setMsg(t('key.saved_msg', { status: data.validated ? 'OK' : 'Error' }))
|
|
213
363
|
setApiKeyInput('')
|
|
214
364
|
await load()
|
|
215
365
|
} catch (e) {
|
|
@@ -229,7 +379,7 @@ window.__ModuleLoader__.load({
|
|
|
229
379
|
const data = await res.json().catch(() => ({}))
|
|
230
380
|
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`)
|
|
231
381
|
setStatus((prev) => ({ ...prev, usage: data }))
|
|
232
|
-
setMsg('
|
|
382
|
+
setMsg(t('quota.refreshed_msg'))
|
|
233
383
|
} catch (e) {
|
|
234
384
|
setErr(String(e.message || e))
|
|
235
385
|
} finally {
|
|
@@ -246,7 +396,7 @@ window.__ModuleLoader__.load({
|
|
|
246
396
|
const res = await fetch(`${ROUTE_PREFIX}/models/sync`, { method: 'POST' })
|
|
247
397
|
const data = await res.json().catch(() => ({}))
|
|
248
398
|
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`)
|
|
249
|
-
setMsg(
|
|
399
|
+
setMsg(t('models.synced_msg', { total: data.totalModelsCount, discovered: data.discoveredCount }))
|
|
250
400
|
await load()
|
|
251
401
|
} catch (e) {
|
|
252
402
|
setErr(String(e.message || e))
|
|
@@ -255,7 +405,7 @@ window.__ModuleLoader__.load({
|
|
|
255
405
|
}
|
|
256
406
|
}
|
|
257
407
|
|
|
258
|
-
// Register/
|
|
408
|
+
// Register/Force Re-sync Provider in DSH
|
|
259
409
|
async function handleRegister() {
|
|
260
410
|
setBusy('register')
|
|
261
411
|
setErr('')
|
|
@@ -268,7 +418,7 @@ window.__ModuleLoader__.load({
|
|
|
268
418
|
})
|
|
269
419
|
const data = await res.json().catch(() => ({}))
|
|
270
420
|
if (!data.ok) throw new Error(data.error || `HTTP ${res.status}`)
|
|
271
|
-
setMsg(
|
|
421
|
+
setMsg(t('diag.resynced_msg', { count: draft.enabledModels?.length || 0 }))
|
|
272
422
|
await load()
|
|
273
423
|
} catch (e) {
|
|
274
424
|
setErr(String(e.message || e))
|
|
@@ -286,7 +436,7 @@ window.__ModuleLoader__.load({
|
|
|
286
436
|
const res = await fetch(`${ROUTE_PREFIX}/unregister`, { method: 'POST' })
|
|
287
437
|
const data = await res.json().catch(() => ({}))
|
|
288
438
|
if (!data.ok) throw new Error(data.error || `HTTP ${res.status}`)
|
|
289
|
-
setMsg('
|
|
439
|
+
setMsg(t('diag.unregistered_msg'))
|
|
290
440
|
await load()
|
|
291
441
|
} catch (e) {
|
|
292
442
|
setErr(String(e.message || e))
|
|
@@ -295,22 +445,37 @@ window.__ModuleLoader__.load({
|
|
|
295
445
|
}
|
|
296
446
|
}
|
|
297
447
|
|
|
298
|
-
//
|
|
299
|
-
async function
|
|
300
|
-
setBusy('
|
|
448
|
+
// Fast Browser Login
|
|
449
|
+
async function handleFastLogin() {
|
|
450
|
+
setBusy('fast-login')
|
|
301
451
|
setErr('')
|
|
302
|
-
setMsg('')
|
|
303
|
-
setSmokeResult(null)
|
|
452
|
+
setMsg(t('key.login_waiting'))
|
|
304
453
|
try {
|
|
305
|
-
const res = await fetch(`${ROUTE_PREFIX}/
|
|
454
|
+
const res = await fetch(`${ROUTE_PREFIX}/auth/begin`, { method: 'POST' })
|
|
455
|
+
const data = await res.json().catch(() => ({}))
|
|
456
|
+
if (!data.ok) throw new Error(data.error || 'Failed to initiate login')
|
|
457
|
+
if (data.authUrl) {
|
|
458
|
+
window.open(data.authUrl, '_blank')
|
|
459
|
+
}
|
|
460
|
+
} catch (e) {
|
|
461
|
+
setErr(String(e.message || e))
|
|
462
|
+
} finally {
|
|
463
|
+
setBusy('')
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Pin active account
|
|
468
|
+
async function handlePinAccount(accountEnv) {
|
|
469
|
+
setBusy(`pin-${accountEnv}`)
|
|
470
|
+
setErr('')
|
|
471
|
+
try {
|
|
472
|
+
const res = await fetch(`${ROUTE_PREFIX}/accounts/active`, {
|
|
306
473
|
method: 'POST',
|
|
307
474
|
headers: { 'Content-Type': 'application/json' },
|
|
308
|
-
body: JSON.stringify({
|
|
475
|
+
body: JSON.stringify({ account: accountEnv }),
|
|
309
476
|
})
|
|
310
477
|
const data = await res.json().catch(() => ({}))
|
|
311
|
-
if (!data.ok) throw new Error(data.error ||
|
|
312
|
-
setSmokeResult(data)
|
|
313
|
-
setMsg(`Smoke тест пройден! Задержка: ${data.latencyMs} мс`)
|
|
478
|
+
if (!data.ok) throw new Error(data.error || 'Failed to switch account')
|
|
314
479
|
await load()
|
|
315
480
|
} catch (e) {
|
|
316
481
|
setErr(String(e.message || e))
|
|
@@ -319,26 +484,31 @@ window.__ModuleLoader__.load({
|
|
|
319
484
|
}
|
|
320
485
|
}
|
|
321
486
|
|
|
322
|
-
// Toggle model
|
|
487
|
+
// Toggle model exclusion (disabledModels logic)
|
|
323
488
|
async function handleToggleModel(id) {
|
|
324
489
|
if (!draft) return
|
|
325
|
-
const
|
|
326
|
-
if (
|
|
327
|
-
|
|
490
|
+
const currentDisabled = new Set(draft.disabledModels || [])
|
|
491
|
+
if (currentDisabled.has(id)) {
|
|
492
|
+
currentDisabled.delete(id) // Re-enable
|
|
328
493
|
} else {
|
|
329
|
-
|
|
494
|
+
currentDisabled.add(id) // Disable
|
|
330
495
|
}
|
|
331
|
-
const
|
|
332
|
-
|
|
496
|
+
const nextDisabled = Array.from(currentDisabled)
|
|
497
|
+
|
|
498
|
+
// Compute next enabled models
|
|
499
|
+
const allIds = (status?.availableModels || []).map((m) => m.id)
|
|
500
|
+
const nextEnabled = allIds.filter((mId) => !currentDisabled.has(mId))
|
|
501
|
+
|
|
502
|
+
setDraft({ ...draft, disabledModels: nextDisabled, enabledModels: nextEnabled })
|
|
333
503
|
|
|
334
504
|
if (scope && snapshotStatus === 'ready') {
|
|
335
|
-
try { await scope.set('
|
|
505
|
+
try { await scope.set('disabledModels', nextDisabled) } catch (_) {}
|
|
336
506
|
}
|
|
337
507
|
try {
|
|
338
508
|
await fetch(`${ROUTE_PREFIX}/models/toggle`, {
|
|
339
509
|
method: 'POST',
|
|
340
510
|
headers: { 'Content-Type': 'application/json' },
|
|
341
|
-
body: JSON.stringify({
|
|
511
|
+
body: JSON.stringify({ disabledModels: nextDisabled }),
|
|
342
512
|
})
|
|
343
513
|
} catch {}
|
|
344
514
|
}
|
|
@@ -346,26 +516,30 @@ window.__ModuleLoader__.load({
|
|
|
346
516
|
// Select all / filter models
|
|
347
517
|
async function handleSetModelsFilter(type) {
|
|
348
518
|
if (!status?.availableModels) return
|
|
349
|
-
|
|
519
|
+
const all = status.availableModels
|
|
520
|
+
let allowed = new Set()
|
|
350
521
|
if (type === 'all') {
|
|
351
|
-
|
|
522
|
+
allowed = new Set(all.map((m) => m.id))
|
|
352
523
|
} else if (type === 'vision') {
|
|
353
|
-
|
|
524
|
+
allowed = new Set(all.filter((m) => m.input?.includes('image') || m.input?.includes('vision')).map((m) => m.id))
|
|
354
525
|
} else if (type === 'coding') {
|
|
355
|
-
|
|
526
|
+
allowed = new Set(all.filter((m) => m.category === 'coding').map((m) => m.id))
|
|
356
527
|
} else if (type === 'recommended') {
|
|
357
|
-
|
|
528
|
+
allowed = new Set(all.filter((m) => m.recommended).map((m) => m.id))
|
|
358
529
|
}
|
|
359
530
|
|
|
360
|
-
|
|
531
|
+
const nextDisabled = all.map((m) => m.id).filter((id) => !allowed.has(id))
|
|
532
|
+
const nextEnabled = Array.from(allowed)
|
|
533
|
+
|
|
534
|
+
setDraft({ ...draft, disabledModels: nextDisabled, enabledModels: nextEnabled })
|
|
361
535
|
if (scope && snapshotStatus === 'ready') {
|
|
362
|
-
try { await scope.set('
|
|
536
|
+
try { await scope.set('disabledModels', nextDisabled) } catch (_) {}
|
|
363
537
|
}
|
|
364
538
|
try {
|
|
365
539
|
await fetch(`${ROUTE_PREFIX}/models/toggle`, {
|
|
366
540
|
method: 'POST',
|
|
367
541
|
headers: { 'Content-Type': 'application/json' },
|
|
368
|
-
body: JSON.stringify({
|
|
542
|
+
body: JSON.stringify({ disabledModels: nextDisabled }),
|
|
369
543
|
})
|
|
370
544
|
} catch {}
|
|
371
545
|
}
|
|
@@ -383,30 +557,31 @@ window.__ModuleLoader__.load({
|
|
|
383
557
|
return React.createElement(
|
|
384
558
|
'div',
|
|
385
559
|
{ className: 'cb-page' },
|
|
386
|
-
React.createElement('div', { className: 'cb-alert cb-alert-err' },
|
|
560
|
+
React.createElement('div', { className: 'cb-alert cb-alert-err' }, `${t('settings.loading')}: ${err}`),
|
|
387
561
|
React.createElement(
|
|
388
562
|
'button',
|
|
389
563
|
{
|
|
390
564
|
type: 'button',
|
|
391
|
-
className: 'cb-btn
|
|
565
|
+
className: 'cb-btn',
|
|
392
566
|
style: { marginTop: '12px', alignSelf: 'flex-start' },
|
|
393
567
|
onClick: () => {
|
|
394
568
|
setErr('')
|
|
395
569
|
load().catch((e) => setErr(String(e.message || e)))
|
|
396
570
|
},
|
|
397
571
|
},
|
|
398
|
-
'
|
|
572
|
+
t('settings.retry')
|
|
399
573
|
)
|
|
400
574
|
)
|
|
401
575
|
}
|
|
402
|
-
return React.createElement('div', { className: 'cb-page' }, '
|
|
576
|
+
return React.createElement('div', { className: 'cb-page' }, t('settings.loading'))
|
|
403
577
|
}
|
|
404
578
|
|
|
405
579
|
const healthOk = !!status.health?.ok
|
|
406
580
|
const keyPresent = !!status.key?.present
|
|
407
581
|
const isRegistered = !!status.isRegistered
|
|
408
582
|
const modelsList = status.availableModels || []
|
|
409
|
-
const
|
|
583
|
+
const disabledSet = new Set(draft.disabledModels || [])
|
|
584
|
+
const enabledCount = modelsList.filter((m) => !disabledSet.has(m.id)).length
|
|
410
585
|
const usage = status.usage
|
|
411
586
|
|
|
412
587
|
return React.createElement(
|
|
@@ -420,27 +595,27 @@ window.__ModuleLoader__.load({
|
|
|
420
595
|
React.createElement(
|
|
421
596
|
'div',
|
|
422
597
|
{ className: 'cb-page-title' },
|
|
423
|
-
|
|
598
|
+
`🤖 ${t('header.title')}`,
|
|
424
599
|
React.createElement(
|
|
425
600
|
'span',
|
|
426
601
|
{ className: `cb-badge ${healthOk ? 'cb-badge-ok' : 'cb-badge-bad'}` },
|
|
427
|
-
healthOk ?
|
|
602
|
+
healthOk ? t('badge.online', { latency: status.health?.latencyMs }) : t('badge.offline')
|
|
428
603
|
),
|
|
429
604
|
React.createElement(
|
|
430
605
|
'span',
|
|
431
606
|
{ className: `cb-badge ${keyPresent ? 'cb-badge-ok' : 'cb-badge-warn'}` },
|
|
432
|
-
keyPresent ?
|
|
607
|
+
keyPresent ? t('badge.key_ok', { source: status.key?.source }) : t('badge.key_missing')
|
|
433
608
|
),
|
|
434
609
|
React.createElement(
|
|
435
610
|
'span',
|
|
436
611
|
{ className: `cb-badge ${isRegistered ? 'cb-badge-ok' : 'cb-badge-warn'}` },
|
|
437
|
-
isRegistered ? '
|
|
612
|
+
isRegistered ? t('badge.registered', { count: enabledCount }) : t('badge.not_registered')
|
|
438
613
|
)
|
|
439
614
|
),
|
|
440
615
|
React.createElement(
|
|
441
616
|
'div',
|
|
442
617
|
{ className: 'cb-page-sub' },
|
|
443
|
-
'
|
|
618
|
+
t('header.sub')
|
|
444
619
|
)
|
|
445
620
|
),
|
|
446
621
|
|
|
@@ -455,12 +630,12 @@ window.__ModuleLoader__.load({
|
|
|
455
630
|
React.createElement(
|
|
456
631
|
'div',
|
|
457
632
|
{ className: 'cb-section-title' },
|
|
458
|
-
'
|
|
633
|
+
t('key.title')
|
|
459
634
|
),
|
|
460
635
|
React.createElement(
|
|
461
636
|
'div',
|
|
462
637
|
{ className: 'cb-section-desc' },
|
|
463
|
-
'
|
|
638
|
+
t('key.desc')
|
|
464
639
|
),
|
|
465
640
|
React.createElement(
|
|
466
641
|
'div',
|
|
@@ -468,7 +643,7 @@ window.__ModuleLoader__.load({
|
|
|
468
643
|
React.createElement('input', {
|
|
469
644
|
className: 'cb-input',
|
|
470
645
|
type: showKey ? 'text' : 'password',
|
|
471
|
-
placeholder: keyPresent ? '
|
|
646
|
+
placeholder: keyPresent ? t('key.placeholder_has') : t('key.placeholder_empty'),
|
|
472
647
|
value: apiKeyInput,
|
|
473
648
|
onChange: (e) => setApiKeyInput(e.target.value),
|
|
474
649
|
}),
|
|
@@ -479,7 +654,7 @@ window.__ModuleLoader__.load({
|
|
|
479
654
|
className: 'cb-btn',
|
|
480
655
|
onClick: () => setShowKey((v) => !v),
|
|
481
656
|
},
|
|
482
|
-
showKey ? '
|
|
657
|
+
showKey ? t('key.hide') : t('key.show')
|
|
483
658
|
),
|
|
484
659
|
React.createElement(
|
|
485
660
|
'button',
|
|
@@ -489,13 +664,23 @@ window.__ModuleLoader__.load({
|
|
|
489
664
|
disabled: !!busy || !apiKeyInput.trim(),
|
|
490
665
|
onClick: handleSaveKey,
|
|
491
666
|
},
|
|
492
|
-
busy === 'save-key' ? '
|
|
667
|
+
busy === 'save-key' ? t('key.saving') : t('key.save')
|
|
668
|
+
),
|
|
669
|
+
React.createElement(
|
|
670
|
+
'button',
|
|
671
|
+
{
|
|
672
|
+
type: 'button',
|
|
673
|
+
className: 'cb-btn',
|
|
674
|
+
disabled: !!busy,
|
|
675
|
+
onClick: handleFastLogin,
|
|
676
|
+
},
|
|
677
|
+
busy === 'fast-login' ? '…' : t('key.login_fast')
|
|
493
678
|
)
|
|
494
679
|
),
|
|
495
680
|
React.createElement(
|
|
496
681
|
'div',
|
|
497
682
|
{ className: 'cb-row', style: { fontSize: '13px', color: 'var(--dsw-alias-label-secondary)' } },
|
|
498
|
-
React.createElement('span', null,
|
|
683
|
+
React.createElement('span', null, t('key.env_label')),
|
|
499
684
|
React.createElement('code', null, draft.apiKeyEnv || 'CLINEBOT_API_KEY'),
|
|
500
685
|
React.createElement('span', null, ` · `),
|
|
501
686
|
React.createElement(
|
|
@@ -506,11 +691,85 @@ window.__ModuleLoader__.load({
|
|
|
506
691
|
rel: 'noreferrer',
|
|
507
692
|
style: { color: 'var(--dsw-alias-state-brand-primary)' },
|
|
508
693
|
},
|
|
509
|
-
'
|
|
694
|
+
t('key.get_key')
|
|
510
695
|
)
|
|
511
696
|
)
|
|
512
697
|
),
|
|
513
698
|
|
|
699
|
+
// Accounts Pool Card
|
|
700
|
+
status.accounts && status.accounts.length > 0
|
|
701
|
+
? React.createElement(
|
|
702
|
+
'div',
|
|
703
|
+
{ className: 'cb-section-card' },
|
|
704
|
+
React.createElement(
|
|
705
|
+
'div',
|
|
706
|
+
{ className: 'cb-section-title' },
|
|
707
|
+
t('accounts.title')
|
|
708
|
+
),
|
|
709
|
+
React.createElement(
|
|
710
|
+
'div',
|
|
711
|
+
{ className: 'cb-section-desc' },
|
|
712
|
+
t('accounts.desc')
|
|
713
|
+
),
|
|
714
|
+
React.createElement(
|
|
715
|
+
'table',
|
|
716
|
+
{ className: 'cb-table' },
|
|
717
|
+
React.createElement(
|
|
718
|
+
'thead',
|
|
719
|
+
null,
|
|
720
|
+
React.createElement(
|
|
721
|
+
'tr',
|
|
722
|
+
null,
|
|
723
|
+
React.createElement('th', null, 'Account'),
|
|
724
|
+
React.createElement('th', null, 'Credential Ref'),
|
|
725
|
+
React.createElement('th', null, 'Status'),
|
|
726
|
+
React.createElement('th', { style: { textAlign: 'right' } }, 'Action')
|
|
727
|
+
)
|
|
728
|
+
),
|
|
729
|
+
React.createElement(
|
|
730
|
+
'tbody',
|
|
731
|
+
null,
|
|
732
|
+
status.accounts.map((acc) => {
|
|
733
|
+
const isActive = status.activeAccount === acc.apiKeyEnv || (!status.activeAccount && acc.id === 'default')
|
|
734
|
+
return React.createElement(
|
|
735
|
+
'tr',
|
|
736
|
+
{ key: acc.id },
|
|
737
|
+
React.createElement('td', null, React.createElement('strong', null, acc.label)),
|
|
738
|
+
React.createElement('td', null, React.createElement('code', null, acc.apiKeyEnv)),
|
|
739
|
+
React.createElement(
|
|
740
|
+
'td',
|
|
741
|
+
null,
|
|
742
|
+
acc.present
|
|
743
|
+
? React.createElement('span', { className: 'cb-badge cb-badge-ok' }, `Configured (${acc.source})`)
|
|
744
|
+
: React.createElement('span', { className: 'cb-badge cb-badge-warn' }, 'Missing Key'),
|
|
745
|
+
isActive
|
|
746
|
+
? React.createElement('span', { className: 'cb-badge cb-badge-ok', style: { marginLeft: '6px' } }, t('accounts.active_badge'))
|
|
747
|
+
: null
|
|
748
|
+
),
|
|
749
|
+
React.createElement(
|
|
750
|
+
'td',
|
|
751
|
+
{ style: { textAlign: 'right' } },
|
|
752
|
+
!isActive && acc.present
|
|
753
|
+
? React.createElement(
|
|
754
|
+
'button',
|
|
755
|
+
{
|
|
756
|
+
type: 'button',
|
|
757
|
+
className: 'cb-btn',
|
|
758
|
+
style: { padding: '4px 8px', fontSize: '11px' },
|
|
759
|
+
disabled: !!busy,
|
|
760
|
+
onClick: () => handlePinAccount(acc.apiKeyEnv),
|
|
761
|
+
},
|
|
762
|
+
t('accounts.pin_btn')
|
|
763
|
+
)
|
|
764
|
+
: null
|
|
765
|
+
)
|
|
766
|
+
)
|
|
767
|
+
})
|
|
768
|
+
)
|
|
769
|
+
)
|
|
770
|
+
)
|
|
771
|
+
: null,
|
|
772
|
+
|
|
514
773
|
// Quota Warning Banner
|
|
515
774
|
status.quotaWarning
|
|
516
775
|
? React.createElement(
|
|
@@ -525,7 +784,7 @@ window.__ModuleLoader__.load({
|
|
|
525
784
|
? React.createElement(
|
|
526
785
|
'div',
|
|
527
786
|
{ style: { fontSize: '11px', opacity: 0.9, marginTop: '2px' } },
|
|
528
|
-
|
|
787
|
+
t('quota.reset_at', { time: new Date(status.quotaWarning.resetsAt).toLocaleTimeString() })
|
|
529
788
|
)
|
|
530
789
|
: null
|
|
531
790
|
)
|
|
@@ -540,7 +799,7 @@ window.__ModuleLoader__.load({
|
|
|
540
799
|
React.createElement(
|
|
541
800
|
'div',
|
|
542
801
|
{ className: 'cb-section-title' },
|
|
543
|
-
'
|
|
802
|
+
t('quota.title'),
|
|
544
803
|
React.createElement(
|
|
545
804
|
'button',
|
|
546
805
|
{
|
|
@@ -549,43 +808,45 @@ window.__ModuleLoader__.load({
|
|
|
549
808
|
disabled: !!busy,
|
|
550
809
|
onClick: handleRefreshQuota,
|
|
551
810
|
},
|
|
552
|
-
busy === 'refresh-quota' ? '
|
|
811
|
+
busy === 'refresh-quota' ? t('quota.refreshing') : t('quota.refresh')
|
|
553
812
|
)
|
|
554
813
|
),
|
|
555
814
|
React.createElement(
|
|
556
815
|
'div',
|
|
557
816
|
{ className: 'cb-section-desc' },
|
|
558
817
|
usage?.user?.email
|
|
559
|
-
?
|
|
560
|
-
: '
|
|
818
|
+
? t('quota.account', { email: usage.user.email, plan: usage.plan || 'ClinePass ($9.99/mo)' })
|
|
819
|
+
: t('quota.desc')
|
|
561
820
|
),
|
|
562
821
|
React.createElement(
|
|
563
822
|
'div',
|
|
564
823
|
{ className: 'cb-grid-2' },
|
|
565
824
|
React.createElement(ProgressBar, {
|
|
566
|
-
label: '
|
|
825
|
+
label: t('quota.window_5h'),
|
|
567
826
|
percentUsed: usage?.windows?.fiveHour?.percentUsed || 0,
|
|
568
827
|
remainingPercent: usage?.windows?.fiveHour?.remainingPercent || 100,
|
|
569
828
|
resetsAt: usage?.windows?.fiveHour?.resetsAt,
|
|
829
|
+
t,
|
|
570
830
|
}),
|
|
571
831
|
React.createElement(ProgressBar, {
|
|
572
|
-
label: '
|
|
832
|
+
label: t('quota.window_weekly'),
|
|
573
833
|
percentUsed: usage?.windows?.weekly?.percentUsed || 0,
|
|
574
834
|
remainingPercent: usage?.windows?.weekly?.remainingPercent || 100,
|
|
575
835
|
resetsAt: usage?.windows?.weekly?.resetsAt,
|
|
836
|
+
t,
|
|
576
837
|
})
|
|
577
838
|
)
|
|
578
839
|
)
|
|
579
840
|
: null,
|
|
580
841
|
|
|
581
|
-
// Card 3: Model Picker Management with Official Plan Sync
|
|
842
|
+
// Card 3: Model Picker Management with Official Plan Sync & Auto-Enable
|
|
582
843
|
React.createElement(
|
|
583
844
|
'div',
|
|
584
845
|
{ className: 'cb-section-card' },
|
|
585
846
|
React.createElement(
|
|
586
847
|
'div',
|
|
587
848
|
{ className: 'cb-section-title' },
|
|
588
|
-
|
|
849
|
+
t('models.title', { enabled: enabledCount, total: modelsList.length }),
|
|
589
850
|
React.createElement(
|
|
590
851
|
'div',
|
|
591
852
|
{ className: 'cb-row' },
|
|
@@ -597,34 +858,34 @@ window.__ModuleLoader__.load({
|
|
|
597
858
|
disabled: !!busy || !keyPresent,
|
|
598
859
|
onClick: handleSyncPlanModels,
|
|
599
860
|
},
|
|
600
|
-
busy === 'sync-models' ? '
|
|
861
|
+
busy === 'sync-models' ? t('models.syncing') : t('models.sync')
|
|
601
862
|
),
|
|
602
863
|
React.createElement(
|
|
603
864
|
'button',
|
|
604
865
|
{ type: 'button', className: 'cb-btn', onClick: () => handleSetModelsFilter('all') },
|
|
605
|
-
'
|
|
866
|
+
t('models.all')
|
|
606
867
|
),
|
|
607
868
|
React.createElement(
|
|
608
869
|
'button',
|
|
609
870
|
{ type: 'button', className: 'cb-btn', onClick: () => handleSetModelsFilter('vision') },
|
|
610
|
-
'
|
|
871
|
+
t('models.vision')
|
|
611
872
|
),
|
|
612
873
|
React.createElement(
|
|
613
874
|
'button',
|
|
614
875
|
{ type: 'button', className: 'cb-btn', onClick: () => handleSetModelsFilter('coding') },
|
|
615
|
-
'
|
|
876
|
+
t('models.coding')
|
|
616
877
|
),
|
|
617
878
|
React.createElement(
|
|
618
879
|
'button',
|
|
619
880
|
{ type: 'button', className: 'cb-btn', onClick: () => handleSetModelsFilter('recommended') },
|
|
620
|
-
'
|
|
881
|
+
t('models.recommended')
|
|
621
882
|
)
|
|
622
883
|
)
|
|
623
884
|
),
|
|
624
885
|
React.createElement(
|
|
625
886
|
'div',
|
|
626
887
|
{ className: 'cb-section-desc' },
|
|
627
|
-
'
|
|
888
|
+
t('models.desc')
|
|
628
889
|
),
|
|
629
890
|
React.createElement(
|
|
630
891
|
'table',
|
|
@@ -635,18 +896,19 @@ window.__ModuleLoader__.load({
|
|
|
635
896
|
React.createElement(
|
|
636
897
|
'tr',
|
|
637
898
|
null,
|
|
638
|
-
React.createElement('th', { style: { width: '40px' } }, '
|
|
639
|
-
React.createElement('th', null, '
|
|
640
|
-
React.createElement('th', null, '
|
|
641
|
-
React.createElement('th', null, '
|
|
642
|
-
React.createElement('th', null, '
|
|
899
|
+
React.createElement('th', { style: { width: '40px' } }, t('models.th_active')),
|
|
900
|
+
React.createElement('th', null, t('models.th_name')),
|
|
901
|
+
React.createElement('th', null, t('models.th_id')),
|
|
902
|
+
React.createElement('th', null, t('models.th_ctx')),
|
|
903
|
+
React.createElement('th', null, t('models.th_caps'))
|
|
643
904
|
)
|
|
644
905
|
),
|
|
645
906
|
React.createElement(
|
|
646
907
|
'tbody',
|
|
647
908
|
null,
|
|
648
|
-
modelsList.map((m) =>
|
|
649
|
-
|
|
909
|
+
modelsList.map((m) => {
|
|
910
|
+
const isEnabled = !disabledSet.has(m.id)
|
|
911
|
+
return React.createElement(
|
|
650
912
|
'tr',
|
|
651
913
|
{ key: m.id },
|
|
652
914
|
React.createElement(
|
|
@@ -654,7 +916,7 @@ window.__ModuleLoader__.load({
|
|
|
654
916
|
null,
|
|
655
917
|
React.createElement('input', {
|
|
656
918
|
type: 'checkbox',
|
|
657
|
-
checked:
|
|
919
|
+
checked: isEnabled,
|
|
658
920
|
onChange: () => handleToggleModel(m.id),
|
|
659
921
|
})
|
|
660
922
|
),
|
|
@@ -663,7 +925,10 @@ window.__ModuleLoader__.load({
|
|
|
663
925
|
null,
|
|
664
926
|
React.createElement('strong', null, m.name),
|
|
665
927
|
m.recommended
|
|
666
|
-
? React.createElement('span', { className: 'cb-badge cb-badge-ok', style: { marginLeft: '6px' } }, '
|
|
928
|
+
? React.createElement('span', { className: 'cb-badge cb-badge-ok', style: { marginLeft: '6px' } }, t('models.star'))
|
|
929
|
+
: null,
|
|
930
|
+
m.isCustom
|
|
931
|
+
? React.createElement('span', { className: 'cb-badge', style: { marginLeft: '6px', opacity: 0.8 } }, t('models.auto_new'))
|
|
667
932
|
: null
|
|
668
933
|
),
|
|
669
934
|
React.createElement('td', null, React.createElement('code', null, m.id)),
|
|
@@ -674,10 +939,13 @@ window.__ModuleLoader__.load({
|
|
|
674
939
|
React.createElement('span', { className: 'cb-badge' }, m.category || 'general'),
|
|
675
940
|
m.input?.includes('image') || m.input?.includes('vision')
|
|
676
941
|
? React.createElement('span', { className: 'cb-badge', style: { marginLeft: '4px' } }, 'Vision')
|
|
942
|
+
: null,
|
|
943
|
+
m.reasoningEfforts?.length || m.reasoning
|
|
944
|
+
? React.createElement('span', { className: 'cb-badge', style: { marginLeft: '4px' } }, '🧠 Reasoning')
|
|
677
945
|
: null
|
|
678
946
|
)
|
|
679
947
|
)
|
|
680
|
-
)
|
|
948
|
+
})
|
|
681
949
|
)
|
|
682
950
|
)
|
|
683
951
|
),
|
|
@@ -689,12 +957,12 @@ window.__ModuleLoader__.load({
|
|
|
689
957
|
React.createElement(
|
|
690
958
|
'div',
|
|
691
959
|
{ className: 'cb-section-title' },
|
|
692
|
-
'
|
|
960
|
+
t('stats.title')
|
|
693
961
|
),
|
|
694
962
|
React.createElement(
|
|
695
963
|
'div',
|
|
696
964
|
{ className: 'cb-section-desc' },
|
|
697
|
-
'
|
|
965
|
+
t('stats.desc')
|
|
698
966
|
),
|
|
699
967
|
React.createElement(
|
|
700
968
|
'div',
|
|
@@ -703,25 +971,25 @@ window.__ModuleLoader__.load({
|
|
|
703
971
|
'div',
|
|
704
972
|
{ className: 'cb-stat-box' },
|
|
705
973
|
React.createElement('div', { className: 'cb-stat-val' }, `${status.sessionStats?.successfulRequests || 0} / ${status.sessionStats?.totalRequests || 0}`),
|
|
706
|
-
React.createElement('div', { className: 'cb-stat-lbl' }, '
|
|
974
|
+
React.createElement('div', { className: 'cb-stat-lbl' }, t('stats.requests'))
|
|
707
975
|
),
|
|
708
976
|
React.createElement(
|
|
709
977
|
'div',
|
|
710
978
|
{ className: 'cb-stat-box' },
|
|
711
979
|
React.createElement('div', { className: 'cb-stat-val' }, `~${status.sessionStats?.totalTokensEst || 0}`),
|
|
712
|
-
React.createElement('div', { className: 'cb-stat-lbl' }, '
|
|
980
|
+
React.createElement('div', { className: 'cb-stat-lbl' }, t('stats.tokens'))
|
|
713
981
|
),
|
|
714
982
|
React.createElement(
|
|
715
983
|
'div',
|
|
716
984
|
{ className: 'cb-stat-box' },
|
|
717
|
-
React.createElement('div', { className: 'cb-stat-val' }, status.sessionStats?.lastLatencyMs ? `${status.sessionStats.lastLatencyMs}
|
|
718
|
-
React.createElement('div', { className: 'cb-stat-lbl' },
|
|
985
|
+
React.createElement('div', { className: 'cb-stat-val' }, status.sessionStats?.lastLatencyMs ? `${status.sessionStats.lastLatencyMs} ms` : '—'),
|
|
986
|
+
React.createElement('div', { className: 'cb-stat-lbl' }, t('stats.latency'))
|
|
719
987
|
),
|
|
720
988
|
React.createElement(
|
|
721
989
|
'div',
|
|
722
990
|
{ className: 'cb-stat-box' },
|
|
723
991
|
React.createElement('div', { className: 'cb-stat-val' }, status.sessionStats?.lastRequestAt ? new Date(status.sessionStats.lastRequestAt).toLocaleTimeString() : '—'),
|
|
724
|
-
React.createElement('div', { className: 'cb-stat-lbl' }, '
|
|
992
|
+
React.createElement('div', { className: 'cb-stat-lbl' }, t('stats.last_req'))
|
|
725
993
|
)
|
|
726
994
|
)
|
|
727
995
|
),
|
|
@@ -733,12 +1001,12 @@ window.__ModuleLoader__.load({
|
|
|
733
1001
|
React.createElement(
|
|
734
1002
|
'div',
|
|
735
1003
|
{ className: 'cb-section-title' },
|
|
736
|
-
'
|
|
1004
|
+
t('diag.title')
|
|
737
1005
|
),
|
|
738
1006
|
React.createElement(
|
|
739
1007
|
'div',
|
|
740
1008
|
{ className: 'cb-section-desc' },
|
|
741
|
-
'
|
|
1009
|
+
t('diag.desc')
|
|
742
1010
|
),
|
|
743
1011
|
React.createElement(
|
|
744
1012
|
'div',
|
|
@@ -751,7 +1019,7 @@ window.__ModuleLoader__.load({
|
|
|
751
1019
|
disabled: !!busy || !keyPresent,
|
|
752
1020
|
onClick: handleSmoke,
|
|
753
1021
|
},
|
|
754
|
-
busy === 'smoke' ? '
|
|
1022
|
+
busy === 'smoke' ? t('diag.smoke_testing') : t('diag.smoke_btn')
|
|
755
1023
|
),
|
|
756
1024
|
isRegistered
|
|
757
1025
|
? React.createElement(
|
|
@@ -762,7 +1030,7 @@ window.__ModuleLoader__.load({
|
|
|
762
1030
|
disabled: !!busy,
|
|
763
1031
|
onClick: handleUnregister,
|
|
764
1032
|
},
|
|
765
|
-
busy === 'unregister' ? '
|
|
1033
|
+
busy === 'unregister' ? t('diag.unregistering') : t('diag.unregister_btn')
|
|
766
1034
|
)
|
|
767
1035
|
: null,
|
|
768
1036
|
React.createElement(
|
|
@@ -774,14 +1042,14 @@ window.__ModuleLoader__.load({
|
|
|
774
1042
|
disabled: !!busy,
|
|
775
1043
|
onClick: handleRegister,
|
|
776
1044
|
},
|
|
777
|
-
busy === 'register' ? '
|
|
1045
|
+
busy === 'register' ? t('diag.resyncing') : t('diag.resync_btn')
|
|
778
1046
|
)
|
|
779
1047
|
),
|
|
780
1048
|
smokeResult
|
|
781
1049
|
? React.createElement(
|
|
782
1050
|
'div',
|
|
783
1051
|
{ className: 'cb-preview' },
|
|
784
|
-
`✅
|
|
1052
|
+
`✅ Latency: ${smokeResult.latencyMs} ms | Model: ${smokeResult.model}\nResponse: ${smokeResult.preview || '(empty)'}`
|
|
785
1053
|
)
|
|
786
1054
|
: null
|
|
787
1055
|
)
|