@goodandready/dsh-clinebot 0.2.1 → 0.3.1
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 +26 -0
- package/README.md +25 -14
- package/docs/README.ru.md +26 -15
- package/docs/README.zh.md +25 -14
- package/docs/design/DESIGN.md +5 -1
- package/lib/client.js +182 -198
- package/lib/cline-client.js +70 -5
- package/lib/index.js +113 -81
- package/lib/models.js +58 -48
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -10,6 +10,32 @@ window.__ModuleLoader__.load({
|
|
|
10
10
|
const SUBTITLE = 'ClinePass subscription ($9.99/mo) with curated & custom models.'
|
|
11
11
|
const ROUTE_PREFIX = '/dsh-clinebot'
|
|
12
12
|
|
|
13
|
+
const en = {
|
|
14
|
+
title: 'ClineBot',
|
|
15
|
+
subtitle: 'ClinePass subscription ($9.99/mo) with curated & custom models.',
|
|
16
|
+
'settings.loading': 'Loading ClineBot settings…',
|
|
17
|
+
'settings.unavailable': 'ClineBot settings unavailable (host namespace is not ready).',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const ru = {
|
|
21
|
+
title: 'ClineBot',
|
|
22
|
+
subtitle: 'Подписка ClinePass ($9.99/мес) с каталогом проверенных моделей.',
|
|
23
|
+
'settings.loading': 'Загрузка настроек ClineBot…',
|
|
24
|
+
'settings.unavailable': 'Настройки ClineBot недоступны (пространство хоста ещё не готово).',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function makeT(dict, fallback) {
|
|
28
|
+
return function t(key, vars) {
|
|
29
|
+
let val = (dict && dict[key]) || (fallback && fallback[key]) || key
|
|
30
|
+
if (vars && typeof val === 'string') {
|
|
31
|
+
for (const k of Object.keys(vars)) {
|
|
32
|
+
val = val.replace(new RegExp('\\{' + k + '\\}', 'g'), String(vars[k]))
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return val
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
13
39
|
function FallbackChevron() {
|
|
14
40
|
return React.createElement(
|
|
15
41
|
'svg',
|
|
@@ -30,6 +56,7 @@ window.__ModuleLoader__.load({
|
|
|
30
56
|
if (document.getElementById('dsh-clinebot-full-css')) return
|
|
31
57
|
const style = document.createElement('style')
|
|
32
58
|
style.id = 'dsh-clinebot-full-css'
|
|
59
|
+
style.dataset.dshPlugin = NS
|
|
33
60
|
style.textContent = `
|
|
34
61
|
.cb-page{display:flex;flex-direction:column;gap:20px;padding:8px 0 32px;max-width:960px}
|
|
35
62
|
.cb-header{display:flex;flex-direction:column;gap:8px;padding-bottom:16px;border-bottom:1px solid var(--dsw-alias-border-l2)}
|
|
@@ -52,12 +79,13 @@ window.__ModuleLoader__.load({
|
|
|
52
79
|
.cb-input:focus{outline:none;border-color:var(--dsw-alias-state-brand-primary)}
|
|
53
80
|
.cb-input-group{display:flex;gap:8px;align-items:center}
|
|
54
81
|
|
|
55
|
-
.cb-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 14px;font-size:13px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-weight:500;display:inline-flex;align-items:center;justify-content:center;gap:6px}
|
|
56
|
-
.cb-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-
|
|
82
|
+
.cb-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 14px;font-size:13px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-weight:500;display:inline-flex;align-items:center;justify-content:center;gap:6px;transition:all .15s ease}
|
|
83
|
+
.cb-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-4, var(--dsw-alias-bg-layer-2));border-color:var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2))}
|
|
57
84
|
.cb-btn-primary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:transparent}
|
|
58
|
-
.cb-btn-primary:hover:not(:disabled){opacity:0.
|
|
85
|
+
.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}
|
|
59
86
|
.cb-btn-danger{color:var(--dsw-alias-state-error-primary);border-color:rgba(239,68,68,0.3)}
|
|
60
|
-
.cb-btn-danger:hover:not(:disabled){background:rgba(239,68,68,0.
|
|
87
|
+
.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:disabled{opacity:0.5;cursor:not-allowed}
|
|
61
89
|
|
|
62
90
|
.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)}
|
|
63
91
|
.cb-bar-head{display:flex;justify-content:space-between;font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary)}
|
|
@@ -72,6 +100,12 @@ window.__ModuleLoader__.load({
|
|
|
72
100
|
|
|
73
101
|
.cb-alert-ok{padding:10px 14px;border-radius:8px;background:rgba(16,185,129,0.1);color:var(--dsw-alias-state-success-primary);font-size:13px}
|
|
74
102
|
.cb-alert-bad{padding:10px 14px;border-radius:8px;background:rgba(239,68,68,0.1);color:var(--dsw-alias-state-error-primary);font-size:13px}
|
|
103
|
+
.cb-alert-err{padding:10px 14px;border-radius:8px;background:rgba(239,68,68,0.1);color:var(--dsw-alias-state-error-primary);font-size:13px}
|
|
104
|
+
.cb-banner-warning{padding:12px 16px;border-radius:8px;background:rgba(245,158,11,0.12);border:1px solid var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);font-size:13px;display:flex;align-items:center;gap:10px;font-weight:500}
|
|
105
|
+
.cb-banner-exhausted{padding:12px 16px;border-radius:8px;background:rgba(239,68,68,0.12);border:1px solid var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);font-size:13px;display:flex;align-items:center;gap:10px;font-weight:600}
|
|
106
|
+
.cb-stat-box{padding:12px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);display:flex;flex-direction:column;gap:4px}
|
|
107
|
+
.cb-stat-val{font-size:18px;font-weight:700;color:var(--dsw-alias-label-primary)}
|
|
108
|
+
.cb-stat-lbl{font-size:12px;color:var(--dsw-alias-label-secondary)}
|
|
75
109
|
.cb-preview{padding:12px;border-radius:8px;background:var(--dsw-alias-bg-layer-2);font-family:monospace;font-size:12px;white-space:pre-wrap;word-break:break-all;border:1px solid var(--dsw-alias-border-l2)}
|
|
76
110
|
`
|
|
77
111
|
document.head.appendChild(style)
|
|
@@ -111,7 +145,22 @@ window.__ModuleLoader__.load({
|
|
|
111
145
|
)
|
|
112
146
|
}
|
|
113
147
|
|
|
114
|
-
function SettingsPage() {
|
|
148
|
+
function SettingsPage(props) {
|
|
149
|
+
const ctx = props?.ctx
|
|
150
|
+
const t = props?.t || makeT(ru, en)
|
|
151
|
+
|
|
152
|
+
const scope = React.useMemo(
|
|
153
|
+
() => (ctx && ctx.settingsScope ? ctx.settingsScope.bind({ namespace: NS }) : undefined),
|
|
154
|
+
[ctx]
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
const snapshot = React.useSyncExternalStore(
|
|
158
|
+
React.useMemo(() => (cb) => (scope ? scope.subscribe(cb) : () => {}), [scope]),
|
|
159
|
+
React.useCallback(() => (scope ? scope.getSnapshot() : { status: 'ready' }), [scope]),
|
|
160
|
+
React.useCallback(() => ({ status: 'loading' }), [])
|
|
161
|
+
)
|
|
162
|
+
const snapshotStatus = snapshot?.status || 'ready'
|
|
163
|
+
|
|
115
164
|
const [status, setStatus] = React.useState(null)
|
|
116
165
|
const [draft, setDraft] = React.useState(null)
|
|
117
166
|
const [busy, setBusy] = React.useState('')
|
|
@@ -123,13 +172,6 @@ window.__ModuleLoader__.load({
|
|
|
123
172
|
const [apiKeyInput, setApiKeyInput] = React.useState('')
|
|
124
173
|
const [showKey, setShowKey] = React.useState(false)
|
|
125
174
|
|
|
126
|
-
// Custom model form state
|
|
127
|
-
const [customId, setCustomId] = React.useState('')
|
|
128
|
-
const [customName, setCustomName] = React.useState('')
|
|
129
|
-
const [customContext, setCustomContext] = React.useState('200000')
|
|
130
|
-
const [customVision, setCustomVision] = React.useState(false)
|
|
131
|
-
const [customCategory, setCustomCategory] = React.useState('coding')
|
|
132
|
-
|
|
133
175
|
React.useEffect(() => {
|
|
134
176
|
ensureCss()
|
|
135
177
|
}, [])
|
|
@@ -195,6 +237,24 @@ window.__ModuleLoader__.load({
|
|
|
195
237
|
}
|
|
196
238
|
}
|
|
197
239
|
|
|
240
|
+
// Sync official models from ClinePass plan
|
|
241
|
+
async function handleSyncPlanModels() {
|
|
242
|
+
setBusy('sync-models')
|
|
243
|
+
setErr('')
|
|
244
|
+
setMsg('')
|
|
245
|
+
try {
|
|
246
|
+
const res = await fetch(`${ROUTE_PREFIX}/models/sync`, { method: 'POST' })
|
|
247
|
+
const data = await res.json().catch(() => ({}))
|
|
248
|
+
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`)
|
|
249
|
+
setMsg(`Синхронизировано моделей подписки: ${data.totalModelsCount} (${data.discoveredCount} получено напрямую из плана ClinePass)`)
|
|
250
|
+
await load()
|
|
251
|
+
} catch (e) {
|
|
252
|
+
setErr(String(e.message || e))
|
|
253
|
+
} finally {
|
|
254
|
+
setBusy('')
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
198
258
|
// Register/Sync Provider in DSH
|
|
199
259
|
async function handleRegister() {
|
|
200
260
|
setBusy('register')
|
|
@@ -251,6 +311,7 @@ window.__ModuleLoader__.load({
|
|
|
251
311
|
if (!data.ok) throw new Error(data.error || `HTTP ${res.status}`)
|
|
252
312
|
setSmokeResult(data)
|
|
253
313
|
setMsg(`Smoke тест пройден! Задержка: ${data.latencyMs} мс`)
|
|
314
|
+
await load()
|
|
254
315
|
} catch (e) {
|
|
255
316
|
setErr(String(e.message || e))
|
|
256
317
|
} finally {
|
|
@@ -270,6 +331,9 @@ window.__ModuleLoader__.load({
|
|
|
270
331
|
const nextList = Array.from(current)
|
|
271
332
|
setDraft({ ...draft, enabledModels: nextList })
|
|
272
333
|
|
|
334
|
+
if (scope && snapshotStatus === 'ready') {
|
|
335
|
+
try { await scope.set('enabledModels', nextList) } catch (_) {}
|
|
336
|
+
}
|
|
273
337
|
try {
|
|
274
338
|
await fetch(`${ROUTE_PREFIX}/models/toggle`, {
|
|
275
339
|
method: 'POST',
|
|
@@ -286,7 +350,7 @@ window.__ModuleLoader__.load({
|
|
|
286
350
|
if (type === 'all') {
|
|
287
351
|
nextList = status.availableModels.map((m) => m.id)
|
|
288
352
|
} else if (type === 'vision') {
|
|
289
|
-
nextList = status.availableModels.filter((m) => m.input?.includes('vision')).map((m) => m.id)
|
|
353
|
+
nextList = status.availableModels.filter((m) => m.input?.includes('image') || m.input?.includes('vision')).map((m) => m.id)
|
|
290
354
|
} else if (type === 'coding') {
|
|
291
355
|
nextList = status.availableModels.filter((m) => m.category === 'coding').map((m) => m.id)
|
|
292
356
|
} else if (type === 'recommended') {
|
|
@@ -294,6 +358,9 @@ window.__ModuleLoader__.load({
|
|
|
294
358
|
}
|
|
295
359
|
|
|
296
360
|
setDraft({ ...draft, enabledModels: nextList })
|
|
361
|
+
if (scope && snapshotStatus === 'ready') {
|
|
362
|
+
try { await scope.set('enabledModels', nextList) } catch (_) {}
|
|
363
|
+
}
|
|
297
364
|
try {
|
|
298
365
|
await fetch(`${ROUTE_PREFIX}/models/toggle`, {
|
|
299
366
|
method: 'POST',
|
|
@@ -303,61 +370,15 @@ window.__ModuleLoader__.load({
|
|
|
303
370
|
} catch {}
|
|
304
371
|
}
|
|
305
372
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
setBusy('add-model')
|
|
313
|
-
setErr('')
|
|
314
|
-
try {
|
|
315
|
-
const res = await fetch(`${ROUTE_PREFIX}/models/custom`, {
|
|
316
|
-
method: 'POST',
|
|
317
|
-
headers: { 'Content-Type': 'application/json' },
|
|
318
|
-
body: JSON.stringify({
|
|
319
|
-
id: customId.trim(),
|
|
320
|
-
name: customName.trim() || customId.trim(),
|
|
321
|
-
contextLength: Number(customContext) || 200000,
|
|
322
|
-
hasVision: customVision,
|
|
323
|
-
category: customCategory,
|
|
324
|
-
}),
|
|
325
|
-
})
|
|
326
|
-
const data = await res.json().catch(() => ({}))
|
|
327
|
-
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`)
|
|
328
|
-
setMsg(`Модель ${data.model?.name} успешно добавлена в каталог`)
|
|
329
|
-
setCustomId('')
|
|
330
|
-
setCustomName('')
|
|
331
|
-
await load()
|
|
332
|
-
} catch (e) {
|
|
333
|
-
setErr(String(e.message || e))
|
|
334
|
-
} finally {
|
|
335
|
-
setBusy('')
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Remove custom model
|
|
340
|
-
async function handleRemoveCustomModel(id) {
|
|
341
|
-
setBusy(`remove-${id}`)
|
|
342
|
-
setErr('')
|
|
343
|
-
try {
|
|
344
|
-
const res = await fetch(`${ROUTE_PREFIX}/models/custom`, {
|
|
345
|
-
method: 'DELETE',
|
|
346
|
-
headers: { 'Content-Type': 'application/json' },
|
|
347
|
-
body: JSON.stringify({ id }),
|
|
348
|
-
})
|
|
349
|
-
const data = await res.json().catch(() => ({}))
|
|
350
|
-
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`)
|
|
351
|
-
setMsg(`Модель ${id} удалена`)
|
|
352
|
-
await load()
|
|
353
|
-
} catch (e) {
|
|
354
|
-
setErr(String(e.message || e))
|
|
355
|
-
} finally {
|
|
356
|
-
setBusy('')
|
|
357
|
-
}
|
|
373
|
+
if (snapshotStatus === 'unavailable') {
|
|
374
|
+
return React.createElement(
|
|
375
|
+
'div',
|
|
376
|
+
{ className: 'cb-page' },
|
|
377
|
+
React.createElement('div', { className: 'cb-banner-warning' }, t('settings.unavailable'))
|
|
378
|
+
)
|
|
358
379
|
}
|
|
359
380
|
|
|
360
|
-
if (!status || !draft) {
|
|
381
|
+
if (!status || !draft || snapshotStatus === 'loading') {
|
|
361
382
|
if (err) {
|
|
362
383
|
return React.createElement(
|
|
363
384
|
'div',
|
|
@@ -490,6 +511,27 @@ window.__ModuleLoader__.load({
|
|
|
490
511
|
)
|
|
491
512
|
),
|
|
492
513
|
|
|
514
|
+
// Quota Warning Banner
|
|
515
|
+
status.quotaWarning
|
|
516
|
+
? React.createElement(
|
|
517
|
+
'div',
|
|
518
|
+
{ className: status.quotaWarning.level === 'exhausted' ? 'cb-banner-exhausted' : 'cb-banner-warning' },
|
|
519
|
+
React.createElement('span', { style: { fontSize: '16px' } }, status.quotaWarning.level === 'exhausted' ? '🚨' : '⚠️'),
|
|
520
|
+
React.createElement(
|
|
521
|
+
'div',
|
|
522
|
+
{ style: { flex: 1 } },
|
|
523
|
+
React.createElement('div', null, status.quotaWarning.message),
|
|
524
|
+
status.quotaWarning.resetsAt
|
|
525
|
+
? React.createElement(
|
|
526
|
+
'div',
|
|
527
|
+
{ style: { fontSize: '11px', opacity: 0.9, marginTop: '2px' } },
|
|
528
|
+
`Ожидаемое время сброса лимита: ${new Date(status.quotaWarning.resetsAt).toLocaleTimeString()}`
|
|
529
|
+
)
|
|
530
|
+
: null
|
|
531
|
+
)
|
|
532
|
+
)
|
|
533
|
+
: null,
|
|
534
|
+
|
|
493
535
|
// Card 2: Subscription Limits & Usage Dashboard
|
|
494
536
|
keyPresent
|
|
495
537
|
? React.createElement(
|
|
@@ -536,7 +578,7 @@ window.__ModuleLoader__.load({
|
|
|
536
578
|
)
|
|
537
579
|
: null,
|
|
538
580
|
|
|
539
|
-
// Card 3: Model Picker Management
|
|
581
|
+
// Card 3: Model Picker Management with Official Plan Sync
|
|
540
582
|
React.createElement(
|
|
541
583
|
'div',
|
|
542
584
|
{ className: 'cb-section-card' },
|
|
@@ -547,6 +589,16 @@ window.__ModuleLoader__.load({
|
|
|
547
589
|
React.createElement(
|
|
548
590
|
'div',
|
|
549
591
|
{ className: 'cb-row' },
|
|
592
|
+
React.createElement(
|
|
593
|
+
'button',
|
|
594
|
+
{
|
|
595
|
+
type: 'button',
|
|
596
|
+
className: 'cb-btn',
|
|
597
|
+
disabled: !!busy || !keyPresent,
|
|
598
|
+
onClick: handleSyncPlanModels,
|
|
599
|
+
},
|
|
600
|
+
busy === 'sync-models' ? 'Синхронизация…' : '🔄 Получить модели из плана'
|
|
601
|
+
),
|
|
550
602
|
React.createElement(
|
|
551
603
|
'button',
|
|
552
604
|
{ type: 'button', className: 'cb-btn', onClick: () => handleSetModelsFilter('all') },
|
|
@@ -572,7 +624,7 @@ window.__ModuleLoader__.load({
|
|
|
572
624
|
React.createElement(
|
|
573
625
|
'div',
|
|
574
626
|
{ className: 'cb-section-desc' },
|
|
575
|
-
'Отметьте
|
|
627
|
+
'Список формируется напрямую из официальной подписки ClinePass без ручного ввода. Отметьте модели, которые должны отображаться в выпадающем списке выбора моделей в чате DeepSeek Harness.'
|
|
576
628
|
),
|
|
577
629
|
React.createElement(
|
|
578
630
|
'table',
|
|
@@ -612,9 +664,6 @@ window.__ModuleLoader__.load({
|
|
|
612
664
|
React.createElement('strong', null, m.name),
|
|
613
665
|
m.recommended
|
|
614
666
|
? React.createElement('span', { className: 'cb-badge cb-badge-ok', style: { marginLeft: '6px' } }, 'Star')
|
|
615
|
-
: null,
|
|
616
|
-
m.isCustom
|
|
617
|
-
? React.createElement('span', { className: 'cb-badge cb-badge-warn', style: { marginLeft: '6px' } }, 'Custom')
|
|
618
667
|
: null
|
|
619
668
|
),
|
|
620
669
|
React.createElement('td', null, React.createElement('code', null, m.id)),
|
|
@@ -623,7 +672,7 @@ window.__ModuleLoader__.load({
|
|
|
623
672
|
'td',
|
|
624
673
|
null,
|
|
625
674
|
React.createElement('span', { className: 'cb-badge' }, m.category || 'general'),
|
|
626
|
-
m.input?.includes('vision')
|
|
675
|
+
m.input?.includes('image') || m.input?.includes('vision')
|
|
627
676
|
? React.createElement('span', { className: 'cb-badge', style: { marginLeft: '4px' } }, 'Vision')
|
|
628
677
|
: null
|
|
629
678
|
)
|
|
@@ -633,134 +682,48 @@ window.__ModuleLoader__.load({
|
|
|
633
682
|
)
|
|
634
683
|
),
|
|
635
684
|
|
|
636
|
-
// Card 4:
|
|
685
|
+
// Card 4: Session Metrics & Usage Tracking
|
|
637
686
|
React.createElement(
|
|
638
687
|
'div',
|
|
639
688
|
{ className: 'cb-section-card' },
|
|
640
689
|
React.createElement(
|
|
641
690
|
'div',
|
|
642
691
|
{ className: 'cb-section-title' },
|
|
643
|
-
'
|
|
692
|
+
'📈 Статистика текущей сессии DSH'
|
|
644
693
|
),
|
|
645
694
|
React.createElement(
|
|
646
695
|
'div',
|
|
647
696
|
{ className: 'cb-section-desc' },
|
|
648
|
-
'
|
|
697
|
+
'Локальный мониторинг запросов, задержек сети и ориентировочного расхода токенов через ClinePass.'
|
|
649
698
|
),
|
|
650
699
|
React.createElement(
|
|
651
700
|
'div',
|
|
652
701
|
{ className: 'cb-grid-2' },
|
|
653
702
|
React.createElement(
|
|
654
703
|
'div',
|
|
655
|
-
|
|
656
|
-
React.createElement('
|
|
657
|
-
React.createElement('
|
|
658
|
-
className: 'cb-input',
|
|
659
|
-
placeholder: 'cline-pass/...',
|
|
660
|
-
value: customId,
|
|
661
|
-
onChange: (e) => setCustomId(e.target.value),
|
|
662
|
-
})
|
|
704
|
+
{ className: 'cb-stat-box' },
|
|
705
|
+
React.createElement('div', { className: 'cb-stat-val' }, `${status.sessionStats?.successfulRequests || 0} / ${status.sessionStats?.totalRequests || 0}`),
|
|
706
|
+
React.createElement('div', { className: 'cb-stat-lbl' }, 'Успешных запросов / Всего')
|
|
663
707
|
),
|
|
664
708
|
React.createElement(
|
|
665
709
|
'div',
|
|
666
|
-
|
|
667
|
-
React.createElement('
|
|
668
|
-
React.createElement('
|
|
669
|
-
className: 'cb-input',
|
|
670
|
-
placeholder: 'Например: DeepSeek V4 MoE',
|
|
671
|
-
value: customName,
|
|
672
|
-
onChange: (e) => setCustomName(e.target.value),
|
|
673
|
-
})
|
|
710
|
+
{ className: 'cb-stat-box' },
|
|
711
|
+
React.createElement('div', { className: 'cb-stat-val' }, `~${status.sessionStats?.totalTokensEst || 0}`),
|
|
712
|
+
React.createElement('div', { className: 'cb-stat-lbl' }, 'Оценка токенов сессии')
|
|
674
713
|
),
|
|
675
714
|
React.createElement(
|
|
676
715
|
'div',
|
|
677
|
-
|
|
678
|
-
React.createElement('
|
|
679
|
-
React.createElement('
|
|
680
|
-
className: 'cb-input',
|
|
681
|
-
type: 'number',
|
|
682
|
-
value: customContext,
|
|
683
|
-
onChange: (e) => setCustomContext(e.target.value),
|
|
684
|
-
})
|
|
716
|
+
{ 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' }, 'Последняя задержка (Latency)')
|
|
685
719
|
),
|
|
686
720
|
React.createElement(
|
|
687
721
|
'div',
|
|
688
|
-
|
|
689
|
-
React.createElement('
|
|
690
|
-
React.createElement(
|
|
691
|
-
'select',
|
|
692
|
-
{
|
|
693
|
-
className: 'cb-input',
|
|
694
|
-
value: customCategory,
|
|
695
|
-
onChange: (e) => setCustomCategory(e.target.value),
|
|
696
|
-
},
|
|
697
|
-
React.createElement('option', { value: 'coding' }, 'Кодинг (Coding)'),
|
|
698
|
-
React.createElement('option', { value: 'reasoning' }, 'Рассуждения (Reasoning)'),
|
|
699
|
-
React.createElement('option', { value: 'multimodal' }, 'Мультимодальная'),
|
|
700
|
-
React.createElement('option', { value: 'general' }, 'Общая (General)')
|
|
701
|
-
)
|
|
722
|
+
{ className: 'cb-stat-box' },
|
|
723
|
+
React.createElement('div', { className: 'cb-stat-val' }, status.sessionStats?.lastRequestAt ? new Date(status.sessionStats.lastRequestAt).toLocaleTimeString() : '—'),
|
|
724
|
+
React.createElement('div', { className: 'cb-stat-lbl' }, 'Время последнего запроса')
|
|
702
725
|
)
|
|
703
|
-
)
|
|
704
|
-
React.createElement(
|
|
705
|
-
'div',
|
|
706
|
-
{ className: 'cb-row', style: { marginTop: '4px' } },
|
|
707
|
-
React.createElement(
|
|
708
|
-
'label',
|
|
709
|
-
{ className: 'cb-row', style: { cursor: 'pointer', fontSize: '13px' } },
|
|
710
|
-
React.createElement('input', {
|
|
711
|
-
type: 'checkbox',
|
|
712
|
-
checked: customVision,
|
|
713
|
-
onChange: (e) => setCustomVision(e.target.checked),
|
|
714
|
-
}),
|
|
715
|
-
' Модель поддерживает изображения (Vision)'
|
|
716
|
-
),
|
|
717
|
-
React.createElement(
|
|
718
|
-
'button',
|
|
719
|
-
{
|
|
720
|
-
type: 'button',
|
|
721
|
-
className: 'cb-btn cb-btn-primary',
|
|
722
|
-
style: { marginLeft: 'auto' },
|
|
723
|
-
disabled: !!busy || !customId.trim(),
|
|
724
|
-
onClick: handleAddCustomModel,
|
|
725
|
-
},
|
|
726
|
-
busy === 'add-model' ? 'Добавление…' : 'Добавить модель'
|
|
727
|
-
)
|
|
728
|
-
),
|
|
729
|
-
// Custom models list with remove button
|
|
730
|
-
draft.customModels?.length > 0
|
|
731
|
-
? React.createElement(
|
|
732
|
-
'div',
|
|
733
|
-
{ style: { marginTop: '10px' } },
|
|
734
|
-
React.createElement('div', { className: 'cb-section-desc' }, 'Добавленные вами модели:'),
|
|
735
|
-
React.createElement(
|
|
736
|
-
'div',
|
|
737
|
-
{ className: 'cb-row', style: { marginTop: '6px' } },
|
|
738
|
-
draft.customModels.map((cm) =>
|
|
739
|
-
React.createElement(
|
|
740
|
-
'span',
|
|
741
|
-
{ key: cm.id, className: 'cb-badge' },
|
|
742
|
-
`${cm.name} (${cm.id})`,
|
|
743
|
-
React.createElement(
|
|
744
|
-
'button',
|
|
745
|
-
{
|
|
746
|
-
type: 'button',
|
|
747
|
-
style: {
|
|
748
|
-
background: 'none',
|
|
749
|
-
border: 'none',
|
|
750
|
-
cursor: 'pointer',
|
|
751
|
-
color: 'var(--dsw-alias-state-error-primary)',
|
|
752
|
-
padding: 0,
|
|
753
|
-
marginLeft: '4px',
|
|
754
|
-
},
|
|
755
|
-
onClick: () => handleRemoveCustomModel(cm.id),
|
|
756
|
-
},
|
|
757
|
-
'×'
|
|
758
|
-
)
|
|
759
|
-
)
|
|
760
|
-
)
|
|
761
|
-
)
|
|
762
|
-
)
|
|
763
|
-
: null
|
|
726
|
+
)
|
|
764
727
|
),
|
|
765
728
|
|
|
766
729
|
// Card 5: Diagnostics & Sync with DSH
|
|
@@ -826,8 +789,9 @@ window.__ModuleLoader__.load({
|
|
|
826
789
|
}
|
|
827
790
|
|
|
828
791
|
// Accordion item for Plugins section
|
|
829
|
-
function PluginCard() {
|
|
792
|
+
function PluginCard(props) {
|
|
830
793
|
const [open, setOpen] = React.useState(false)
|
|
794
|
+
const t = props?.t || makeT(ru, en)
|
|
831
795
|
React.useEffect(() => {
|
|
832
796
|
ensureCss()
|
|
833
797
|
}, [])
|
|
@@ -855,41 +819,61 @@ window.__ModuleLoader__.load({
|
|
|
855
819
|
React.createElement(
|
|
856
820
|
'div',
|
|
857
821
|
{ style: { flex: 1 } },
|
|
858
|
-
React.createElement('div', { style: { fontWeight: 600, fontSize: '15px' } },
|
|
859
|
-
React.createElement('div', { style: { fontSize: '13px', color: 'var(--dsw-alias-label-secondary)' } },
|
|
822
|
+
React.createElement('div', { style: { fontWeight: 600, fontSize: '15px' } }, t('title')),
|
|
823
|
+
React.createElement('div', { style: { fontSize: '13px', color: 'var(--dsw-alias-label-secondary)' } }, t('subtitle'))
|
|
860
824
|
),
|
|
861
825
|
React.createElement('span', { style: { transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .16s' } },
|
|
862
826
|
React.createElement(Chevron)
|
|
863
827
|
)
|
|
864
828
|
),
|
|
865
|
-
open ? React.createElement('div', { style: { marginTop: '16px' } }, React.createElement(SettingsPage)) : null
|
|
829
|
+
open ? React.createElement('div', { style: { marginTop: '16px' } }, React.createElement(SettingsPage, { ...props, ctx: props?.ctx })) : null
|
|
866
830
|
)
|
|
867
831
|
}
|
|
868
832
|
|
|
869
833
|
function apply(ctx) {
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
834
|
+
if (ctx.locale && ctx.locale.register) {
|
|
835
|
+
try { ctx.locale.register(NS, { en, ru }) } catch (_) {}
|
|
836
|
+
}
|
|
837
|
+
const t = (ctx.locale && ctx.locale.bind) ? ctx.locale.bind(NS) : makeT(ru, en)
|
|
838
|
+
|
|
839
|
+
let placed = false
|
|
840
|
+
try {
|
|
841
|
+
placed = !!ctx.slots.inject('settings.plugin.item', () => {
|
|
842
|
+
return ctx.slots.register(
|
|
843
|
+
{
|
|
844
|
+
name: 'settings.plugin.item',
|
|
845
|
+
key: NS,
|
|
846
|
+
locale: NS,
|
|
847
|
+
inject: () => ({ ctx }),
|
|
848
|
+
},
|
|
849
|
+
PluginCard
|
|
850
|
+
)
|
|
851
|
+
})
|
|
852
|
+
} catch (_) {
|
|
853
|
+
placed = false
|
|
854
|
+
}
|
|
882
855
|
|
|
883
|
-
//
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
856
|
+
// Fallback to settings.section if settings.plugin.item is not declared
|
|
857
|
+
if (!placed) {
|
|
858
|
+
try {
|
|
859
|
+
ctx.slots.inject('settings.section', () => {
|
|
860
|
+
return ctx.slots.register(
|
|
861
|
+
{
|
|
862
|
+
name: 'settings.section',
|
|
863
|
+
id: '@goodandready/dsh-clinebot',
|
|
864
|
+
order: 28,
|
|
865
|
+
locale: NS,
|
|
866
|
+
label: () => t('title'),
|
|
867
|
+
inject: () => ({ ctx }),
|
|
868
|
+
},
|
|
869
|
+
SettingsPage
|
|
870
|
+
)
|
|
871
|
+
})
|
|
872
|
+
} catch (_) {}
|
|
873
|
+
}
|
|
890
874
|
}
|
|
891
875
|
|
|
892
|
-
module.exports = { apply, inject: ['slots'] }
|
|
876
|
+
module.exports = { apply, inject: ['slots', 'locale', 'settingsScope'] }
|
|
893
877
|
return module.exports
|
|
894
878
|
},
|
|
895
879
|
})
|