tensorgrid-ui 2.0.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 +83 -20
- package/lib/http-runtime.js +67 -4
- package/lib/reviewers.js +7 -2
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -198,6 +198,8 @@ window.__ModuleLoader__.load({
|
|
|
198
198
|
'review.bySubscription': 'subscription',
|
|
199
199
|
'review.limit': 'That is as many reviewers as one review can hold.',
|
|
200
200
|
'review.byApiKey': 'paid per token',
|
|
201
|
+
'review.groupSubscription': 'Included in a subscription',
|
|
202
|
+
'review.groupPaid': 'Paid separately \u2014 per token',
|
|
201
203
|
'review.nav': 'Reviewers',
|
|
202
204
|
'review.title': 'Second opinion',
|
|
203
205
|
'review.hint': 'Independent reviewers examine finished work and report what is wrong. Choose how many and which ones: different models find different things.',
|
|
@@ -274,6 +276,8 @@ window.__ModuleLoader__.load({
|
|
|
274
276
|
'review.bySubscription': '订阅',
|
|
275
277
|
'review.limit': '一次审阅最多只能有这么多审阅者。',
|
|
276
278
|
'review.byApiKey': '按量计费',
|
|
279
|
+
'review.groupSubscription': '包含在订阅中',
|
|
280
|
+
'review.groupPaid': '单独计费 \u2014 按量',
|
|
277
281
|
'review.nav': '审阅者',
|
|
278
282
|
'review.title': '第二意见',
|
|
279
283
|
'review.hint': '独立审阅者检查已完成的工作并报告问题。自行决定数量与人选:不同模型发现的问题不同。',
|
|
@@ -1647,6 +1651,8 @@ window.__ModuleLoader__.load({
|
|
|
1647
1651
|
'review.bySubscription': 'по подписке',
|
|
1648
1652
|
'review.limit': 'Больше ревизоров в один обзор не поместится.',
|
|
1649
1653
|
'review.byApiKey': 'по токенам',
|
|
1654
|
+
'review.groupSubscription': 'Входят в подписку',
|
|
1655
|
+
'review.groupPaid': 'Оплата отдельно \u2014 по токенам',
|
|
1650
1656
|
'review.nav': 'Ревизоры',
|
|
1651
1657
|
'review.title': 'Второе мнение',
|
|
1652
1658
|
'review.hint': 'Независимые ревизоры смотрят готовую работу и сообщают, что в ней не так. Сколько их и какие — выбираете вы: разные модели находят разное.',
|
|
@@ -2333,13 +2339,12 @@ window.__ModuleLoader__.load({
|
|
|
2333
2339
|
/**
|
|
2334
2340
|
* Выбор значения: известные варианты плюс «своё».
|
|
2335
2341
|
*
|
|
2336
|
-
*
|
|
2337
|
-
*
|
|
2338
|
-
*
|
|
2339
|
-
*
|
|
2340
|
-
* интерфейс бесполезным до следующего обновления пакета.
|
|
2342
|
+
* `groups` разносит варианты по подписям. Для моделей это принципиально:
|
|
2343
|
+
* один и тот же `claude-opus-5` доступен и по подписке, и через
|
|
2344
|
+
* OpenRouter по токенам, и вперемешку они неразличимы — а разница в том,
|
|
2345
|
+
* придёт ли за них счёт.
|
|
2341
2346
|
*/
|
|
2342
|
-
function ValuePicker({ t, value, options, disabled, onPick, placeholder }) {
|
|
2347
|
+
function ValuePicker({ t, value, options, disabled, onPick, placeholder, groups }) {
|
|
2343
2348
|
const known = options.some((option) => option.value === value)
|
|
2344
2349
|
const [custom, setCustom] = React.useState(value !== null && !known)
|
|
2345
2350
|
|
|
@@ -2367,6 +2372,17 @@ window.__ModuleLoader__.load({
|
|
|
2367
2372
|
})
|
|
2368
2373
|
}
|
|
2369
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
|
+
|
|
2370
2386
|
return jsxs('select', {
|
|
2371
2387
|
className: 'dsx-rev__select',
|
|
2372
2388
|
value: value === null ? '' : value,
|
|
@@ -2377,7 +2393,7 @@ window.__ModuleLoader__.load({
|
|
|
2377
2393
|
},
|
|
2378
2394
|
children: [
|
|
2379
2395
|
jsx('option', { value: '', children: t('review.asConfigured') }, ''),
|
|
2380
|
-
...
|
|
2396
|
+
...body,
|
|
2381
2397
|
jsx('option', { value: '\u0000custom', children: t('review.other') }, 'custom'),
|
|
2382
2398
|
],
|
|
2383
2399
|
})
|
|
@@ -2461,6 +2477,21 @@ window.__ModuleLoader__.load({
|
|
|
2461
2477
|
|
|
2462
2478
|
const catalogue = state !== null && Array.isArray(state.catalogue) ? state.catalogue : []
|
|
2463
2479
|
const journal = state !== null && state.journal ? state.journal : null
|
|
2480
|
+
const effortMap = state !== null && state.efforts ? state.efforts : {}
|
|
2481
|
+
|
|
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 : []
|
|
2494
|
+
}
|
|
2464
2495
|
const reviewers = state !== null && state.settings && Array.isArray(state.settings.reviewers)
|
|
2465
2496
|
? state.settings.reviewers
|
|
2466
2497
|
: []
|
|
@@ -2487,10 +2518,16 @@ window.__ModuleLoader__.load({
|
|
|
2487
2518
|
}])
|
|
2488
2519
|
}
|
|
2489
2520
|
|
|
2521
|
+
/**
|
|
2522
|
+
* Отметка на карточке: по подписке или за отдельные деньги.
|
|
2523
|
+
*
|
|
2524
|
+
* Ровно два состояния, как и в списке моделей. Разница между
|
|
2525
|
+
* сохранённым ключом и ключом из окружения человека не касается:
|
|
2526
|
+
* и то и другое означает счёт по токенам.
|
|
2527
|
+
*/
|
|
2490
2528
|
const billingLabel = (kind) => {
|
|
2491
|
-
if (kind ===
|
|
2492
|
-
|
|
2493
|
-
return null
|
|
2529
|
+
if (kind === undefined || kind === null) return null
|
|
2530
|
+
return kind === 'subscription' ? t('review.bySubscription') : t('review.byApiKey')
|
|
2494
2531
|
}
|
|
2495
2532
|
|
|
2496
2533
|
const atLimit = reviewers.length >= 8
|
|
@@ -2555,15 +2592,33 @@ window.__ModuleLoader__.load({
|
|
|
2555
2592
|
value: entry.model,
|
|
2556
2593
|
disabled: busy,
|
|
2557
2594
|
placeholder: t('review.modelName'),
|
|
2558
|
-
options: models.map((m) => ({
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
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
|
+
],
|
|
2567
2622
|
onPick: (value) => {
|
|
2568
2623
|
const found = models.find((m) => m.id === value)
|
|
2569
2624
|
patch(entry.id, {
|
|
@@ -2583,7 +2638,15 @@ window.__ModuleLoader__.load({
|
|
|
2583
2638
|
value: entry.effort,
|
|
2584
2639
|
disabled: busy,
|
|
2585
2640
|
placeholder: t('review.effortName'),
|
|
2586
|
-
|
|
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 })),
|
|
2587
2650
|
onPick: (value) => patch(entry.id, { effort: value }),
|
|
2588
2651
|
}),
|
|
2589
2652
|
],
|
package/lib/http-runtime.js
CHANGED
|
@@ -215,6 +215,43 @@ export async function handleUpdate(req, res, state, loadModule) {
|
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Уровни усилия ДЛЯ КОНКРЕТНОЙ МОДЕЛИ.
|
|
220
|
+
*
|
|
221
|
+
* Зашитый список был неверен почти для всех. Проверено на живых данных:
|
|
222
|
+
*
|
|
223
|
+
* anthropic/claude-opus-5 minimal, low, medium, high, xhigh, max
|
|
224
|
+
* anthropic/claude-sonnet-4-5 off, minimal, low, medium, high
|
|
225
|
+
* openrouter/x-ai/grok-4.6 low, medium, high, xhigh
|
|
226
|
+
* openrouter/deepseek-v4-pro off, high, xhigh
|
|
227
|
+
*
|
|
228
|
+
* Мало того, ОДНА И ТА ЖЕ модель через подписку и через OpenRouter даёт
|
|
229
|
+
* разные наборы: у `claude-opus-5` в подписке есть `minimal`, а через
|
|
230
|
+
* OpenRouter его нет. Это разные маршруты, и общего списка тут быть не может.
|
|
231
|
+
*
|
|
232
|
+
* Спрашивается только для ВЫБРАННЫХ моделей — их единицы. Обойти так все
|
|
233
|
+
* 366 моделей OpenRouter значило бы держать открытие настроек минутами.
|
|
234
|
+
*/
|
|
235
|
+
async function modelEfforts(llm, provider, model) {
|
|
236
|
+
if (llm === null || llm === undefined || typeof llm.resolveModelInfo !== 'function') return null
|
|
237
|
+
if (typeof provider !== 'string' || provider === '' || typeof model !== 'string' || model === '') return null
|
|
238
|
+
try {
|
|
239
|
+
const info = await llm.resolveModelInfo(provider, model)
|
|
240
|
+
const efforts = info?.reasoning?.efforts
|
|
241
|
+
if (!Array.isArray(efforts) || efforts.length === 0) return null
|
|
242
|
+
return {
|
|
243
|
+
efforts: efforts
|
|
244
|
+
.map((entry) => (typeof entry === 'string' ? entry : String(entry?.id ?? entry?.name ?? '')))
|
|
245
|
+
.filter((id) => id !== ''),
|
|
246
|
+
defaultEffort: info.reasoning.defaultEffort === undefined ? null : String(info.reasoning.defaultEffort),
|
|
247
|
+
}
|
|
248
|
+
} catch {
|
|
249
|
+
// Модель могла исчезнуть или провайдер не ответить — покажем без списка,
|
|
250
|
+
// и человек впишет своё значение вручную.
|
|
251
|
+
return null
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
218
255
|
/**
|
|
219
256
|
* Собрать модели, доступные на маршруте.
|
|
220
257
|
*
|
|
@@ -310,11 +347,33 @@ export async function handleReview(req, res, loadModule, services) {
|
|
|
310
347
|
internal: spec.kind === 'internal',
|
|
311
348
|
optional: spec.optional === true,
|
|
312
349
|
models: spec.kind === 'internal' ? routeModels : spec.models.map((id) => ({ provider: null, id, billing: 'external' })),
|
|
313
|
-
|
|
350
|
+
// Для ВНЕШНИХ продуктов список остаётся зашитым: спросить у Claude Code
|
|
351
|
+
// или Codex их уровни нечем, они не наш маршрут.
|
|
352
|
+
efforts: spec.kind === 'internal' ? [] : spec.efforts,
|
|
314
353
|
defaultProvider: spec.defaultProvider ?? null,
|
|
315
354
|
defaultModel: spec.defaultModel ?? null,
|
|
316
355
|
}))
|
|
317
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Уровни усилия для тех моделей, что УЖЕ выбраны.
|
|
359
|
+
*
|
|
360
|
+
* Их единицы, и спросить их дёшево. Обходить так все 366 моделей
|
|
361
|
+
* OpenRouter значило бы держать открытие настроек минутами.
|
|
362
|
+
*/
|
|
363
|
+
const effortsFor = async (chosen) => {
|
|
364
|
+
const map = {}
|
|
365
|
+
const seen = new Set()
|
|
366
|
+
for (const entry of chosen.reviewers ?? []) {
|
|
367
|
+
if (entry.provider === null || entry.model === null) continue
|
|
368
|
+
const key = `${entry.provider}/${entry.model}`
|
|
369
|
+
if (seen.has(key)) continue
|
|
370
|
+
seen.add(key)
|
|
371
|
+
const info = await modelEfforts(services?.llm, entry.provider, entry.model)
|
|
372
|
+
if (info !== null) map[key] = info
|
|
373
|
+
}
|
|
374
|
+
return map
|
|
375
|
+
}
|
|
376
|
+
|
|
318
377
|
// Журнал отдаётся вместе с настройками: панель находок и настройки —
|
|
319
378
|
// один раздел, и второй запрос за тем же экраном был бы лишней задержкой.
|
|
320
379
|
const log = await loadModule('./review-log.js')
|
|
@@ -331,7 +390,8 @@ export async function handleReview(req, res, loadModule, services) {
|
|
|
331
390
|
}
|
|
332
391
|
|
|
333
392
|
if (req.method === 'GET') {
|
|
334
|
-
|
|
393
|
+
const current = await settings.readSettings(null)
|
|
394
|
+
reply(res, 200, { catalogue, settings: current, efforts: await effortsFor(current), journal: digest })
|
|
335
395
|
return
|
|
336
396
|
}
|
|
337
397
|
|
|
@@ -367,9 +427,11 @@ export async function handleReview(req, res, loadModule, services) {
|
|
|
367
427
|
'human',
|
|
368
428
|
)
|
|
369
429
|
const after = await log.read()
|
|
430
|
+
const current = await settings.readSettings(null)
|
|
370
431
|
reply(res, 200, {
|
|
371
432
|
catalogue,
|
|
372
|
-
settings:
|
|
433
|
+
settings: current,
|
|
434
|
+
efforts: await effortsFor(current),
|
|
373
435
|
journal: { latest: log.latest(after), accuracy: log.accuracy(after), runs: after.runs.length },
|
|
374
436
|
// Отклонённое возражение должно быть ВИДНО: иначе человек считает,
|
|
375
437
|
// что высказался, а в панели ничего не изменилось.
|
|
@@ -381,7 +443,8 @@ export async function handleReview(req, res, loadModule, services) {
|
|
|
381
443
|
// Список приходит целиком и целиком же замещает сохранённый. Для списка
|
|
382
444
|
// это единственно верно: слияние возвращало бы удалённую запись обратно,
|
|
383
445
|
// то есть удалить ревизора стало бы нельзя.
|
|
384
|
-
|
|
446
|
+
const saved = await settings.writeSettings(null, body)
|
|
447
|
+
reply(res, 200, { catalogue, settings: saved, efforts: await effortsFor(saved), journal: digest })
|
|
385
448
|
} catch (error) {
|
|
386
449
|
reply(res, 500, { problem: String(error?.message ?? error) })
|
|
387
450
|
}
|
package/lib/reviewers.js
CHANGED
|
@@ -63,7 +63,12 @@ export const REVIEWERS = {
|
|
|
63
63
|
label: 'Наш ревизор',
|
|
64
64
|
kind: 'internal',
|
|
65
65
|
models: [],
|
|
66
|
-
|
|
66
|
+
// Уровни усилия НЕ перечисляются: у каждой модели они свои, и зашитый
|
|
67
|
+
// список врал почти везде. Проверено на живых данных: у claude-opus-5
|
|
68
|
+
// их шесть, у deepseek-v4-pro — три, а одна и та же модель через
|
|
69
|
+
// подписку и через OpenRouter даёт РАЗНЫЕ наборы. Их спрашивают у
|
|
70
|
+
// маршрута по выбранной модели.
|
|
71
|
+
efforts: [],
|
|
67
72
|
defaultProvider: 'anthropic',
|
|
68
73
|
defaultModel: 'claude-opus-5',
|
|
69
74
|
},
|
|
@@ -71,7 +76,7 @@ export const REVIEWERS = {
|
|
|
71
76
|
label: 'Наш ревизор, вторая модель',
|
|
72
77
|
kind: 'internal',
|
|
73
78
|
models: [],
|
|
74
|
-
efforts: [
|
|
79
|
+
efforts: [],
|
|
75
80
|
// Намеренно ДРУГОЕ семейство: два прогона одной модели дадут почти
|
|
76
81
|
// одинаковые находки, и второй ревизор не окупит своего времени.
|
|
77
82
|
defaultProvider: 'openai-codex',
|
package/package.json
CHANGED