tensorgrid-ui 1.10.0 → 2.1.0
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/lib/client.js +203 -110
- package/lib/http-runtime.js +103 -22
- package/lib/index.js +4 -2
- package/lib/review-settings.js +156 -224
- package/lib/review.js +36 -26
- package/lib/reviewers.js +7 -2
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -180,10 +180,7 @@ window.__ModuleLoader__.load({
|
|
|
180
180
|
'review.effortName': 'effort',
|
|
181
181
|
'review.other': 'another…',
|
|
182
182
|
'review.modelName': 'model name',
|
|
183
|
-
'review.custom': 'Your own set of reviewers — no mode matches it.',
|
|
184
|
-
'review.details': 'Detailed settings',
|
|
185
183
|
'review.fromList': 'back to the list',
|
|
186
|
-
'review.ours': 'ours',
|
|
187
184
|
'review.objectFalse': 'I disagree — this is real',
|
|
188
185
|
'review.accuracy': 'How often each reviewer turned out to be right',
|
|
189
186
|
'review.notChecked': 'not verified yet',
|
|
@@ -193,9 +190,19 @@ window.__ModuleLoader__.load({
|
|
|
193
190
|
'review.objectReal': 'I disagree — this is wrong',
|
|
194
191
|
'review.of': 'of',
|
|
195
192
|
'review.byHuman': 'your call',
|
|
193
|
+
'review.needsProduct': 'Works only if that product is installed separately',
|
|
194
|
+
'review.add': 'Add a reviewer:',
|
|
195
|
+
'review.participates': 'Take part in reviews',
|
|
196
|
+
'review.noneActive': 'Every reviewer is switched off — a review will find nothing.',
|
|
197
|
+
'review.remove': 'Remove this reviewer',
|
|
198
|
+
'review.bySubscription': 'subscription',
|
|
199
|
+
'review.limit': 'That is as many reviewers as one review can hold.',
|
|
200
|
+
'review.byApiKey': 'paid per token',
|
|
201
|
+
'review.groupSubscription': 'Included in a subscription',
|
|
202
|
+
'review.groupPaid': 'Paid separately \u2014 per token',
|
|
196
203
|
'review.nav': 'Reviewers',
|
|
197
204
|
'review.title': 'Second opinion',
|
|
198
|
-
'review.hint': 'Independent reviewers examine finished work and report what is wrong.
|
|
205
|
+
'review.hint': 'Independent reviewers examine finished work and report what is wrong. Choose how many and which ones: different models find different things.',
|
|
199
206
|
'review.model': 'Model',
|
|
200
207
|
'review.effort': 'Effort',
|
|
201
208
|
'review.asConfigured': 'as configured',
|
|
@@ -251,10 +258,7 @@ window.__ModuleLoader__.load({
|
|
|
251
258
|
'review.effortName': '强度',
|
|
252
259
|
'review.other': '其他…',
|
|
253
260
|
'review.modelName': '模型名称',
|
|
254
|
-
'review.custom': '自定义审阅者组合——没有匹配的模式。',
|
|
255
|
-
'review.details': '详细设置',
|
|
256
261
|
'review.fromList': '返回列表',
|
|
257
|
-
'review.ours': '我们的',
|
|
258
262
|
'review.objectFalse': '我不同意——这是真的',
|
|
259
263
|
'review.accuracy': '各审阅者的正确率',
|
|
260
264
|
'review.notChecked': '尚未核实',
|
|
@@ -264,9 +268,19 @@ window.__ModuleLoader__.load({
|
|
|
264
268
|
'review.objectReal': '我不同意——这是错的',
|
|
265
269
|
'review.of': '共',
|
|
266
270
|
'review.byHuman': '你的判断',
|
|
271
|
+
'review.needsProduct': '仅在单独安装该产品后可用',
|
|
272
|
+
'review.add': '添加审阅者:',
|
|
273
|
+
'review.participates': '参与审阅',
|
|
274
|
+
'review.noneActive': '所有审阅者都已关闭——审阅将一无所获。',
|
|
275
|
+
'review.remove': '移除该审阅者',
|
|
276
|
+
'review.bySubscription': '订阅',
|
|
277
|
+
'review.limit': '一次审阅最多只能有这么多审阅者。',
|
|
278
|
+
'review.byApiKey': '按量计费',
|
|
279
|
+
'review.groupSubscription': '包含在订阅中',
|
|
280
|
+
'review.groupPaid': '单独计费 \u2014 按量',
|
|
267
281
|
'review.nav': '审阅者',
|
|
268
282
|
'review.title': '第二意见',
|
|
269
|
-
'review.hint': '
|
|
283
|
+
'review.hint': '独立审阅者检查已完成的工作并报告问题。自行决定数量与人选:不同模型发现的问题不同。',
|
|
270
284
|
'review.model': '模型',
|
|
271
285
|
'review.effort': '推理强度',
|
|
272
286
|
'review.asConfigured': '按产品设置',
|
|
@@ -1619,10 +1633,7 @@ window.__ModuleLoader__.load({
|
|
|
1619
1633
|
'review.effortName': 'усилие',
|
|
1620
1634
|
'review.other': 'другая…',
|
|
1621
1635
|
'review.modelName': 'имя модели',
|
|
1622
|
-
'review.custom': 'Свой набор ревизоров — ни один режим ему не соответствует.',
|
|
1623
|
-
'review.details': 'Подробные настройки',
|
|
1624
1636
|
'review.fromList': 'вернуться к списку',
|
|
1625
|
-
'review.ours': 'наш',
|
|
1626
1637
|
'review.objectFalse': 'Не согласен — это настоящая',
|
|
1627
1638
|
'review.accuracy': 'Как часто каждый ревизор оказывался прав',
|
|
1628
1639
|
'review.notChecked': 'ещё не проверено',
|
|
@@ -1632,9 +1643,19 @@ window.__ModuleLoader__.load({
|
|
|
1632
1643
|
'review.objectReal': 'Не согласен — это ложная',
|
|
1633
1644
|
'review.of': 'из',
|
|
1634
1645
|
'review.byHuman': 'ваше решение',
|
|
1646
|
+
'review.needsProduct': 'Работает, только если этот продукт установлен отдельно',
|
|
1647
|
+
'review.add': 'Добавить ревизора:',
|
|
1648
|
+
'review.participates': 'Участвует в обзорах',
|
|
1649
|
+
'review.noneActive': 'Все ревизоры выключены — обзор ничего не найдёт.',
|
|
1650
|
+
'review.remove': 'Убрать этого ревизора',
|
|
1651
|
+
'review.bySubscription': 'по подписке',
|
|
1652
|
+
'review.limit': 'Больше ревизоров в один обзор не поместится.',
|
|
1653
|
+
'review.byApiKey': 'по токенам',
|
|
1654
|
+
'review.groupSubscription': 'Входят в подписку',
|
|
1655
|
+
'review.groupPaid': 'Оплата отдельно \u2014 по токенам',
|
|
1635
1656
|
'review.nav': 'Ревизоры',
|
|
1636
1657
|
'review.title': 'Второе мнение',
|
|
1637
|
-
'review.hint': 'Независимые ревизоры смотрят готовую работу и сообщают, что в ней не так.
|
|
1658
|
+
'review.hint': 'Независимые ревизоры смотрят готовую работу и сообщают, что в ней не так. Сколько их и какие — выбираете вы: разные модели находят разное.',
|
|
1638
1659
|
'review.model': 'Модель',
|
|
1639
1660
|
'review.effort': 'Усилие',
|
|
1640
1661
|
'review.asConfigured': 'как настроено',
|
|
@@ -1788,7 +1809,7 @@ window.__ModuleLoader__.load({
|
|
|
1788
1809
|
// поэтому строка остаётся согласованной при любой палитре.
|
|
1789
1810
|
'.dsx-setting{display:flex;align-items:center;justify-content:space-between;gap:16px;width:100%;}',
|
|
1790
1811
|
'.dsx-setting__title{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;}',
|
|
1791
|
-
'.dsx-rev__panel{margin-top:14px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l1)}.dsx-rev__panel-head{display:flex;align-items:center;gap:8px;margin-bottom:8px}.dsx-rev__count{padding:1px 7px;border-radius:99px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-tertiary);font-size:11px}.dsx-rev__findings{display:flex;flex-direction:column;gap:8px}.dsx-rev__finding{padding:9px 11px;border-radius:8px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2)}.dsx-rev__finding--false{opacity:.55}.dsx-rev__finding-head{display:flex;align-items:baseline;gap:7px;flex-wrap:wrap}.dsx-rev__finding-title{font-weight:600;color:var(--dsw-alias-label-primary);font-size:13px;flex:1}.dsx-rev__sev{padding:1px 6px;border-radius:4px;font-size:10px;text-transform:uppercase;letter-spacing:.03em}.dsx-rev__sev--crit{background:#5c1f22;color:#ffb4b4}.dsx-rev__sev--high{background:#5a3f16;color:#f0cd8a}.dsx-rev__sev--unknown{background:#3a3a44;color:#c9c9d4}.dsx-rev__sev--low{background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-tertiary)}.dsx-rev__who{font-size:11px;color:var(--dsw-alias-label-tertiary)}.dsx-rev__where{margin-top:4px;font-family:ui-monospace,monospace;font-size:11px;color:var(--dsw-alias-label-tertiary)}.dsx-rev__what{margin-top:4px;font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.45}.dsx-rev__verdict{display:flex;align-items:flex-start;gap:6px;margin-top:7px;padding-top:7px;border-top:1px dashed var(--dsw-alias-border-l1);font-size:11px;color:var(--dsw-alias-label-secondary)}.dsx-rev__verdict-mark{font-weight:700}.dsx-rev__verdict-text{flex:1;line-height:1.4}.dsx-rev__by{color:var(--dsw-alias-label-tertiary);white-space:nowrap}.dsx-rev__object{margin-top:5px}.dsx-rev__object-btn{border:0;background:none;padding:0;color:var(--dsw-alias-label-tertiary);font-size:11px;cursor:pointer;text-decoration:underline dotted}.dsx-rev__object-btn:hover{color:var(--dsw-alias-label-primary)}.dsx-rev__stats{margin-top:12px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l1)}.dsx-rev__stat{display:flex;justify-content:space-between;gap:10px;margin-top:4px;font-size:12px}.dsx-rev__stat-name{color:var(--dsw-alias-label-secondary)}.dsx-rev__stat-rate{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums}.dsx-
|
|
1812
|
+
'.dsx-rev__panel{margin-top:14px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l1)}.dsx-rev__panel-head{display:flex;align-items:center;gap:8px;margin-bottom:8px}.dsx-rev__count{padding:1px 7px;border-radius:99px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-tertiary);font-size:11px}.dsx-rev__findings{display:flex;flex-direction:column;gap:8px}.dsx-rev__finding{padding:9px 11px;border-radius:8px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2)}.dsx-rev__finding--false{opacity:.55}.dsx-rev__finding-head{display:flex;align-items:baseline;gap:7px;flex-wrap:wrap}.dsx-rev__finding-title{font-weight:600;color:var(--dsw-alias-label-primary);font-size:13px;flex:1}.dsx-rev__sev{padding:1px 6px;border-radius:4px;font-size:10px;text-transform:uppercase;letter-spacing:.03em}.dsx-rev__sev--crit{background:#5c1f22;color:#ffb4b4}.dsx-rev__sev--high{background:#5a3f16;color:#f0cd8a}.dsx-rev__sev--unknown{background:#3a3a44;color:#c9c9d4}.dsx-rev__sev--low{background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-tertiary)}.dsx-rev__who{font-size:11px;color:var(--dsw-alias-label-tertiary)}.dsx-rev__where{margin-top:4px;font-family:ui-monospace,monospace;font-size:11px;color:var(--dsw-alias-label-tertiary)}.dsx-rev__what{margin-top:4px;font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.45}.dsx-rev__verdict{display:flex;align-items:flex-start;gap:6px;margin-top:7px;padding-top:7px;border-top:1px dashed var(--dsw-alias-border-l1);font-size:11px;color:var(--dsw-alias-label-secondary)}.dsx-rev__verdict-mark{font-weight:700}.dsx-rev__verdict-text{flex:1;line-height:1.4}.dsx-rev__by{color:var(--dsw-alias-label-tertiary);white-space:nowrap}.dsx-rev__object{margin-top:5px}.dsx-rev__object-btn{border:0;background:none;padding:0;color:var(--dsw-alias-label-tertiary);font-size:11px;cursor:pointer;text-decoration:underline dotted}.dsx-rev__object-btn:hover{color:var(--dsw-alias-label-primary)}.dsx-rev__stats{margin-top:12px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l1)}.dsx-rev__stat{display:flex;justify-content:space-between;gap:10px;margin-top:4px;font-size:12px}.dsx-rev__stat-name{color:var(--dsw-alias-label-secondary)}.dsx-rev__stat-rate{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums}.dsx-rev__tag{margin-left:6px;padding:1px 6px;border-radius:99px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-tertiary);font-size:10px}.dsx-rev__custom{display:inline-flex;align-items:center;gap:4px}.dsx-rev__input{padding:3px 6px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-size:12px;width:170px}.dsx-rev__back{border:0;background:none;color:var(--dsw-alias-label-tertiary);cursor:pointer;font-size:14px;line-height:1;padding:0 2px}.dsx-rev__remove{margin-left:auto;border:0;background:none;color:var(--dsw-alias-label-tertiary);font-size:16px;line-height:1;padding:0 4px;cursor:pointer}.dsx-rev__remove:hover{color:#ffb4b4}.dsx-rev__add{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:12px}.dsx-rev__add-label{color:var(--dsw-alias-label-secondary);font-size:12px;margin-right:2px}.dsx-rev__add-btn{padding:4px 11px;border:1px dashed var(--dsw-alias-border-l2);border-radius:99px;background:transparent;color:var(--dsw-alias-label-secondary);font-size:12px;cursor:pointer;font-family:inherit}.dsx-rev__add-btn:hover:not(:disabled){border-style:solid;color:var(--dsw-alias-label-primary)}.dsx-rev__add-btn:disabled{opacity:.4;cursor:default}.dsx-rev__list{display:flex;flex-direction:column;gap:8px}.dsx-rev__card{padding:10px 12px;border-radius:8px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);opacity:.6;transition:opacity .15s ease,border-color .15s ease}.dsx-rev__card--on{opacity:1;border-color:var(--dsw-alias-border-l2)}.dsx-rev__head{display:flex;align-items:center;gap:8px;cursor:pointer}.dsx-rev__name{font-weight:600;color:var(--dsw-alias-label-primary)}.dsx-rev__fields{display:flex;flex-wrap:wrap;gap:12px;margin-top:8px;padding-left:22px}.dsx-rev__field{display:flex;align-items:center;gap:6px;color:var(--dsw-alias-label-secondary);font-size:12px}.dsx-rev__select{padding:3px 6px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-size:12px}.dsx-subs{display:flex;flex-direction:column;gap:14px;padding:4px 0}.dsx-subs__head{display:flex;flex-direction:column;gap:4px}.dsx-subs__title{font-weight:600;font-size:15px;color:var(--dsw-alias-label-primary)}.dsx-subs__note{padding-top:6px;border-top:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}.dsx-setting--stack{flex-direction:column;align-items:stretch;gap:10px}.dsx-auth__list{display:flex;flex-direction:column;gap:6px}.dsx-auth__row{display:flex;align-items:center;justify-content:space-between;gap:12px}.dsx-auth__name{color:var(--dsw-alias-label-primary)}.dsx-auth__badge{margin-left:8px;padding:1px 6px;border-radius:999px;font-size:11px;color:var(--dsw-alias-state-success-primary);border:1px solid var(--dsw-alias-border-l2)}.dsx-auth__panel{display:flex;flex-direction:column;gap:8px;padding:10px;border-radius:8px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1)}.dsx-auth__notice,.dsx-auth__prompt{display:flex;flex-direction:column;gap:6px;color:var(--dsw-alias-label-secondary)}.dsx-auth__link{color:var(--dsw-alias-brand-primary);word-break:break-all}.dsx-auth__code{font-family:ui-monospace,monospace;letter-spacing:.08em;color:var(--dsw-alias-label-primary)}.dsx-auth__input{flex:1;min-width:0;padding:4px 8px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}.dsx-setting__hint--strong{color:var(--dsw-alias-label-primary)}.dsx-setting__hint--warn{color:var(--dsw-alias-state-warn-primary)}.dsx-setting__hint{color:var(--dsw-alias-label-secondary);font-size:12px;margin-top:2px;}',
|
|
1792
1813
|
'.dsx-setting__control{display:inline-flex;padding:2px;gap:2px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-layer-2);flex:none;}',
|
|
1793
1814
|
'.dsx-seg{appearance:none;border:0;cursor:pointer;padding:4px 10px;border-radius:6px;font-size:12px;line-height:18px;background:transparent;color:var(--dsw-alias-label-secondary);transition:background .16s ease,color .16s ease;}',
|
|
1794
1815
|
'.dsx-seg:hover{color:var(--dsw-alias-label-primary);}',
|
|
@@ -2318,13 +2339,12 @@ window.__ModuleLoader__.load({
|
|
|
2318
2339
|
/**
|
|
2319
2340
|
* Выбор значения: известные варианты плюс «своё».
|
|
2320
2341
|
*
|
|
2321
|
-
*
|
|
2322
|
-
*
|
|
2323
|
-
*
|
|
2324
|
-
*
|
|
2325
|
-
* интерфейс бесполезным до следующего обновления пакета.
|
|
2342
|
+
* `groups` разносит варианты по подписям. Для моделей это принципиально:
|
|
2343
|
+
* один и тот же `claude-opus-5` доступен и по подписке, и через
|
|
2344
|
+
* OpenRouter по токенам, и вперемешку они неразличимы — а разница в том,
|
|
2345
|
+
* придёт ли за них счёт.
|
|
2326
2346
|
*/
|
|
2327
|
-
function ValuePicker({ t, value, options, disabled, onPick, placeholder }) {
|
|
2347
|
+
function ValuePicker({ t, value, options, disabled, onPick, placeholder, groups }) {
|
|
2328
2348
|
const known = options.some((option) => option.value === value)
|
|
2329
2349
|
const [custom, setCustom] = React.useState(value !== null && !known)
|
|
2330
2350
|
|
|
@@ -2346,12 +2366,23 @@ window.__ModuleLoader__.load({
|
|
|
2346
2366
|
disabled: disabled,
|
|
2347
2367
|
title: t('review.fromList'),
|
|
2348
2368
|
onClick: () => { setCustom(false); onPick(null) },
|
|
2349
|
-
children: '
|
|
2369
|
+
children: '\u00d7',
|
|
2350
2370
|
}),
|
|
2351
2371
|
],
|
|
2352
2372
|
})
|
|
2353
2373
|
}
|
|
2354
2374
|
|
|
2375
|
+
// Группы — это `optgroup`, а не подписи-разделители внутри списка:
|
|
2376
|
+
// браузер сам не даст выбрать заголовок и показывает его иначе.
|
|
2377
|
+
const body = groups === undefined
|
|
2378
|
+
? options.map((option) => jsx('option', { value: option.value, children: option.label }, option.value))
|
|
2379
|
+
: groups
|
|
2380
|
+
.filter((group) => group.options.length > 0)
|
|
2381
|
+
.map((group) => jsx('optgroup', {
|
|
2382
|
+
label: group.label,
|
|
2383
|
+
children: group.options.map((option) => jsx('option', { value: option.value, children: option.label }, option.value)),
|
|
2384
|
+
}, group.label))
|
|
2385
|
+
|
|
2355
2386
|
return jsxs('select', {
|
|
2356
2387
|
className: 'dsx-rev__select',
|
|
2357
2388
|
value: value === null ? '' : value,
|
|
@@ -2362,12 +2393,11 @@ window.__ModuleLoader__.load({
|
|
|
2362
2393
|
},
|
|
2363
2394
|
children: [
|
|
2364
2395
|
jsx('option', { value: '', children: t('review.asConfigured') }, ''),
|
|
2365
|
-
...
|
|
2396
|
+
...body,
|
|
2366
2397
|
jsx('option', { value: '\u0000custom', children: t('review.other') }, 'custom'),
|
|
2367
2398
|
],
|
|
2368
2399
|
})
|
|
2369
2400
|
}
|
|
2370
|
-
|
|
2371
2401
|
/** Класс важности: имена уровней приходят с Host по-русски. */
|
|
2372
2402
|
function severityClass(severity) {
|
|
2373
2403
|
if (severity === 'критично') return 'crit'
|
|
@@ -2385,16 +2415,19 @@ window.__ModuleLoader__.load({
|
|
|
2385
2415
|
}
|
|
2386
2416
|
// ── Ревизоры ─────────────────────────────────────────────────────────
|
|
2387
2417
|
//
|
|
2388
|
-
//
|
|
2418
|
+
// Свободный список, а не готовые режимы.
|
|
2419
|
+
//
|
|
2420
|
+
// Режимы «быстрый, обычный, глубокий» отвечали за человека на вопрос, на
|
|
2421
|
+
// который он вполне способен ответить сам, и при этом не давали позвать,
|
|
2422
|
+
// скажем, три разные модели одного семейства. Теперь: сколько угодно
|
|
2423
|
+
// записей, у каждой свой вид, модель и усилие.
|
|
2389
2424
|
//
|
|
2390
|
-
//
|
|
2391
|
-
//
|
|
2392
|
-
//
|
|
2393
|
-
// не должен её выпрашивать.
|
|
2425
|
+
// Рядом с каждой моделью показано, чем она оплачена. Это не украшение:
|
|
2426
|
+
// подписка безлимитна, а ключ API считается по токенам, и человек,
|
|
2427
|
+
// собирающий состав, должен видеть разницу ДО того, как получит счёт.
|
|
2394
2428
|
function ReviewRow({ t }) {
|
|
2395
2429
|
const [state, setState] = React.useState(null)
|
|
2396
2430
|
const [busy, setBusy] = React.useState(false)
|
|
2397
|
-
const [expanded, setExpanded] = React.useState(false)
|
|
2398
2431
|
|
|
2399
2432
|
const translate = React.useRef(t)
|
|
2400
2433
|
translate.current = t
|
|
@@ -2443,41 +2476,63 @@ window.__ModuleLoader__.load({
|
|
|
2443
2476
|
React.useEffect(() => { refresh() }, [refresh])
|
|
2444
2477
|
|
|
2445
2478
|
const catalogue = state !== null && Array.isArray(state.catalogue) ? state.catalogue : []
|
|
2446
|
-
const modes = state !== null && Array.isArray(state.modes) ? state.modes : []
|
|
2447
|
-
const settings = state !== null && state.settings ? state.settings : null
|
|
2448
2479
|
const journal = state !== null && state.journal ? state.journal : null
|
|
2480
|
+
const effortMap = state !== null && state.efforts ? state.efforts : {}
|
|
2449
2481
|
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
const
|
|
2458
|
-
|
|
2482
|
+
/**
|
|
2483
|
+
* Уровни усилия для записи.
|
|
2484
|
+
*
|
|
2485
|
+
* У наших ревизоров приходят с Host по выбранной модели; у внешних
|
|
2486
|
+
* продуктов остаются зашитыми — спросить у них нечем.
|
|
2487
|
+
*/
|
|
2488
|
+
const effortsFor = (entry) => {
|
|
2489
|
+
const spec = catalogue.find((item) => item.kind === entry.kind)
|
|
2490
|
+
if (spec && !spec.internal) return spec.efforts || []
|
|
2491
|
+
if (entry.provider === null || entry.model === null) return []
|
|
2492
|
+
const info = effortMap[entry.provider + '/' + entry.model]
|
|
2493
|
+
return info && Array.isArray(info.efforts) ? info.efforts : []
|
|
2459
2494
|
}
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2495
|
+
const reviewers = state !== null && state.settings && Array.isArray(state.settings.reviewers)
|
|
2496
|
+
? state.settings.reviewers
|
|
2497
|
+
: []
|
|
2498
|
+
|
|
2499
|
+
// Список уходит ЦЕЛИКОМ: удаление записи иначе было бы невыразимо —
|
|
2500
|
+
// слияние на стороне Host всегда возвращало бы удалённое обратно.
|
|
2501
|
+
const save = React.useCallback((next) => { refresh({ reviewers: next }) }, [refresh])
|
|
2502
|
+
|
|
2503
|
+
const patch = (id, changes) =>
|
|
2504
|
+
save(reviewers.map((entry) => (entry.id === id ? { ...entry, ...changes } : entry)))
|
|
2505
|
+
|
|
2506
|
+
const remove = (id) => save(reviewers.filter((entry) => entry.id !== id))
|
|
2507
|
+
|
|
2508
|
+
const add = (kind) => {
|
|
2509
|
+
const spec = catalogue.find((entry) => entry.kind === kind)
|
|
2510
|
+
save([...reviewers, {
|
|
2511
|
+
kind,
|
|
2512
|
+
// Умолчание берётся из справочника: пустая модель означала бы
|
|
2513
|
+
// умолчание маршрута, то есть копию уже имеющегося ревизора.
|
|
2514
|
+
provider: spec && spec.internal ? spec.defaultProvider : null,
|
|
2515
|
+
model: spec ? spec.defaultModel : null,
|
|
2516
|
+
effort: null,
|
|
2517
|
+
enabled: true,
|
|
2518
|
+
}])
|
|
2466
2519
|
}
|
|
2467
2520
|
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
save({ ...settings, choices })
|
|
2521
|
+
/**
|
|
2522
|
+
* Отметка на карточке: по подписке или за отдельные деньги.
|
|
2523
|
+
*
|
|
2524
|
+
* Ровно два состояния, как и в списке моделей. Разница между
|
|
2525
|
+
* сохранённым ключом и ключом из окружения человека не касается:
|
|
2526
|
+
* и то и другое означает счёт по токенам.
|
|
2527
|
+
*/
|
|
2528
|
+
const billingLabel = (kind) => {
|
|
2529
|
+
if (kind === undefined || kind === null) return null
|
|
2530
|
+
return kind === 'subscription' ? t('review.bySubscription') : t('review.byApiKey')
|
|
2479
2531
|
}
|
|
2480
2532
|
|
|
2533
|
+
const atLimit = reviewers.length >= 8
|
|
2534
|
+
const activeCount = reviewers.filter((entry) => entry.enabled).length
|
|
2535
|
+
|
|
2481
2536
|
return jsxs('div', {
|
|
2482
2537
|
className: 'dsx-subs',
|
|
2483
2538
|
children: [
|
|
@@ -2492,53 +2547,37 @@ window.__ModuleLoader__.load({
|
|
|
2492
2547
|
? jsx('div', { className: 'dsx-setting__hint dsx-setting__hint--warn', children: state.problem })
|
|
2493
2548
|
: null,
|
|
2494
2549
|
|
|
2495
|
-
|
|
2496
|
-
modes.length === 0 ? null : jsx('div', {
|
|
2497
|
-
className: 'dsx-rev__modes',
|
|
2498
|
-
children: modes.map((mode) => jsxs('button', {
|
|
2499
|
-
type: 'button',
|
|
2500
|
-
className: 'dsx-rev__mode' + (settings !== null && settings.mode === mode.id ? ' dsx-rev__mode--on' : ''),
|
|
2501
|
-
disabled: busy,
|
|
2502
|
-
onClick: () => save({ mode: mode.id, applyMode: true }),
|
|
2503
|
-
children: [
|
|
2504
|
-
jsx('span', { className: 'dsx-rev__mode-name', children: mode.label }),
|
|
2505
|
-
jsx('span', { className: 'dsx-rev__mode-hint', children: mode.hint }),
|
|
2506
|
-
],
|
|
2507
|
-
}, mode.id)),
|
|
2508
|
-
}),
|
|
2509
|
-
settings !== null && settings.mode === null
|
|
2510
|
-
? jsx('div', { className: 'dsx-setting__hint', children: t('review.custom') })
|
|
2511
|
-
: null,
|
|
2512
|
-
|
|
2513
|
-
// Подробные настройки
|
|
2514
|
-
jsx('button', {
|
|
2515
|
-
type: 'button',
|
|
2516
|
-
className: 'dsx-rev__toggle',
|
|
2517
|
-
onClick: () => setExpanded(!expanded),
|
|
2518
|
-
children: (expanded ? '▾ ' : '▸ ') + t('review.details'),
|
|
2519
|
-
}),
|
|
2520
|
-
|
|
2521
|
-
!expanded ? null : jsx('div', {
|
|
2550
|
+
jsx('div', {
|
|
2522
2551
|
className: 'dsx-rev__list',
|
|
2523
|
-
children:
|
|
2524
|
-
const
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
const
|
|
2528
|
-
const choice = stored || { provider: null, model: null, effort: null }
|
|
2529
|
-
const models = Array.isArray(entry.models) ? entry.models : []
|
|
2552
|
+
children: reviewers.map((entry) => {
|
|
2553
|
+
const spec = catalogue.find((item) => item.kind === entry.kind)
|
|
2554
|
+
const models = spec && Array.isArray(spec.models) ? spec.models : []
|
|
2555
|
+
const chosen = models.find((m) => m.id === entry.model)
|
|
2556
|
+
const billing = billingLabel(chosen ? chosen.billing : null)
|
|
2530
2557
|
|
|
2531
2558
|
return jsxs('div', {
|
|
2532
|
-
className: 'dsx-rev__card' + (
|
|
2559
|
+
className: 'dsx-rev__card' + (entry.enabled ? ' dsx-rev__card--on' : ''),
|
|
2533
2560
|
children: [
|
|
2534
|
-
jsxs('
|
|
2561
|
+
jsxs('div', {
|
|
2535
2562
|
className: 'dsx-rev__head',
|
|
2536
2563
|
children: [
|
|
2537
|
-
jsx('input', {
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
:
|
|
2564
|
+
jsx('input', {
|
|
2565
|
+
type: 'checkbox',
|
|
2566
|
+
checked: entry.enabled,
|
|
2567
|
+
disabled: busy,
|
|
2568
|
+
title: t('review.participates'),
|
|
2569
|
+
onChange: () => patch(entry.id, { enabled: !entry.enabled }),
|
|
2570
|
+
}),
|
|
2571
|
+
jsx('span', { className: 'dsx-rev__name', children: spec ? spec.label : entry.kind }),
|
|
2572
|
+
billing === null ? null : jsx('span', { className: 'dsx-rev__tag', children: billing }),
|
|
2573
|
+
jsx('button', {
|
|
2574
|
+
type: 'button',
|
|
2575
|
+
className: 'dsx-rev__remove',
|
|
2576
|
+
disabled: busy,
|
|
2577
|
+
title: t('review.remove'),
|
|
2578
|
+
onClick: () => remove(entry.id),
|
|
2579
|
+
children: '\u00d7',
|
|
2580
|
+
}),
|
|
2542
2581
|
],
|
|
2543
2582
|
}),
|
|
2544
2583
|
jsxs('div', {
|
|
@@ -2550,16 +2589,42 @@ window.__ModuleLoader__.load({
|
|
|
2550
2589
|
jsx('span', { children: t('review.model') }),
|
|
2551
2590
|
jsx(ValuePicker, {
|
|
2552
2591
|
t: t,
|
|
2553
|
-
value:
|
|
2592
|
+
value: entry.model,
|
|
2554
2593
|
disabled: busy,
|
|
2555
2594
|
placeholder: t('review.modelName'),
|
|
2556
|
-
options: models.map((m) => ({
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2595
|
+
options: models.map((m) => ({ value: m.id, label: m.id })),
|
|
2596
|
+
// РАЗДЕЛЕНИЕ ПО ОПЛАТЕ.
|
|
2597
|
+
//
|
|
2598
|
+
// Один и тот же claude-opus-5 доступен и по
|
|
2599
|
+
// подписке, и через OpenRouter по токенам. В общем
|
|
2600
|
+
// списке они неразличимы, а разница в том, придёт
|
|
2601
|
+
// ли за них счёт. Провайдер стоит в названии: у
|
|
2602
|
+
// OpenRouter сотни моделей, и без него не понять,
|
|
2603
|
+
// чья она.
|
|
2604
|
+
// Групп ДВЕ, а не три. Различие, которое важно
|
|
2605
|
+
// человеку, одно: придёт ли за эту модель
|
|
2606
|
+
// отдельный счёт. Ключ, сохранённый в dsh, и
|
|
2607
|
+
// ключ из окружения — с этой стороны одно и то
|
|
2608
|
+
// же, а третья группа оказывалась почти пустой и
|
|
2609
|
+
// только мешала выбирать.
|
|
2610
|
+
groups: [
|
|
2611
|
+
{
|
|
2612
|
+
label: t('review.groupSubscription'),
|
|
2613
|
+
options: models.filter((m) => m.billing === 'subscription')
|
|
2614
|
+
.map((m) => ({ value: m.id, label: m.provider + ' \u00b7 ' + m.id })),
|
|
2615
|
+
},
|
|
2616
|
+
{
|
|
2617
|
+
label: t('review.groupPaid'),
|
|
2618
|
+
options: models.filter((m) => m.billing !== 'subscription')
|
|
2619
|
+
.map((m) => ({ value: m.id, label: (m.provider === null ? '' : m.provider + ' \u00b7 ') + m.id })),
|
|
2620
|
+
},
|
|
2621
|
+
],
|
|
2560
2622
|
onPick: (value) => {
|
|
2561
2623
|
const found = models.find((m) => m.id === value)
|
|
2562
|
-
|
|
2624
|
+
patch(entry.id, {
|
|
2625
|
+
provider: value === null ? null : (found ? found.provider : entry.provider),
|
|
2626
|
+
model: value,
|
|
2627
|
+
})
|
|
2563
2628
|
},
|
|
2564
2629
|
}),
|
|
2565
2630
|
],
|
|
@@ -2570,21 +2635,52 @@ window.__ModuleLoader__.load({
|
|
|
2570
2635
|
jsx('span', { children: t('review.effort') }),
|
|
2571
2636
|
jsx(ValuePicker, {
|
|
2572
2637
|
t: t,
|
|
2573
|
-
value:
|
|
2638
|
+
value: entry.effort,
|
|
2574
2639
|
disabled: busy,
|
|
2575
2640
|
placeholder: t('review.effortName'),
|
|
2576
|
-
|
|
2577
|
-
|
|
2641
|
+
// Уровни у КАЖДОЙ МОДЕЛИ свои, и зашитый список
|
|
2642
|
+
// врал почти везде. Проверено: у claude-opus-5 их
|
|
2643
|
+
// шесть, у deepseek-v4-pro — три, а одна и та же
|
|
2644
|
+
// модель через подписку и через OpenRouter даёт
|
|
2645
|
+
// разные наборы.
|
|
2646
|
+
//
|
|
2647
|
+
// Пока модель не выбрана, спрашивать нечего:
|
|
2648
|
+
// остаётся ввод своего значения.
|
|
2649
|
+
options: effortsFor(entry).map((e) => ({ value: e, label: e })),
|
|
2650
|
+
onPick: (value) => patch(entry.id, { effort: value }),
|
|
2578
2651
|
}),
|
|
2579
2652
|
],
|
|
2580
2653
|
}),
|
|
2581
2654
|
],
|
|
2582
2655
|
}),
|
|
2583
2656
|
],
|
|
2584
|
-
}, entry.
|
|
2657
|
+
}, entry.id)
|
|
2585
2658
|
}),
|
|
2586
2659
|
}),
|
|
2587
2660
|
|
|
2661
|
+
// Добавление: по кнопке на каждый вид ревизора. Внешние продукты
|
|
2662
|
+
// помечены — они работают, только если установлены отдельно.
|
|
2663
|
+
jsxs('div', {
|
|
2664
|
+
className: 'dsx-rev__add',
|
|
2665
|
+
children: [
|
|
2666
|
+
jsx('span', { className: 'dsx-rev__add-label', children: t('review.add') }),
|
|
2667
|
+
...catalogue.map((spec) => jsx('button', {
|
|
2668
|
+
type: 'button',
|
|
2669
|
+
className: 'dsx-rev__add-btn',
|
|
2670
|
+
disabled: busy || atLimit,
|
|
2671
|
+
title: spec.optional ? t('review.needsProduct') : '',
|
|
2672
|
+
onClick: () => add(spec.kind),
|
|
2673
|
+
children: spec.label + (spec.optional ? ' \u2217' : ''),
|
|
2674
|
+
}, spec.kind)),
|
|
2675
|
+
],
|
|
2676
|
+
}),
|
|
2677
|
+
atLimit ? jsx('div', { className: 'dsx-setting__hint', children: t('review.limit') }) : null,
|
|
2678
|
+
|
|
2679
|
+
reviewers.length === 0
|
|
2680
|
+
? jsx('div', { className: 'dsx-setting__hint dsx-setting__hint--warn', children: t('review.none') })
|
|
2681
|
+
: activeCount === 0
|
|
2682
|
+
? jsx('div', { className: 'dsx-setting__hint dsx-setting__hint--warn', children: t('review.noneActive') })
|
|
2683
|
+
: null,
|
|
2588
2684
|
// ── Панель находок ──────────────────────────────────────────
|
|
2589
2685
|
//
|
|
2590
2686
|
// Показывает последний обзор: что нашли ревизоры и что из этого
|
|
@@ -2680,9 +2776,6 @@ window.__ModuleLoader__.load({
|
|
|
2680
2776
|
}),
|
|
2681
2777
|
],
|
|
2682
2778
|
}),
|
|
2683
|
-
settings !== null && settings.enabled.length === 0
|
|
2684
|
-
? jsx('div', { className: 'dsx-setting__hint dsx-setting__hint--warn', children: t('review.none') })
|
|
2685
|
-
: null,
|
|
2686
2779
|
jsx('div', { className: 'dsx-subs__note', children: t('review.note') }),
|
|
2687
2780
|
],
|
|
2688
2781
|
})
|