dsh-switchman 1.7.0 → 1.8.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/README.de.md +25 -4
- package/README.es.md +25 -4
- package/README.fr.md +25 -4
- package/README.it.md +25 -4
- package/README.ja.md +25 -4
- package/README.ko.md +25 -4
- package/README.md +26 -4
- package/README.pt.md +25 -4
- package/README.ru.md +25 -4
- package/README.zh-TW.md +25 -4
- package/README.zh.md +26 -4
- package/client.js +1916 -35
- package/host/config.js +93 -4
- package/host/context-watch.js +126 -58
- package/host/intent-presets.js +163 -0
- package/host/intent.js +952 -0
- package/host/routes.js +158 -3
- package/host/vision.js +62 -26
- package/index.js +18 -2
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +3 -3
package/client.js
CHANGED
|
@@ -32,6 +32,33 @@ window.__ModuleLoader__.load({
|
|
|
32
32
|
const React = require('react');
|
|
33
33
|
const h = React.createElement;
|
|
34
34
|
|
|
35
|
+
// ------------------------------------------------------------------
|
|
36
|
+
// Pin the "Auto" group to the top of the app's model pickers.
|
|
37
|
+
//
|
|
38
|
+
// The composer/command pickers (ui-model-selection ModelSelect and
|
|
39
|
+
// PopupSelectView) order provider groups with a compiled-in
|
|
40
|
+
// comparator (`provider-order.ts`) that pins only the deepseek ids
|
|
41
|
+
// and leaves every other group in catalog registration order — which
|
|
42
|
+
// lands our pseudo provider (registered last, by a plugin) at the
|
|
43
|
+
// bottom. There is no extension point, and the asar cannot be
|
|
44
|
+
// patched (ElectronAsarIntegrity), but the comparator reaches us
|
|
45
|
+
// through Array.prototype.toSorted: wrap it and pre-pin our group
|
|
46
|
+
// whenever THAT comparator (identified by its source) is used.
|
|
47
|
+
// Every other toSorted call passes through untouched.
|
|
48
|
+
// ------------------------------------------------------------------
|
|
49
|
+
(function pinAutoGroupTop() {
|
|
50
|
+
const original = Array.prototype.toSorted;
|
|
51
|
+
if (typeof original !== 'function') return;
|
|
52
|
+
Array.prototype.toSorted = function (comparator, ...rest) {
|
|
53
|
+
if (typeof comparator === 'function' && String(comparator).includes('deepseek-official')) {
|
|
54
|
+
return original.call(this, (left, right) =>
|
|
55
|
+
((left?.id === 'dsh-switchman-auto' ? -1 : 0) - (right?.id === 'dsh-switchman-auto' ? -1 : 0))
|
|
56
|
+
|| comparator(left, right), ...rest);
|
|
57
|
+
}
|
|
58
|
+
return original.call(this, comparator, ...rest);
|
|
59
|
+
};
|
|
60
|
+
})();
|
|
61
|
+
|
|
35
62
|
/** Locale namespace shared by every dsh-switchman client surface. */
|
|
36
63
|
const NS = 'dsh-switchman';
|
|
37
64
|
|
|
@@ -166,10 +193,10 @@ window.__ModuleLoader__.load({
|
|
|
166
193
|
/** Numeric watermark fields: settings field, schema minimum, and the
|
|
167
194
|
* Host-side default (also the fallback for malformed stored values). */
|
|
168
195
|
const WM_NUMBER_FIELDS = [
|
|
169
|
-
{ field: 'wmSoftTokens', min: 1000, def:
|
|
170
|
-
{ field: 'wmHardTokens', min: 2000, def:
|
|
171
|
-
{ field: 'wmForceTokens', min: 3000, def:
|
|
172
|
-
{ field: 'wmReadBudgetTokens', min: 200, def:
|
|
196
|
+
{ field: 'wmSoftTokens', min: 1000, def: 80000 },
|
|
197
|
+
{ field: 'wmHardTokens', min: 2000, def: 120000 },
|
|
198
|
+
{ field: 'wmForceTokens', min: 3000, def: 150000 },
|
|
199
|
+
{ field: 'wmReadBudgetTokens', min: 200, def: 3000 },
|
|
173
200
|
{ field: 'wmSubagentForceTokens', min: 0, def: 0 },
|
|
174
201
|
];
|
|
175
202
|
|
|
@@ -188,6 +215,48 @@ window.__ModuleLoader__.load({
|
|
|
188
215
|
},
|
|
189
216
|
};
|
|
190
217
|
|
|
218
|
+
/** Intent-routing classifier kinds (design: intent-routing.md §2;
|
|
219
|
+
* dictionary keys intentClassifierKind + Chat/Systemone). Two
|
|
220
|
+
* explicit channels: chat = llm catalog route, systemone =
|
|
221
|
+
* hand-configured decision endpoint + model. */
|
|
222
|
+
const INTENT_KINDS = ['chat', 'systemone'];
|
|
223
|
+
|
|
224
|
+
/** Numeric intent fields: settings field, schema minimum, and the
|
|
225
|
+
* Host-side default (also the fallback for malformed stored values). */
|
|
226
|
+
const INTENT_NUMBER_FIELDS = [
|
|
227
|
+
{ field: 'intentCacheTtlSeconds', min: 0, max: Infinity, def: 300 },
|
|
228
|
+
{ field: 'intentSwitchConfidence', min: 0, max: 100, def: 80 },
|
|
229
|
+
{ field: 'intentTimeoutMs', min: 200, max: Infinity, def: 60000 },
|
|
230
|
+
{ field: 'intentHistoryMessages', min: 1, max: 20, def: 6 },
|
|
231
|
+
{ field: 'intentHistoryMaxChars', min: 200, max: 20000, def: 2400 },
|
|
232
|
+
{ field: 'intentContextWindow', min: 1024, max: Infinity, def: 200000 },
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
/** Dictionary keys per numeric intent row. */
|
|
236
|
+
const INTENT_ROW_KEYS = {
|
|
237
|
+
intentCacheTtlSeconds: {
|
|
238
|
+
label: 'intentCacheTtlLabel',
|
|
239
|
+
help: 'intentCacheTtlHelp',
|
|
240
|
+
},
|
|
241
|
+
intentSwitchConfidence: {
|
|
242
|
+
label: 'intentSwitchConfidenceLabel',
|
|
243
|
+
help: 'intentSwitchConfidenceHelp',
|
|
244
|
+
},
|
|
245
|
+
intentTimeoutMs: { label: 'intentTimeoutLabel', help: 'intentTimeoutHelp' },
|
|
246
|
+
intentHistoryMessages: {
|
|
247
|
+
label: 'intentHistoryMessagesLabel',
|
|
248
|
+
help: 'intentHistoryMessagesHelp',
|
|
249
|
+
},
|
|
250
|
+
intentHistoryMaxChars: {
|
|
251
|
+
label: 'intentHistoryMaxCharsLabel',
|
|
252
|
+
help: 'intentHistoryMaxCharsHelp',
|
|
253
|
+
},
|
|
254
|
+
intentContextWindow: {
|
|
255
|
+
label: 'intentContextWindowLabel',
|
|
256
|
+
help: 'intentContextWindowHelp',
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
|
|
191
260
|
/** Every settings field this page edits, in mutate order. */
|
|
192
261
|
const ALL_FIELDS = [
|
|
193
262
|
'uiLocale',
|
|
@@ -203,6 +272,16 @@ window.__ModuleLoader__.load({
|
|
|
203
272
|
'wmSubagentCap',
|
|
204
273
|
'teamsMode',
|
|
205
274
|
'syncWhitelist',
|
|
275
|
+
// Intent recognition (design: intent-routing.md §2).
|
|
276
|
+
'intentEnabled',
|
|
277
|
+
'intentClassifierRoute',
|
|
278
|
+
'intentClassifierEffort',
|
|
279
|
+
'intentClassifierKind',
|
|
280
|
+
'intentClassifierEndpoint',
|
|
281
|
+
'intentClassifierSystemoneModel',
|
|
282
|
+
'intentCandidates',
|
|
283
|
+
...INTENT_NUMBER_FIELDS.map(({ field }) => field),
|
|
284
|
+
'intentFallbackRoute',
|
|
206
285
|
];
|
|
207
286
|
|
|
208
287
|
/** Stable identity of one provider/model route. */
|
|
@@ -240,6 +319,37 @@ window.__ModuleLoader__.load({
|
|
|
240
319
|
: null;
|
|
241
320
|
}
|
|
242
321
|
|
|
322
|
+
/** One well-formed intent candidate ({provider, model[, effort][,
|
|
323
|
+
* note]}) — the design's IntentCandidateSchema — or null; blank
|
|
324
|
+
* effort/note fields are dropped rather than kept. */
|
|
325
|
+
function sanitizeIntentCandidate(entry) {
|
|
326
|
+
if (entry === null || typeof entry !== 'object') return null;
|
|
327
|
+
const provider =
|
|
328
|
+
typeof entry.provider === 'string' ? entry.provider.trim() : '';
|
|
329
|
+
const model = typeof entry.model === 'string' ? entry.model.trim() : '';
|
|
330
|
+
const id =
|
|
331
|
+
typeof entry.id === 'string' && entry.id.trim() !== ''
|
|
332
|
+
? entry.id.trim()
|
|
333
|
+
: '';
|
|
334
|
+
// Only identified rows (preset or custom) may live without a
|
|
335
|
+
// route — a preset still waiting for its model must survive the
|
|
336
|
+
// snapshot round-trip instead of being dropped like junk.
|
|
337
|
+
if (id === '' && (provider === '' || model === '')) return null;
|
|
338
|
+
const candidate = {
|
|
339
|
+
id,
|
|
340
|
+
preset: entry.preset === true,
|
|
341
|
+
enabled: entry.enabled !== false,
|
|
342
|
+
provider,
|
|
343
|
+
model,
|
|
344
|
+
note: typeof entry.note === 'string' ? entry.note : '',
|
|
345
|
+
};
|
|
346
|
+
if (typeof entry.label === 'string' && entry.label !== '')
|
|
347
|
+
candidate.label = entry.label;
|
|
348
|
+
if (typeof entry.effort === 'string' && entry.effort !== '')
|
|
349
|
+
candidate.effort = entry.effort;
|
|
350
|
+
return candidate;
|
|
351
|
+
}
|
|
352
|
+
|
|
243
353
|
/** Is `value` one of the predefined candidates? */
|
|
244
354
|
function isCandidate(value) {
|
|
245
355
|
return LANGUAGES.some((language) => language.value === value);
|
|
@@ -327,6 +437,30 @@ window.__ModuleLoader__.load({
|
|
|
327
437
|
wmSubagentCap: bool('wmSubagentCap', true),
|
|
328
438
|
teamsMode: bool('teamsMode', false),
|
|
329
439
|
syncWhitelist: bool('syncWhitelist', false),
|
|
440
|
+
// Intent recognition (design: intent-routing.md §2): route
|
|
441
|
+
// objects degrade to the frozen empty-route default; the
|
|
442
|
+
// candidate array follows the pool-array sanitizers.
|
|
443
|
+
intentEnabled: bool('intentEnabled', false),
|
|
444
|
+
intentClassifierRoute:
|
|
445
|
+
sanitizeRoute(value?.intentClassifierRoute) ??
|
|
446
|
+
({ provider: '', model: '' }),
|
|
447
|
+
intentClassifierEffort: string('intentClassifierEffort'),
|
|
448
|
+
intentClassifierKind: INTENT_KINDS.includes(
|
|
449
|
+
value?.intentClassifierKind,
|
|
450
|
+
)
|
|
451
|
+
? value.intentClassifierKind
|
|
452
|
+
: 'chat',
|
|
453
|
+
intentClassifierEndpoint: string('intentClassifierEndpoint'),
|
|
454
|
+
intentClassifierSystemoneModel: string('intentClassifierSystemoneModel'),
|
|
455
|
+
intentCandidates: Array.isArray(value?.intentCandidates)
|
|
456
|
+
? value.intentCandidates.map(sanitizeIntentCandidate).filter(Boolean)
|
|
457
|
+
: [],
|
|
458
|
+
...Object.fromEntries(
|
|
459
|
+
INTENT_NUMBER_FIELDS.map((spec) => [spec.field, number(spec)]),
|
|
460
|
+
),
|
|
461
|
+
intentFallbackRoute:
|
|
462
|
+
sanitizeRoute(value?.intentFallbackRoute) ??
|
|
463
|
+
({ provider: '', model: '' }),
|
|
330
464
|
};
|
|
331
465
|
}
|
|
332
466
|
|
|
@@ -377,6 +511,25 @@ window.__ModuleLoader__.load({
|
|
|
377
511
|
wmSubagentCap: values.wmSubagentCap,
|
|
378
512
|
teamsMode: values.teamsMode,
|
|
379
513
|
syncWhitelist: values.syncWhitelist,
|
|
514
|
+
// Intent recognition: routes compare as route keys, the
|
|
515
|
+
// candidate array as an ordered sequence (list order is the
|
|
516
|
+
// classifier's #1..#n numbering), everything else verbatim.
|
|
517
|
+
intentEnabled: values.intentEnabled,
|
|
518
|
+
intentClassifierRoute: routeKey(values.intentClassifierRoute),
|
|
519
|
+
intentClassifierEffort: values.intentClassifierEffort,
|
|
520
|
+
intentClassifierKind: values.intentClassifierKind,
|
|
521
|
+
intentClassifierEndpoint: values.intentClassifierEndpoint,
|
|
522
|
+
intentClassifierSystemoneModel: values.intentClassifierSystemoneModel,
|
|
523
|
+
intentCandidates: values.intentCandidates
|
|
524
|
+
.map(
|
|
525
|
+
(entry) =>
|
|
526
|
+
`${entry.id ?? ''}\0${entry.enabled === false ? '0' : '1'}\0${routeKey(entry)}\0${entry.effort ?? ''}\0${entry.note ?? ''}`,
|
|
527
|
+
)
|
|
528
|
+
.join('|'),
|
|
529
|
+
...Object.fromEntries(
|
|
530
|
+
INTENT_NUMBER_FIELDS.map(({ field }) => [field, values[field]]),
|
|
531
|
+
),
|
|
532
|
+
intentFallbackRoute: routeKey(values.intentFallbackRoute),
|
|
380
533
|
});
|
|
381
534
|
}
|
|
382
535
|
|
|
@@ -388,10 +541,15 @@ window.__ModuleLoader__.load({
|
|
|
388
541
|
/** Index one catalog answer: route key → display metadata. Models are
|
|
389
542
|
* keyed by `id` (the sessionController wire shape: {id, name}) with
|
|
390
543
|
* the group id as provider; the flattened `model` shape is accepted
|
|
391
|
-
* too.
|
|
544
|
+
* too. The switchman pseudo "Auto" group pins to the top (stable
|
|
545
|
+
* sort keeps every other group in wire order). */
|
|
392
546
|
function indexCatalog(groups) {
|
|
393
547
|
const index = new Map();
|
|
394
|
-
|
|
548
|
+
const ordered = (Array.isArray(groups) ? groups : []).slice().sort(
|
|
549
|
+
(a, b) => (a?.id === 'dsh-switchman-auto' || a?.provider === 'dsh-switchman-auto' ? 0 : 1)
|
|
550
|
+
- (b?.id === 'dsh-switchman-auto' || b?.provider === 'dsh-switchman-auto' ? 0 : 1),
|
|
551
|
+
);
|
|
552
|
+
for (const group of ordered) {
|
|
395
553
|
const providerName = group.name ?? group.id ?? '';
|
|
396
554
|
for (const model of Array.isArray(group.models) ? group.models : []) {
|
|
397
555
|
const provider = model.provider ?? group.id;
|
|
@@ -652,6 +810,69 @@ window.__ModuleLoader__.load({
|
|
|
652
810
|
uiLocaleLabel: '界面语言',
|
|
653
811
|
uiLocaleHelp: '「自动」跟随 DeepSeek Harness 应用语言;选择具体语言后,本插件界面将固定为该语言。',
|
|
654
812
|
uiLocaleAuto: '自动(跟随 DSH)',
|
|
813
|
+
intentSectionTitle: '意图识别',
|
|
814
|
+
intentSectionDescription:
|
|
815
|
+
'选择「自动」模型时,按对话内容用分类器小模型自动路由候选模型;含厂商缓存续用窗口与切换置信度门槛。',
|
|
816
|
+
intentEnabledLabel: '启用意图识别',
|
|
817
|
+
intentEnabledHelp:
|
|
818
|
+
'主开关;关闭时选中「自动」直接走兜底链,不发起分类调用。',
|
|
819
|
+
intentClassifierRouteLabel: '分类器模型',
|
|
820
|
+
intentClassifierRouteHelp: '执行分类的小模型路由(provider + model)。',
|
|
821
|
+
intentClassifierEffortLabel: '分类器思考强度',
|
|
822
|
+
intentClassifierEffortHelp:
|
|
823
|
+
'可选固定 reasoning effort,优先于适配器默认。',
|
|
824
|
+
intentClassifierKindLabel: '分类器类型',
|
|
825
|
+
intentClassifierKindHelp:
|
|
826
|
+
'chat = 直接选 LLM 目录里的对话模型;systemone = 手填端点+模型的决策类型。',
|
|
827
|
+
intentClassifierKindChat: '对话模型 (LLM 目录)',
|
|
828
|
+
intentClassifierKindSystemone: '决策类型 (手配端点+模型)',
|
|
829
|
+
intentClassifierEndpointLabel: '决策端点',
|
|
830
|
+
intentClassifierEndpointHelp: '分类器类型为「决策端点」时直接调用的 /v1/systemone 完整 URL(不经 llm 提供方)',
|
|
831
|
+
intentSystemoneModelLabel: '决策端点模型',
|
|
832
|
+
intentSystemoneModelHelp: '发送到决策端点的 model 参数(如 tev1:4b-mxfp8),自由填写,无需存在于模型供应商列表',
|
|
833
|
+
intentEndpointUnset: '未设置',
|
|
834
|
+
intentCandidatesLabel: '候选模型',
|
|
835
|
+
intentCandidatesHelp:
|
|
836
|
+
'可被自动路由到的模型清单;note 是给分类器看的一句话能力描述。',
|
|
837
|
+
intentCandidatesEmpty: '暂无候选:至少配置 1 个候选,「自动」才能路由。',
|
|
838
|
+
intentCandidatesAdd: '添加候选',
|
|
839
|
+
intentCandidateRemove: '删除候选',
|
|
840
|
+
intentCandidateEnable: '启用',
|
|
841
|
+
intentCandidateUnset: '已启用但未选模型:暂不参与路由,选一个模型即生效',
|
|
842
|
+
intentCandidatePresetBadge: '预置',
|
|
843
|
+
intentCandidateNotePlaceholder: '一句话能力描述(给分类器看)',
|
|
844
|
+
intentCacheTtlLabel: '缓存续用窗口(秒)',
|
|
845
|
+
intentCacheTtlHelp:
|
|
846
|
+
'该窗口内置信度不足时沿用当前模型,避免打断厂商 prompt cache。',
|
|
847
|
+
intentSwitchConfidenceLabel: '切换置信度(0–100)',
|
|
848
|
+
intentSwitchConfidenceHelp:
|
|
849
|
+
'分类置信度达到该值才允许离开当前模型。',
|
|
850
|
+
intentTimeoutLabel: '分类超时(毫秒)',
|
|
851
|
+
intentTimeoutHelp:
|
|
852
|
+
'分类调用预算;超时或失败时按兜底链放行(fail-open)。',
|
|
853
|
+
intentHistoryMessagesLabel: '历史消息条数(1–20)',
|
|
854
|
+
intentHistoryMessagesHelp: '喂给分类器的最近消息条数。',
|
|
855
|
+
intentHistoryMaxCharsLabel: '摘录字符预算',
|
|
856
|
+
intentContextWindowLabel: '上下文窗口',
|
|
857
|
+
intentContextWindowHelp: 'Auto 伪模型声明的上下文窗口(token),用于用量显示与自动压缩水位;当 0.8×窗口低于 force 水位时生效值会自动抬升,保证平滑交接先触发。',
|
|
858
|
+
intentHistoryMaxCharsHelp: '对话摘录的总字符上限(200–20000)。',
|
|
859
|
+
intentFallbackRouteLabel: '兜底模型',
|
|
860
|
+
intentFallbackRouteHelp:
|
|
861
|
+
'分类失败时依次:上一轮实际路由 → 兜底模型 → 候选第一位;留空用候选第一位。',
|
|
862
|
+
intentRouteUnset: '未设置',
|
|
863
|
+
intentEffortFollow: '跟随模型默认',
|
|
864
|
+
intentDockAuto: '自动',
|
|
865
|
+
intentDockUnrouted: '未路由',
|
|
866
|
+
intentDockUsing: '在用',
|
|
867
|
+
intentDockDecided: '决策',
|
|
868
|
+
intentDockUnconfigured: '意图识别未配置完整',
|
|
869
|
+
intentDockCache: '缓存窗口剩余 {seconds} 秒',
|
|
870
|
+
intentDockOverflowRisk: '候选 {model} 窗口 {window}k 低于交接线,近上限可能溢出',
|
|
871
|
+
intentDockTipCandidates: '候选:{count}',
|
|
872
|
+
intentDockTipOn: '开',
|
|
873
|
+
intentDockTipOff: '关',
|
|
874
|
+
intentModelLabelTrigger: '自动',
|
|
875
|
+
intentModelLabelFull: '自动(意图识别)',
|
|
655
876
|
},
|
|
656
877
|
en: {
|
|
657
878
|
badge: 'Team autonomy',
|
|
@@ -810,6 +1031,75 @@ window.__ModuleLoader__.load({
|
|
|
810
1031
|
uiLocaleLabel: 'Interface language',
|
|
811
1032
|
uiLocaleHelp: "Auto follows the DeepSeek Harness app language; picking a language forces this plugin's UI into it.",
|
|
812
1033
|
uiLocaleAuto: 'Auto (follow DSH)',
|
|
1034
|
+
intentSectionTitle: 'Intent routing',
|
|
1035
|
+
intentSectionDescription:
|
|
1036
|
+
"When the 'Auto' pseudo-model is picked, a small classifier model routes each turn to the best candidate by conversation content; with vendor cache-reuse window and a switch-confidence gate.",
|
|
1037
|
+
intentEnabledLabel: 'Enable intent routing',
|
|
1038
|
+
intentEnabledHelp:
|
|
1039
|
+
"Master switch; off = the 'Auto' selection goes straight down the fallback chain without any classifier call.",
|
|
1040
|
+
intentClassifierRouteLabel: 'Classifier model',
|
|
1041
|
+
intentClassifierRouteHelp:
|
|
1042
|
+
'Route (provider + model) of the small model that classifies each turn.',
|
|
1043
|
+
intentClassifierEffortLabel: 'Classifier reasoning effort',
|
|
1044
|
+
intentClassifierEffortHelp:
|
|
1045
|
+
'Optional pinned reasoning effort; overrides the adapter default.',
|
|
1046
|
+
intentClassifierKindLabel: 'Classifier kind',
|
|
1047
|
+
intentClassifierKindHelp:
|
|
1048
|
+
'chat = a chat model from the LLM catalog; systemone = decision type with a hand-configured endpoint + model.',
|
|
1049
|
+
intentClassifierKindChat: 'Chat model (LLM catalog)',
|
|
1050
|
+
intentClassifierKindSystemone: 'Decision (custom endpoint+model)',
|
|
1051
|
+
intentClassifierEndpointLabel: 'Decision endpoint',
|
|
1052
|
+
intentClassifierEndpointHelp: 'Full /v1/systemone URL called directly when the classifier kind is the decision endpoint (no llm provider)',
|
|
1053
|
+
intentSystemoneModelLabel: 'Decision endpoint model',
|
|
1054
|
+
intentSystemoneModelHelp: 'Model name sent to the decision endpoint (e.g. tev1:4b-mxfp8); free text, no provider entry needed',
|
|
1055
|
+
intentEndpointUnset: 'Not set',
|
|
1056
|
+
intentCandidatesLabel: 'Candidates',
|
|
1057
|
+
intentCandidatesHelp:
|
|
1058
|
+
'Models the router may pick; note is a one-line capability description the classifier reads.',
|
|
1059
|
+
intentCandidatesEmpty:
|
|
1060
|
+
'No candidates yet: configure at least 1 candidate for routing to work.',
|
|
1061
|
+
intentCandidatesAdd: 'Add candidate',
|
|
1062
|
+
intentCandidateRemove: 'Remove candidate',
|
|
1063
|
+
intentCandidateEnable: 'Enable',
|
|
1064
|
+
intentCandidateUnset: 'Enabled but no model selected — excluded from routing until a model is chosen',
|
|
1065
|
+
intentCandidatePresetBadge: 'Preset',
|
|
1066
|
+
intentCandidateNotePlaceholder:
|
|
1067
|
+
'one-line capability note (read by the classifier)',
|
|
1068
|
+
intentCacheTtlLabel: 'Cache reuse window (seconds)',
|
|
1069
|
+
intentCacheTtlHelp:
|
|
1070
|
+
'Inside this window a low-confidence result keeps the current model, preserving the vendor prompt cache.',
|
|
1071
|
+
intentSwitchConfidenceLabel: 'Switch confidence (0–100)',
|
|
1072
|
+
intentSwitchConfidenceHelp:
|
|
1073
|
+
'The classifier confidence must reach this value to leave the current model.',
|
|
1074
|
+
intentTimeoutLabel: 'Classification timeout (ms)',
|
|
1075
|
+
intentTimeoutHelp:
|
|
1076
|
+
'Budget for the classifier call; timeout or failure falls back (fail-open).',
|
|
1077
|
+
intentHistoryMessagesLabel: 'History messages (1–20)',
|
|
1078
|
+
intentHistoryMessagesHelp:
|
|
1079
|
+
'How many recent messages are fed to the classifier.',
|
|
1080
|
+
intentHistoryMaxCharsLabel: 'Excerpt character budget',
|
|
1081
|
+
intentContextWindowLabel: 'Context window',
|
|
1082
|
+
intentContextWindowHelp: 'Context window (tokens) declared by the Auto pseudo-model; drives the usage popover and auto-compaction pressure. When 0.8× the window is below the force watermark the effective value is raised automatically, so the smooth handover fires first.',
|
|
1083
|
+
intentHistoryMaxCharsHelp:
|
|
1084
|
+
'Total character cap of the conversation excerpt (200–20000).',
|
|
1085
|
+
intentFallbackRouteLabel: 'Fallback model',
|
|
1086
|
+
intentFallbackRouteHelp:
|
|
1087
|
+
'On classification failure: last routed model → fallback → first candidate; empty = first candidate.',
|
|
1088
|
+
intentRouteUnset: 'Not set',
|
|
1089
|
+
intentEffortFollow: 'model default',
|
|
1090
|
+
intentDockAuto: 'Auto',
|
|
1091
|
+
intentDockUnrouted: 'unrouted',
|
|
1092
|
+
intentDockUsing: 'using',
|
|
1093
|
+
intentDockDecided: 'decided',
|
|
1094
|
+
intentDockUnconfigured:
|
|
1095
|
+
'Intent routing is not fully configured',
|
|
1096
|
+
intentDockCache: 'Cache window: {seconds}s left',
|
|
1097
|
+
intentDockOverflowRisk: 'candidate {model} window {window}k is below the handover line; may overflow near the cap',
|
|
1098
|
+
intentDockTipCandidates: 'candidates: {count}',
|
|
1099
|
+
intentDockTipOn: 'on',
|
|
1100
|
+
intentDockTipOff: 'off',
|
|
1101
|
+
intentModelLabelTrigger: 'Auto',
|
|
1102
|
+
intentModelLabelFull: 'Auto (intent routing)',
|
|
813
1103
|
},
|
|
814
1104
|
};
|
|
815
1105
|
|
|
@@ -959,6 +1249,69 @@ window.__ModuleLoader__.load({
|
|
|
959
1249
|
uiLocaleLabel: '介面語言',
|
|
960
1250
|
uiLocaleHelp: '「自動」跟隨 DeepSeek Harness 應用程式語言;指定語言後,本外掛的 UI 固定使用該語言。',
|
|
961
1251
|
uiLocaleAuto: '自動(跟隨 DSH)',
|
|
1252
|
+
intentSectionTitle: '意圖識別',
|
|
1253
|
+
intentSectionDescription:
|
|
1254
|
+
'選擇「自動」模型時,按對話內容用分類器小模型自動路由候選模型;含供應商快取續用視窗與切換信心門檻。',
|
|
1255
|
+
intentEnabledLabel: '啟用意圖識別',
|
|
1256
|
+
intentEnabledHelp:
|
|
1257
|
+
'主開關;關閉時選中「自動」直接走備援鏈,不發起分類呼叫。',
|
|
1258
|
+
intentClassifierRouteLabel: '分類器模型',
|
|
1259
|
+
intentClassifierRouteHelp: '執行分類的小模型路由(provider + model)。',
|
|
1260
|
+
intentClassifierEffortLabel: '分類器思考強度',
|
|
1261
|
+
intentClassifierEffortHelp:
|
|
1262
|
+
'可選固定 reasoning effort,優先於配接器預設。',
|
|
1263
|
+
intentClassifierKindLabel: '分類器類型',
|
|
1264
|
+
intentClassifierKindHelp:
|
|
1265
|
+
'chat = 直接選 LLM 目錄裡的對話模型;systemone = 手填端點+模型的決策類型。',
|
|
1266
|
+
intentClassifierKindChat: '對話模型 (LLM 目錄)',
|
|
1267
|
+
intentClassifierKindSystemone: '決策類型 (自配端點+模型)',
|
|
1268
|
+
intentClassifierEndpointLabel: '決策端點',
|
|
1269
|
+
intentClassifierEndpointHelp: '分類器類型為「決策端點」時直接呼叫的 /v1/systemone 完整 URL(不經 llm 提供方)',
|
|
1270
|
+
intentSystemoneModelLabel: '決策端點模型',
|
|
1271
|
+
intentSystemoneModelHelp: '傳送到決策端點的 model 參數(如 tev1:4b-mxfp8),自由填寫,無需存在於模型供應商清單',
|
|
1272
|
+
intentEndpointUnset: '未設定',
|
|
1273
|
+
intentCandidatesLabel: '候選模型',
|
|
1274
|
+
intentCandidatesHelp:
|
|
1275
|
+
'可被自動路由到的模型清單;note 是給分類器看的一句話能力描述。',
|
|
1276
|
+
intentCandidatesEmpty: '暫無候選:至少設定 1 個候選,「自動」才能路由。',
|
|
1277
|
+
intentCandidatesAdd: '新增候選',
|
|
1278
|
+
intentCandidateRemove: '刪除候選',
|
|
1279
|
+
intentCandidateEnable: '啟用',
|
|
1280
|
+
intentCandidateUnset: '已啟用但未選模型:暫不參與路由,選一個模型即生效',
|
|
1281
|
+
intentCandidatePresetBadge: '預置',
|
|
1282
|
+
intentCandidateNotePlaceholder: '一句話能力描述(給分類器看)',
|
|
1283
|
+
intentCacheTtlLabel: '快取續用視窗(秒)',
|
|
1284
|
+
intentCacheTtlHelp:
|
|
1285
|
+
'視窗內信心不足時沿用目前模型,避免打斷供應商 prompt cache。',
|
|
1286
|
+
intentSwitchConfidenceLabel: '切換信心(0–100)',
|
|
1287
|
+
intentSwitchConfidenceHelp:
|
|
1288
|
+
'分類信心達到該值才允許離開目前模型。',
|
|
1289
|
+
intentTimeoutLabel: '分類逾時(毫秒)',
|
|
1290
|
+
intentTimeoutHelp:
|
|
1291
|
+
'分類呼叫預算;逾時或失敗時按備援鏈放行(fail-open)。',
|
|
1292
|
+
intentHistoryMessagesLabel: '歷史訊息條數(1–20)',
|
|
1293
|
+
intentHistoryMessagesHelp: '餵給分類器的最近訊息條數。',
|
|
1294
|
+
intentHistoryMaxCharsLabel: '摘錄字元預算',
|
|
1295
|
+
intentContextWindowLabel: '上下文視窗',
|
|
1296
|
+
intentContextWindowHelp: 'Auto 偽模型宣告的上下文視窗(token),用於用量顯示與自動壓縮水位;當 0.8×視窗低於 force 水位時生效值會自動抬升,確保平滑交接先觸發。',
|
|
1297
|
+
intentHistoryMaxCharsHelp: '對話摘錄的總字元上限(200–20000)。',
|
|
1298
|
+
intentFallbackRouteLabel: '備援模型',
|
|
1299
|
+
intentFallbackRouteHelp:
|
|
1300
|
+
'分類失敗時依次:上一輪實際路由 → 備援模型 → 候選第一位;留空用候選第一位。',
|
|
1301
|
+
intentRouteUnset: '未設定',
|
|
1302
|
+
intentEffortFollow: '跟隨模型預設',
|
|
1303
|
+
intentDockAuto: '自動',
|
|
1304
|
+
intentDockUnrouted: '未路由',
|
|
1305
|
+
intentDockUsing: '在用',
|
|
1306
|
+
intentDockDecided: '決策',
|
|
1307
|
+
intentDockUnconfigured: '意圖識別未設定完整',
|
|
1308
|
+
intentDockCache: '快取視窗剩餘 {seconds} 秒',
|
|
1309
|
+
intentDockOverflowRisk: '候選 {model} 視窗 {window}k 低於交接線,接近上限可能溢出',
|
|
1310
|
+
intentDockTipCandidates: '候選:{count}',
|
|
1311
|
+
intentDockTipOn: '開',
|
|
1312
|
+
intentDockTipOff: '關',
|
|
1313
|
+
intentModelLabelTrigger: '自動',
|
|
1314
|
+
intentModelLabelFull: '自動(意圖識別)',
|
|
962
1315
|
},
|
|
963
1316
|
ja: {
|
|
964
1317
|
badge: '自律チーム',
|
|
@@ -1096,6 +1449,73 @@ window.__ModuleLoader__.load({
|
|
|
1096
1449
|
uiLocaleLabel: 'インターフェース言語',
|
|
1097
1450
|
uiLocaleHelp: '「自動」は DeepSeek Harness アプリの言語に従います。言語を選ぶと、このプラグインの UI がその言語に固定されます。',
|
|
1098
1451
|
uiLocaleAuto: '自動(DSH に従う)',
|
|
1452
|
+
intentSectionTitle: 'インテントルーティング',
|
|
1453
|
+
intentSectionDescription:
|
|
1454
|
+
'「自動」モデル選択時、会話内容から小型の分類モデルが各ターンを最適な候補へルーティングします。ベンダーキャッシュ再利用窓と切替信頼度ゲート付き。',
|
|
1455
|
+
intentEnabledLabel: 'インテントルーティングを有効化',
|
|
1456
|
+
intentEnabledHelp:
|
|
1457
|
+
'マスタースイッチ。オフのとき「自動」は分類呼び出しを行わずフォールバックチェーンへ直行します。',
|
|
1458
|
+
intentClassifierRouteLabel: '分類モデル',
|
|
1459
|
+
intentClassifierRouteHelp:
|
|
1460
|
+
'分類を行う小型モデルのルート(provider + model)。',
|
|
1461
|
+
intentClassifierEffortLabel: '分類の思考強度',
|
|
1462
|
+
intentClassifierEffortHelp:
|
|
1463
|
+
'任意の reasoning effort 固定。アダプターのデフォルトより優先します。',
|
|
1464
|
+
intentClassifierKindLabel: '分類器の種類',
|
|
1465
|
+
intentClassifierKindHelp:
|
|
1466
|
+
'chat = LLM カタログの対話モデルを直接選択。systemone = エンドポイント+モデルを手動設定する決定タイプ。',
|
|
1467
|
+
intentClassifierKindChat: '対話モデル (LLM カタログ)',
|
|
1468
|
+
intentClassifierKindSystemone: '決定タイプ (手動エンドポイント+モデル)',
|
|
1469
|
+
intentClassifierEndpointLabel: '判定エンドポイント',
|
|
1470
|
+
intentClassifierEndpointHelp: '分類器の種類が判定エンドポイントのとき直接呼び出す /v1/systemone の完全 URL(LLM プロバイダ不要)',
|
|
1471
|
+
intentSystemoneModelLabel: '決定エンドポイントのモデル',
|
|
1472
|
+
intentSystemoneModelHelp: '決定エンドポイントへ送る model パラメータ(例: tev1:4b-mxfp8)。自由入力で、プロバイダー一覧への登録は不要',
|
|
1473
|
+
intentEndpointUnset: '未設定',
|
|
1474
|
+
intentCandidatesLabel: '候補モデル',
|
|
1475
|
+
intentCandidatesHelp:
|
|
1476
|
+
'自動ルーティングされ得るモデル一覧。note は分類器が読む一行の能力説明です。',
|
|
1477
|
+
intentCandidatesEmpty:
|
|
1478
|
+
'候補なし:ルーティングには 1 件以上の候補が必要です。',
|
|
1479
|
+
intentCandidatesAdd: '候補を追加',
|
|
1480
|
+
intentCandidateRemove: '候補を削除',
|
|
1481
|
+
intentCandidateEnable: '有効化',
|
|
1482
|
+
intentCandidateUnset: '有効ですがモデル未選択:ルーティング対象外。モデルを選ぶと有効になります',
|
|
1483
|
+
intentCandidatePresetBadge: 'プリセット',
|
|
1484
|
+
intentCandidateNotePlaceholder: '一行の能力説明(分類器向け)',
|
|
1485
|
+
intentCacheTtlLabel: 'キャッシュ再利用窓(秒)',
|
|
1486
|
+
intentCacheTtlHelp:
|
|
1487
|
+
'窓内で信頼度が不足する場合は現在モデルを維持し、ベンダーの prompt cache を保ちます。',
|
|
1488
|
+
intentSwitchConfidenceLabel: '切替信頼度(0–100)',
|
|
1489
|
+
intentSwitchConfidenceHelp:
|
|
1490
|
+
'この値に達した信頼度でのみ現在モデルから切り替えます。',
|
|
1491
|
+
intentTimeoutLabel: '分類タイムアウト(ミリ秒)',
|
|
1492
|
+
intentTimeoutHelp:
|
|
1493
|
+
'分類呼び出しの予算。タイムアウトや失敗はフォールバックで継続(fail-open)。',
|
|
1494
|
+
intentHistoryMessagesLabel: '履歴メッセージ数(1–20)',
|
|
1495
|
+
intentHistoryMessagesHelp: '分類器に渡す最近のメッセージ数。',
|
|
1496
|
+
intentHistoryMaxCharsLabel: '抜粋文字数予算',
|
|
1497
|
+
intentContextWindowLabel: 'コンテキストウィンドウ',
|
|
1498
|
+
intentContextWindowHelp: 'Auto 疑似モデルが宣言するコンテキストウィンドウ(トークン)。使用量表示と自動圧縮のしきい値に使用。0.8×ウィンドウが force 水位を下回ると実効値が自動的に引き上げられ、平滑なハンドオーバーが先に発火します。',
|
|
1499
|
+
intentHistoryMaxCharsHelp:
|
|
1500
|
+
'会話抜粋の合計文字上限(200–20000)。',
|
|
1501
|
+
intentFallbackRouteLabel: 'フォールバックモデル',
|
|
1502
|
+
intentFallbackRouteHelp:
|
|
1503
|
+
'分類失敗時:前回ルート → フォールバック → 候補の先頭。空欄は候補の先頭。',
|
|
1504
|
+
intentRouteUnset: '未設定',
|
|
1505
|
+
intentEffortFollow: 'モデルのデフォルト',
|
|
1506
|
+
intentDockAuto: '自動',
|
|
1507
|
+
intentDockUnrouted: '未ルーティング',
|
|
1508
|
+
intentDockUsing: '使用中',
|
|
1509
|
+
intentDockDecided: '判定',
|
|
1510
|
+
intentDockUnconfigured:
|
|
1511
|
+
'インテントルーティングの設定が不完全です',
|
|
1512
|
+
intentDockCache: 'キャッシュ窓 残り {seconds} 秒',
|
|
1513
|
+
intentDockOverflowRisk: '候補 {model} のウィンドウ {window}k はハンドオーバー線未満。上限付近で溢れる恐れがあります。',
|
|
1514
|
+
intentDockTipCandidates: '候補:{count}',
|
|
1515
|
+
intentDockTipOn: 'オン',
|
|
1516
|
+
intentDockTipOff: 'オフ',
|
|
1517
|
+
intentModelLabelTrigger: '自動',
|
|
1518
|
+
intentModelLabelFull: '自動(インテントルーティング)',
|
|
1099
1519
|
},
|
|
1100
1520
|
ko: {
|
|
1101
1521
|
badge: '자율 팀',
|
|
@@ -1233,6 +1653,73 @@ window.__ModuleLoader__.load({
|
|
|
1233
1653
|
uiLocaleLabel: '인터페이스 언어',
|
|
1234
1654
|
uiLocaleHelp: '자동은 DeepSeek Harness 앱 언어를 따르고, 언어를 직접 고르면 이 플러그인의 UI가 그 언어로 고정됩니다.',
|
|
1235
1655
|
uiLocaleAuto: '자동(DSH 따르기)',
|
|
1656
|
+
intentSectionTitle: '의도 라우팅',
|
|
1657
|
+
intentSectionDescription:
|
|
1658
|
+
'「자동」모델 선택 시 대화 내용을 기반으로 소형 분류 모델이 각 턴을 최적 후보로 라우팅합니다. 벤더 캐시 재사용 창과 전환 신뢰도 게이트 포함.',
|
|
1659
|
+
intentEnabledLabel: '의도 라우팅 사용',
|
|
1660
|
+
intentEnabledHelp:
|
|
1661
|
+
'마스터 스위치. 끄면 「자동」은 분류 호출 없이 폴백 체인으로 바로 진행합니다.',
|
|
1662
|
+
intentClassifierRouteLabel: '분류 모델',
|
|
1663
|
+
intentClassifierRouteHelp:
|
|
1664
|
+
'분류를 수행하는 소형 모델 경로(provider + model)입니다.',
|
|
1665
|
+
intentClassifierEffortLabel: '분류 추론 강도',
|
|
1666
|
+
intentClassifierEffortHelp:
|
|
1667
|
+
'선택적 reasoning effort 고정. 어댑터 기본값보다 우선합니다.',
|
|
1668
|
+
intentClassifierKindLabel: '분류기 종류',
|
|
1669
|
+
intentClassifierKindHelp:
|
|
1670
|
+
'chat = LLM 카탈로그의 대화 모델 직접 선택. systemone = 엔드포인트+모델을 수동 설정하는 결정 유형.',
|
|
1671
|
+
intentClassifierKindChat: '대화 모델 (LLM 카탈로그)',
|
|
1672
|
+
intentClassifierKindSystemone: '결정 유형 (수동 엔드포인트+모델)',
|
|
1673
|
+
intentClassifierEndpointLabel: '판정 엔드포인트',
|
|
1674
|
+
intentClassifierEndpointHelp: '분류기 종류가 판정 엔드포인트일 때 직접 호출하는 /v1/systemone 전체 URL(LLM 공급자 불필요)',
|
|
1675
|
+
intentSystemoneModelLabel: '결정 엔드포인트 모델',
|
|
1676
|
+
intentSystemoneModelHelp: '결정 엔드포인트로 보내는 model 매개변수(예: tev1:4b-mxfp8). 자유 입력이며 공급자 목록에 없어도 됨',
|
|
1677
|
+
intentEndpointUnset: '미설정',
|
|
1678
|
+
intentCandidatesLabel: '후보 모델',
|
|
1679
|
+
intentCandidatesHelp:
|
|
1680
|
+
'자동 라우팅될 수 있는 모델 목록. note는 분류기가 읽는 한 줄 능력 설명입니다.',
|
|
1681
|
+
intentCandidatesEmpty:
|
|
1682
|
+
'후보 없음: 라우팅하려면 후보를 1개 이상 설정하세요.',
|
|
1683
|
+
intentCandidatesAdd: '후보 추가',
|
|
1684
|
+
intentCandidateRemove: '후보 삭제',
|
|
1685
|
+
intentCandidateEnable: '사용',
|
|
1686
|
+
intentCandidateUnset: '활성화되었으나 모델 미선택: 라우팅에서 제외됩니다. 모델을 선택하면 활성화됩니다',
|
|
1687
|
+
intentCandidatePresetBadge: '프리셋',
|
|
1688
|
+
intentCandidateNotePlaceholder: '한 줄 능력 설명(분류기용)',
|
|
1689
|
+
intentCacheTtlLabel: '캐시 재사용 창(초)',
|
|
1690
|
+
intentCacheTtlHelp:
|
|
1691
|
+
'창 안에서 신뢰도가 부족하면 현재 모델을 유지해 벤더 prompt cache를 보존합니다.',
|
|
1692
|
+
intentSwitchConfidenceLabel: '전환 신뢰도(0–100)',
|
|
1693
|
+
intentSwitchConfidenceHelp:
|
|
1694
|
+
'이 값에 도달한 신뢰도에서만 현재 모델을 떠날 수 있습니다.',
|
|
1695
|
+
intentTimeoutLabel: '분류 타임아웃(밀리초)',
|
|
1696
|
+
intentTimeoutHelp:
|
|
1697
|
+
'분류 호출 예산. 시간 초과나 실패 시 폴백으로 계속합니다(fail-open).',
|
|
1698
|
+
intentHistoryMessagesLabel: '히스토리 메시지 수(1–20)',
|
|
1699
|
+
intentHistoryMessagesHelp: '분류기에 전달할 최근 메시지 수.',
|
|
1700
|
+
intentHistoryMaxCharsLabel: '발췌 문자 예산',
|
|
1701
|
+
intentContextWindowLabel: '컨텍스트 윈도우',
|
|
1702
|
+
intentContextWindowHelp: 'Auto 의사 모델이 선언하는 컨텍스트 윈도우(토큰). 사용량 표시와 자동 압축 기준에 사용. 0.8×윈도우가 force 임계값보다 낮으면 실제 적용 값이 자동으로 상향되어 부드러운 인계가 먼저 실행됩니다.',
|
|
1703
|
+
intentHistoryMaxCharsHelp:
|
|
1704
|
+
'대화 발췌의 총 문자 상한(200–20000).',
|
|
1705
|
+
intentFallbackRouteLabel: '폴백 모델',
|
|
1706
|
+
intentFallbackRouteHelp:
|
|
1707
|
+
'분류 실패 시: 이전 라우트 → 폴백 → 첫 후보. 빈 값은 첫 후보.',
|
|
1708
|
+
intentRouteUnset: '미설정',
|
|
1709
|
+
intentEffortFollow: '모델 기본값',
|
|
1710
|
+
intentDockAuto: '자동',
|
|
1711
|
+
intentDockUnrouted: '미라우팅',
|
|
1712
|
+
intentDockUsing: '사용 중',
|
|
1713
|
+
intentDockDecided: '판정',
|
|
1714
|
+
intentDockUnconfigured:
|
|
1715
|
+
'의도 라우팅 설정이 완전하지 않습니다',
|
|
1716
|
+
intentDockCache: '캐시 창 남은 {seconds}초',
|
|
1717
|
+
intentDockOverflowRisk: '후보 {model} 창 {window}k가 인계선 미만입니다. 상한 근처에서 넘칠 수 있습니다.',
|
|
1718
|
+
intentDockTipCandidates: '후보: {count}',
|
|
1719
|
+
intentDockTipOn: '켬',
|
|
1720
|
+
intentDockTipOff: '끔',
|
|
1721
|
+
intentModelLabelTrigger: '자동',
|
|
1722
|
+
intentModelLabelFull: '자동(의도 라우팅)',
|
|
1236
1723
|
},
|
|
1237
1724
|
de: {
|
|
1238
1725
|
badge: 'Autonomes Team',
|
|
@@ -1370,6 +1857,75 @@ window.__ModuleLoader__.load({
|
|
|
1370
1857
|
uiLocaleLabel: 'Sprache der Oberfläche',
|
|
1371
1858
|
uiLocaleHelp: 'Auto folgt der App-Sprache von DeepSeek Harness; wählt man eine Sprache, wird die Oberfläche dieses Plugins darauf festgelegt.',
|
|
1372
1859
|
uiLocaleAuto: 'Auto (DSH folgen)',
|
|
1860
|
+
intentSectionTitle: 'Intent-Routing',
|
|
1861
|
+
intentSectionDescription:
|
|
1862
|
+
'Bei Wahl des Pseudo-Modells „Auto“ routet ein kleines Klassifikatormodell jeden Zug anhand des Gesprächsverlaufs zum besten Kandidaten; mit Cache-Wiederverwendungsfenster und Konfidenzschwelle.',
|
|
1863
|
+
intentEnabledLabel: 'Intent-Routing aktivieren',
|
|
1864
|
+
intentEnabledHelp:
|
|
1865
|
+
'Hauptschalter; aus = die „Auto“-Auswahl geht ohne Klassifikatoraufruf direkt in die Fallback-Kette.',
|
|
1866
|
+
intentClassifierRouteLabel: 'Klassifikator-Modell',
|
|
1867
|
+
intentClassifierRouteHelp:
|
|
1868
|
+
'Route (Provider + Modell) des kleinen Klassifikationsmodells.',
|
|
1869
|
+
intentClassifierEffortLabel: 'Reasoning-Aufwand des Klassifikators',
|
|
1870
|
+
intentClassifierEffortHelp:
|
|
1871
|
+
'Optionaler fester Reasoning-Effort; hat Vorrang vor dem Adapter-Standard.',
|
|
1872
|
+
intentClassifierKindLabel: 'Klassifikator-Typ',
|
|
1873
|
+
intentClassifierKindHelp:
|
|
1874
|
+
'chat = Chat-Modell direkt aus dem LLM-Katalog; systemone = Entscheidung mit eigenem Endpunkt + Modell.',
|
|
1875
|
+
intentClassifierKindChat: 'Chat-Modell (LLM-Katalog)',
|
|
1876
|
+
intentClassifierKindSystemone: 'Entscheidung (eigener Endpunkt+Modell)',
|
|
1877
|
+
intentClassifierEndpointLabel: 'Entscheidungs-Endpunkt',
|
|
1878
|
+
intentClassifierEndpointHelp: 'Vollständige /v1/systemone-URL, die direkt aufgerufen wird, wenn der Klassifikator-Typ der Entscheidungs-Endpunkt ist (ohne LLM-Provider)',
|
|
1879
|
+
intentSystemoneModelLabel: 'Modell des Entscheidungsendpunkts',
|
|
1880
|
+
intentSystemoneModelHelp: 'Modelname, der an den Entscheidungsendpunkt gesendet wird (z. B. tev1:4b-mxfp8); freier Text, kein Anbieter-Eintrag nötig',
|
|
1881
|
+
intentEndpointUnset: 'Nicht festgelegt',
|
|
1882
|
+
intentCandidatesLabel: 'Kandidaten',
|
|
1883
|
+
intentCandidatesHelp:
|
|
1884
|
+
'Modelle, zu denen geroutet werden darf; note ist eine Einzeilen-Fähigkeitsbeschreibung für den Klassifikator.',
|
|
1885
|
+
intentCandidatesEmpty:
|
|
1886
|
+
'Keine Kandidaten: Für das Routing ist mindestens 1 Kandidat nötig.',
|
|
1887
|
+
intentCandidatesAdd: 'Kandidat hinzufügen',
|
|
1888
|
+
intentCandidateRemove: 'Kandidat entfernen',
|
|
1889
|
+
intentCandidateEnable: 'Aktivieren',
|
|
1890
|
+
intentCandidateUnset: 'Aktiviert, aber ohne Modell — wird beim Routing übergangen; mit gewähltem Modell aktiv',
|
|
1891
|
+
intentCandidatePresetBadge: 'Preset',
|
|
1892
|
+
intentCandidateNotePlaceholder:
|
|
1893
|
+
'Einzeilen-Fähigkeitsbeschreibung (für den Klassifikator)',
|
|
1894
|
+
intentCacheTtlLabel: 'Cache-Fenster (Sekunden)',
|
|
1895
|
+
intentCacheTtlHelp:
|
|
1896
|
+
'Innerhalb des Fensters hält geringe Konfidenz das aktuelle Modell und schont den Anbieter-Prompt-Cache.',
|
|
1897
|
+
intentSwitchConfidenceLabel: 'Wechsel-Konfidenz (0–100)',
|
|
1898
|
+
intentSwitchConfidenceHelp:
|
|
1899
|
+
'Erst ab dieser Klassifikationskonfidenz darf das aktuelle Modell verlassen werden.',
|
|
1900
|
+
intentTimeoutLabel: 'Klassifikations-Timeout (ms)',
|
|
1901
|
+
intentTimeoutHelp:
|
|
1902
|
+
'Budget für den Klassifikatoraufruf; Timeout oder Fehler fällt zurück (fail-open).',
|
|
1903
|
+
intentHistoryMessagesLabel: 'Verlaufsnachrichten (1–20)',
|
|
1904
|
+
intentHistoryMessagesHelp:
|
|
1905
|
+
'Wie viele letzte Nachrichten dem Klassifikator übergeben werden.',
|
|
1906
|
+
intentHistoryMaxCharsLabel: 'Auszug-Zeichenbudget',
|
|
1907
|
+
intentContextWindowLabel: 'Kontextfenster',
|
|
1908
|
+
intentContextWindowHelp: 'Vom Auto-Pseudo-Modell deklariertes Kontextfenster (Token); steuert Verbrauchsanzeige und Kompressionsdruck. Liegt 0,8× des Fensters unter der Force-Schwelle, wird der effektive Wert automatisch angehoben, damit die sanfte Übergabe zuerst auslöst.',
|
|
1909
|
+
intentHistoryMaxCharsHelp:
|
|
1910
|
+
'Gesamtzeichengrenze des Gesprächsauszugs (200–20000).',
|
|
1911
|
+
intentFallbackRouteLabel: 'Fallback-Modell',
|
|
1912
|
+
intentFallbackRouteHelp:
|
|
1913
|
+
'Bei Klassifikationsfehler: zuletzt geroutet → Fallback → erster Kandidat; leer = erster Kandidat.',
|
|
1914
|
+
intentRouteUnset: 'Nicht festgelegt',
|
|
1915
|
+
intentEffortFollow: 'Modellstandard',
|
|
1916
|
+
intentDockAuto: 'Auto',
|
|
1917
|
+
intentDockUnrouted: 'nicht geroutet',
|
|
1918
|
+
intentDockUsing: 'aktiv',
|
|
1919
|
+
intentDockDecided: 'entschieden',
|
|
1920
|
+
intentDockUnconfigured:
|
|
1921
|
+
'Intent-Routing ist nicht vollständig konfiguriert',
|
|
1922
|
+
intentDockCache: 'Cache-Fenster: noch {seconds}s',
|
|
1923
|
+
intentDockOverflowRisk: 'Kandidat {model}: Fenster {window}k liegt unter der Übergabelinie; nahe dem Limit kann es überlaufen.',
|
|
1924
|
+
intentDockTipCandidates: 'Kandidaten: {count}',
|
|
1925
|
+
intentDockTipOn: 'ein',
|
|
1926
|
+
intentDockTipOff: 'aus',
|
|
1927
|
+
intentModelLabelTrigger: 'Auto',
|
|
1928
|
+
intentModelLabelFull: 'Auto (Intent-Routing)',
|
|
1373
1929
|
},
|
|
1374
1930
|
es: {
|
|
1375
1931
|
badge: 'Equipo autónomo',
|
|
@@ -1507,6 +2063,75 @@ window.__ModuleLoader__.load({
|
|
|
1507
2063
|
uiLocaleLabel: 'Idioma de la interfaz',
|
|
1508
2064
|
uiLocaleHelp: 'Automático sigue el idioma de la app DeepSeek Harness; al elegir un idioma, la interfaz del plugin se fija en él.',
|
|
1509
2065
|
uiLocaleAuto: 'Automático (seguir DSH)',
|
|
2066
|
+
intentSectionTitle: 'Enrutado por intención',
|
|
2067
|
+
intentSectionDescription:
|
|
2068
|
+
'Al elegir el pseudo-modelo «Auto», un clasificador pequeño dirige cada turno al mejor candidato según la conversación; con ventana de reutilización de caché y umbral de confianza.',
|
|
2069
|
+
intentEnabledLabel: 'Activar enrutado por intención',
|
|
2070
|
+
intentEnabledHelp:
|
|
2071
|
+
'Interruptor maestro; apagado = la selección «Auto» va directa a la cadena de reserva sin llamada al clasificador.',
|
|
2072
|
+
intentClassifierRouteLabel: 'Modelo clasificador',
|
|
2073
|
+
intentClassifierRouteHelp:
|
|
2074
|
+
'Ruta (provider + model) del modelo pequeño que clasifica cada turno.',
|
|
2075
|
+
intentClassifierEffortLabel: 'Esfuerzo de razonamiento del clasificador',
|
|
2076
|
+
intentClassifierEffortHelp:
|
|
2077
|
+
'Esfuerzo de razonamiento fijo opcional; prevalece sobre el valor por defecto del adaptador.',
|
|
2078
|
+
intentClassifierKindLabel: 'Tipo de clasificador',
|
|
2079
|
+
intentClassifierKindHelp:
|
|
2080
|
+
'chat = modelo de chat del catálogo LLM; systemone = tipo de decisión con endpoint + modelo propios.',
|
|
2081
|
+
intentClassifierKindChat: 'Modelo de chat (catálogo LLM)',
|
|
2082
|
+
intentClassifierKindSystemone: 'Decisión (endpoint+modelo propios)',
|
|
2083
|
+
intentClassifierEndpointLabel: 'Endpoint de decisión',
|
|
2084
|
+
intentClassifierEndpointHelp: 'URL /v1/systemone completa que se llama directamente cuando el tipo de clasificador es el endpoint de decisión (sin proveedor LLM)',
|
|
2085
|
+
intentSystemoneModelLabel: 'Modelo del endpoint de decisión',
|
|
2086
|
+
intentSystemoneModelHelp: 'Parámetro model que se envía al endpoint de decisión (p. ej. tev1:4b-mxfp8); texto libre, no necesita estar en la lista de proveedores',
|
|
2087
|
+
intentEndpointUnset: 'Sin establecer',
|
|
2088
|
+
intentCandidatesLabel: 'Candidatos',
|
|
2089
|
+
intentCandidatesHelp:
|
|
2090
|
+
'Modelos a los que se puede enrutar; note es una descripción de capacidad en una línea que lee el clasificador.',
|
|
2091
|
+
intentCandidatesEmpty:
|
|
2092
|
+
'Sin candidatos: configure al menos 1 candidato para poder enrutar.',
|
|
2093
|
+
intentCandidatesAdd: 'Añadir candidato',
|
|
2094
|
+
intentCandidateRemove: 'Eliminar candidato',
|
|
2095
|
+
intentCandidateEnable: 'Activar',
|
|
2096
|
+
intentCandidateUnset: 'Activada pero sin modelo: excluida del enrutado; se activa al elegir un modelo',
|
|
2097
|
+
intentCandidatePresetBadge: 'Preset',
|
|
2098
|
+
intentCandidateNotePlaceholder:
|
|
2099
|
+
'descripción de capacidad en una línea (para el clasificador)',
|
|
2100
|
+
intentCacheTtlLabel: 'Ventana de reutilización de caché (segundos)',
|
|
2101
|
+
intentCacheTtlHelp:
|
|
2102
|
+
'Dentro de esta ventana una confianza baja mantiene el modelo actual y preserva la caché de prompt del proveedor.',
|
|
2103
|
+
intentSwitchConfidenceLabel: 'Confianza para cambiar (0–100)',
|
|
2104
|
+
intentSwitchConfidenceHelp:
|
|
2105
|
+
'Solo con esta confianza de clasificación se permite dejar el modelo actual.',
|
|
2106
|
+
intentTimeoutLabel: 'Tiempo máximo de clasificación (ms)',
|
|
2107
|
+
intentTimeoutHelp:
|
|
2108
|
+
'Presupuesto de la llamada al clasificador; tiempo agotado o fallo usa la reserva (fail-open).',
|
|
2109
|
+
intentHistoryMessagesLabel: 'Mensajes de historial (1–20)',
|
|
2110
|
+
intentHistoryMessagesHelp:
|
|
2111
|
+
'Cuántos mensajes recientes se entregan al clasificador.',
|
|
2112
|
+
intentHistoryMaxCharsLabel: 'Presupuesto de caracteres del extracto',
|
|
2113
|
+
intentContextWindowLabel: 'Ventana de contexto',
|
|
2114
|
+
intentContextWindowHelp: 'Ventana de contexto (tokens) declarada por el pseudo-modelo Auto; controla la visualización de uso y la presión de compactación. Si 0,8× la ventana queda por debajo del umbral force, el valor efectivo se eleva automáticamente para que el traspaso suave se dispare primero.',
|
|
2115
|
+
intentHistoryMaxCharsHelp:
|
|
2116
|
+
'Límite total de caracteres del extracto de conversación (200–20000).',
|
|
2117
|
+
intentFallbackRouteLabel: 'Modelo de reserva',
|
|
2118
|
+
intentFallbackRouteHelp:
|
|
2119
|
+
'Si falla la clasificación: último modelo enrutado → reserva → primer candidato; vacío = primer candidato.',
|
|
2120
|
+
intentRouteUnset: 'Sin establecer',
|
|
2121
|
+
intentEffortFollow: 'predeterminado del modelo',
|
|
2122
|
+
intentDockAuto: 'Auto',
|
|
2123
|
+
intentDockUnrouted: 'sin enrutar',
|
|
2124
|
+
intentDockUsing: 'en uso',
|
|
2125
|
+
intentDockDecided: 'decidido',
|
|
2126
|
+
intentDockUnconfigured:
|
|
2127
|
+
'El enrutado por intención no está completamente configurado',
|
|
2128
|
+
intentDockCache: 'Ventana de caché: {seconds}s restantes',
|
|
2129
|
+
intentDockOverflowRisk: 'El candidato {model} tiene una ventana de {window}k por debajo de la línea de traspaso; puede desbordarse cerca del límite.',
|
|
2130
|
+
intentDockTipCandidates: 'candidatos: {count}',
|
|
2131
|
+
intentDockTipOn: 'sí',
|
|
2132
|
+
intentDockTipOff: 'no',
|
|
2133
|
+
intentModelLabelTrigger: 'Auto',
|
|
2134
|
+
intentModelLabelFull: 'Auto (enrutado por intención)',
|
|
1510
2135
|
},
|
|
1511
2136
|
fr: {
|
|
1512
2137
|
badge: 'Équipe autonome',
|
|
@@ -1644,6 +2269,75 @@ window.__ModuleLoader__.load({
|
|
|
1644
2269
|
uiLocaleLabel: 'Langue de l’interface',
|
|
1645
2270
|
uiLocaleHelp: 'Auto suit la langue de l’application DeepSeek Harness ; choisir une langue force l’interface du plugin dans cette langue.',
|
|
1646
2271
|
uiLocaleAuto: 'Auto (suivre DSH)',
|
|
2272
|
+
intentSectionTitle: 'Routage par intention',
|
|
2273
|
+
intentSectionDescription:
|
|
2274
|
+
'Lorsque le pseudo-modèle « Auto » est choisi, un petit classificateur route chaque tour vers le meilleur candidat d’après la conversation ; avec fenêtre de réutilisation du cache et seuil de confiance.',
|
|
2275
|
+
intentEnabledLabel: 'Activer le routage par intention',
|
|
2276
|
+
intentEnabledHelp:
|
|
2277
|
+
'Interrupteur principal ; désactivé, la sélection « Auto » passe directement par la chaîne de secours sans appel au classificateur.',
|
|
2278
|
+
intentClassifierRouteLabel: 'Modèle classificateur',
|
|
2279
|
+
intentClassifierRouteHelp:
|
|
2280
|
+
'Route (provider + model) du petit modèle qui classe chaque tour.',
|
|
2281
|
+
intentClassifierEffortLabel: 'Effort de raisonnement du classificateur',
|
|
2282
|
+
intentClassifierEffortHelp:
|
|
2283
|
+
'Effort de raisonnement épinglé facultatif ; prime sur la valeur par défaut de l’adaptateur.',
|
|
2284
|
+
intentClassifierKindLabel: 'Type de classificateur',
|
|
2285
|
+
intentClassifierKindHelp:
|
|
2286
|
+
'chat = modèle de chat du catalogue LLM ; systemone = type de décision avec endpoint + modèle personnalisés.',
|
|
2287
|
+
intentClassifierKindChat: 'Modèle de chat (catalogue LLM)',
|
|
2288
|
+
intentClassifierKindSystemone: 'Décision (endpoint+modèle personnalisés)',
|
|
2289
|
+
intentClassifierEndpointLabel: 'Endpoint de décision',
|
|
2290
|
+
intentClassifierEndpointHelp: 'URL /v1/systemone complète appelée directement quand le type de classificateur est l\'endpoint de décision (sans fournisseur LLM)',
|
|
2291
|
+
intentSystemoneModelLabel: 'Modèle du point de décision',
|
|
2292
|
+
intentSystemoneModelHelp: 'Paramètre model envoyé au point de décision (p. ex. tev1:4b-mxfp8) ; texte libre, inutile qu\'il figure dans la liste des fournisseurs',
|
|
2293
|
+
intentEndpointUnset: 'Non défini',
|
|
2294
|
+
intentCandidatesLabel: 'Candidats',
|
|
2295
|
+
intentCandidatesHelp:
|
|
2296
|
+
'Modèles vers lesquels le routage peut aller ; note est une description de capacité d’une ligne lue par le classificateur.',
|
|
2297
|
+
intentCandidatesEmpty:
|
|
2298
|
+
'Aucun candidat : configurez au moins 1 candidat pour permettre le routage.',
|
|
2299
|
+
intentCandidatesAdd: 'Ajouter un candidat',
|
|
2300
|
+
intentCandidateRemove: 'Supprimer le candidat',
|
|
2301
|
+
intentCandidateEnable: 'Activer',
|
|
2302
|
+
intentCandidateUnset: 'Activée sans modèle — exclue du routage ; choisir un modèle l\'active',
|
|
2303
|
+
intentCandidatePresetBadge: 'Preset',
|
|
2304
|
+
intentCandidateNotePlaceholder:
|
|
2305
|
+
'description de capacité en une ligne (pour le classificateur)',
|
|
2306
|
+
intentCacheTtlLabel: 'Fenêtre de réutilisation du cache (secondes)',
|
|
2307
|
+
intentCacheTtlHelp:
|
|
2308
|
+
'Dans cette fenêtre, une confiance insuffisante conserve le modèle actuel et préserve le cache de prompt du fournisseur.',
|
|
2309
|
+
intentSwitchConfidenceLabel: 'Confiance de bascule (0–100)',
|
|
2310
|
+
intentSwitchConfidenceHelp:
|
|
2311
|
+
'Seule une confiance de classification atteignant cette valeur permet de quitter le modèle actuel.',
|
|
2312
|
+
intentTimeoutLabel: 'Délai de classification (ms)',
|
|
2313
|
+
intentTimeoutHelp:
|
|
2314
|
+
'Budget de l’appel au classificateur ; dépassé ou en échec, le secours prend le relais (fail-open).',
|
|
2315
|
+
intentHistoryMessagesLabel: 'Messages d’historique (1–20)',
|
|
2316
|
+
intentHistoryMessagesHelp:
|
|
2317
|
+
'Nombre de messages récents transmis au classificateur.',
|
|
2318
|
+
intentHistoryMaxCharsLabel: 'Budget de caractères de l’extrait',
|
|
2319
|
+
intentContextWindowLabel: 'Fenêtre de contexte',
|
|
2320
|
+
intentContextWindowHelp: 'Fenêtre de contexte (jetons) déclarée par le pseudo-modèle Auto ; pilote l\'affichage d\'utilisation et le seuil de compression. Si 0,8× la fenêtre passe sous le seuil force, la valeur effective est relevée automatiquement pour que la passation douce se déclenche d\'abord.',
|
|
2321
|
+
intentHistoryMaxCharsHelp:
|
|
2322
|
+
'Plafond total de caractères de l’extrait de conversation (200–20000).',
|
|
2323
|
+
intentFallbackRouteLabel: 'Modèle de secours',
|
|
2324
|
+
intentFallbackRouteHelp:
|
|
2325
|
+
'En cas d’échec : dernier modèle routé → secours → premier candidat ; vide = premier candidat.',
|
|
2326
|
+
intentRouteUnset: 'Non défini',
|
|
2327
|
+
intentEffortFollow: 'par défaut du modèle',
|
|
2328
|
+
intentDockAuto: 'Auto',
|
|
2329
|
+
intentDockUnrouted: 'non routé',
|
|
2330
|
+
intentDockUsing: 'en cours',
|
|
2331
|
+
intentDockDecided: 'décidé',
|
|
2332
|
+
intentDockUnconfigured:
|
|
2333
|
+
'Le routage par intention n’est pas entièrement configuré',
|
|
2334
|
+
intentDockCache: 'Fenêtre de cache : {seconds}s restantes',
|
|
2335
|
+
intentDockOverflowRisk: 'Le candidat {model} a une fenêtre de {window}k sous la ligne de passation ; risque de débordement près du plafond.',
|
|
2336
|
+
intentDockTipCandidates: 'candidats : {count}',
|
|
2337
|
+
intentDockTipOn: 'activé',
|
|
2338
|
+
intentDockTipOff: 'désactivé',
|
|
2339
|
+
intentModelLabelTrigger: 'Auto',
|
|
2340
|
+
intentModelLabelFull: 'Auto (routage par intention)',
|
|
1647
2341
|
},
|
|
1648
2342
|
it: {
|
|
1649
2343
|
badge: 'Squadra autonoma',
|
|
@@ -1781,6 +2475,75 @@ window.__ModuleLoader__.load({
|
|
|
1781
2475
|
uiLocaleLabel: 'Lingua dell’interfaccia',
|
|
1782
2476
|
uiLocaleHelp: 'Auto segue la lingua dell’app DeepSeek Harness; scegliere una lingua forza l’interfaccia del plugin in quella lingua.',
|
|
1783
2477
|
uiLocaleAuto: 'Auto (segui DSH)',
|
|
2478
|
+
intentSectionTitle: 'Instradamento per intento',
|
|
2479
|
+
intentSectionDescription:
|
|
2480
|
+
'Quando si sceglie il pseudo-modello «Auto», un piccolo classificatore instrada ogni turno al candidato migliore in base alla conversazione; con finestra di riutilizzo della cache e soglia di confidenza.',
|
|
2481
|
+
intentEnabledLabel: 'Attiva instradamento per intento',
|
|
2482
|
+
intentEnabledHelp:
|
|
2483
|
+
'Interruttore principale; spento = la selezione «Auto» va direttamente alla catena di riserva senza chiamate di classificazione.',
|
|
2484
|
+
intentClassifierRouteLabel: 'Modello classificatore',
|
|
2485
|
+
intentClassifierRouteHelp:
|
|
2486
|
+
'Route (provider + model) del piccolo modello che classifica ogni turno.',
|
|
2487
|
+
intentClassifierEffortLabel: 'Sforzo di ragionamento del classificatore',
|
|
2488
|
+
intentClassifierEffortHelp:
|
|
2489
|
+
'Sforzo di ragionamento fissato facoltativo; ha precedenza sul valore predefinito dell’adattatore.',
|
|
2490
|
+
intentClassifierKindLabel: 'Tipo di classificatore',
|
|
2491
|
+
intentClassifierKindHelp:
|
|
2492
|
+
'chat = modello di chat dal catalogo LLM; systemone = tipo di decisione con endpoint + modello personalizzati.',
|
|
2493
|
+
intentClassifierKindChat: 'Modello di chat (catalogo LLM)',
|
|
2494
|
+
intentClassifierKindSystemone: 'Decisione (endpoint+modello personalizzati)',
|
|
2495
|
+
intentClassifierEndpointLabel: 'Endpoint di decisione',
|
|
2496
|
+
intentClassifierEndpointHelp: 'URL /v1/systemone completa chiamata direttamente quando il tipo di classificatore è l\'endpoint di decisione (senza provider LLM)',
|
|
2497
|
+
intentSystemoneModelLabel: 'Modello dell\'endpoint di decisione',
|
|
2498
|
+
intentSystemoneModelHelp: 'Parametro model inviato all\'endpoint di decisione (es. tev1:4b-mxfp8); testo libero, non deve stare nell\'elenco dei provider',
|
|
2499
|
+
intentEndpointUnset: 'Non impostato',
|
|
2500
|
+
intentCandidatesLabel: 'Candidati',
|
|
2501
|
+
intentCandidatesHelp:
|
|
2502
|
+
'Modelli verso cui è possibile instradare; note è una descrizione di capacità in una riga letta dal classificatore.',
|
|
2503
|
+
intentCandidatesEmpty:
|
|
2504
|
+
'Nessun candidato: configura almeno 1 candidato per consentire l’instradamento.',
|
|
2505
|
+
intentCandidatesAdd: 'Aggiungi candidato',
|
|
2506
|
+
intentCandidateRemove: 'Rimuovi candidato',
|
|
2507
|
+
intentCandidateEnable: 'Attiva',
|
|
2508
|
+
intentCandidateUnset: 'Attiva ma senza modello: esclusa dal routing; si attiva scegliendo un modello',
|
|
2509
|
+
intentCandidatePresetBadge: 'Preset',
|
|
2510
|
+
intentCandidateNotePlaceholder:
|
|
2511
|
+
'descrizione di capacità in una riga (per il classificatore)',
|
|
2512
|
+
intentCacheTtlLabel: 'Finestra di riutilizzo cache (secondi)',
|
|
2513
|
+
intentCacheTtlHelp:
|
|
2514
|
+
'In questa finestra una confidenza bassa mantiene il modello attuale e preserva la cache dei prompt del fornitore.',
|
|
2515
|
+
intentSwitchConfidenceLabel: 'Confidenza di cambio (0–100)',
|
|
2516
|
+
intentSwitchConfidenceHelp:
|
|
2517
|
+
'Solo con questa confidenza di classificazione è possibile abbandonare il modello attuale.',
|
|
2518
|
+
intentTimeoutLabel: 'Timeout di classificazione (ms)',
|
|
2519
|
+
intentTimeoutHelp:
|
|
2520
|
+
'Budget per la chiamata al classificatore; timeout o errore passano alla riserva (fail-open).',
|
|
2521
|
+
intentHistoryMessagesLabel: 'Messaggi di cronologia (1–20)',
|
|
2522
|
+
intentHistoryMessagesHelp:
|
|
2523
|
+
'Quanti messaggi recenti vengono passati al classificatore.',
|
|
2524
|
+
intentHistoryMaxCharsLabel: 'Budget di caratteri dell’estratto',
|
|
2525
|
+
intentContextWindowLabel: 'Finestra di contesto',
|
|
2526
|
+
intentContextWindowHelp: 'Finestra di contesto (token) dichiarata dal pseudo-modello Auto; determina la visualizzazione dell\'uso e la soglia di compressione. Se 0,8× la finestra scende sotto la soglia force, il valore effettivo viene innalzato automaticamente così la consegna fluida scatta prima.',
|
|
2527
|
+
intentHistoryMaxCharsHelp:
|
|
2528
|
+
'Limite totale di caratteri dell’estratto della conversazione (200–20000).',
|
|
2529
|
+
intentFallbackRouteLabel: 'Modello di riserva',
|
|
2530
|
+
intentFallbackRouteHelp:
|
|
2531
|
+
'In caso di errore: ultimo modello instradato → riserva → primo candidato; vuoto = primo candidato.',
|
|
2532
|
+
intentRouteUnset: 'Non impostato',
|
|
2533
|
+
intentEffortFollow: 'predefinito del modello',
|
|
2534
|
+
intentDockAuto: 'Auto',
|
|
2535
|
+
intentDockUnrouted: 'non instradato',
|
|
2536
|
+
intentDockUsing: 'in uso',
|
|
2537
|
+
intentDockDecided: 'deciso',
|
|
2538
|
+
intentDockUnconfigured:
|
|
2539
|
+
'L’instradamento per intento non è completamente configurato',
|
|
2540
|
+
intentDockCache: 'Finestra cache: {seconds}s rimanenti',
|
|
2541
|
+
intentDockOverflowRisk: 'Il candidato {model} ha una finestra di {window}k sotto la linea di consegna; può traboccare vicino al limite.',
|
|
2542
|
+
intentDockTipCandidates: 'candidati: {count}',
|
|
2543
|
+
intentDockTipOn: 'on',
|
|
2544
|
+
intentDockTipOff: 'off',
|
|
2545
|
+
intentModelLabelTrigger: 'Auto',
|
|
2546
|
+
intentModelLabelFull: 'Auto (instradamento per intento)',
|
|
1784
2547
|
},
|
|
1785
2548
|
pt: {
|
|
1786
2549
|
badge: 'Equipe autônoma',
|
|
@@ -1918,6 +2681,75 @@ window.__ModuleLoader__.load({
|
|
|
1918
2681
|
uiLocaleLabel: 'Idioma da interface',
|
|
1919
2682
|
uiLocaleHelp: 'Auto segue o idioma do aplicativo DeepSeek Harness; escolher um idioma força a interface do plugin para ele.',
|
|
1920
2683
|
uiLocaleAuto: 'Auto (seguir DSH)',
|
|
2684
|
+
intentSectionTitle: 'Roteamento por intenção',
|
|
2685
|
+
intentSectionDescription:
|
|
2686
|
+
'Ao escolher o pseudo-modelo «Auto», um classificador pequeno direciona cada rodada ao melhor candidato conforme a conversa; com janela de reuso de cache e limite de confiança.',
|
|
2687
|
+
intentEnabledLabel: 'Ativar roteamento por intenção',
|
|
2688
|
+
intentEnabledHelp:
|
|
2689
|
+
'Interruptor mestre; desligado = a seleção «Auto» vai direto à cadeia de reserva sem chamada ao classificador.',
|
|
2690
|
+
intentClassifierRouteLabel: 'Modelo classificador',
|
|
2691
|
+
intentClassifierRouteHelp:
|
|
2692
|
+
'Rota (provider + model) do modelo pequeno que classifica cada rodada.',
|
|
2693
|
+
intentClassifierEffortLabel: 'Esforço de raciocínio do classificador',
|
|
2694
|
+
intentClassifierEffortHelp:
|
|
2695
|
+
'Esforço de raciocínio fixo opcional; prevalece sobre o padrão do adaptador.',
|
|
2696
|
+
intentClassifierKindLabel: 'Tipo de classificador',
|
|
2697
|
+
intentClassifierKindHelp:
|
|
2698
|
+
'chat = modelo de conversa do catálogo LLM; systemone = tipo de decisão com endpoint + modelo personalizados.',
|
|
2699
|
+
intentClassifierKindChat: 'Modelo de conversa (catálogo LLM)',
|
|
2700
|
+
intentClassifierKindSystemone: 'Decisão (endpoint+modelo personalizados)',
|
|
2701
|
+
intentClassifierEndpointLabel: 'Endpoint de decisão',
|
|
2702
|
+
intentClassifierEndpointHelp: 'URL /v1/systemone completa chamada diretamente quando o tipo de classificador é o endpoint de decisão (sem provedor LLM)',
|
|
2703
|
+
intentSystemoneModelLabel: 'Modelo do endpoint de decisão',
|
|
2704
|
+
intentSystemoneModelHelp: 'Parâmetro model enviado ao endpoint de decisão (ex.: tev1:4b-mxfp8); texto livre, não precisa estar na lista de provedores',
|
|
2705
|
+
intentEndpointUnset: 'Não definido',
|
|
2706
|
+
intentCandidatesLabel: 'Candidatos',
|
|
2707
|
+
intentCandidatesHelp:
|
|
2708
|
+
'Modelos para os quais o roteamento pode ir; note é uma descrição de capacidade em uma linha lida pelo classificador.',
|
|
2709
|
+
intentCandidatesEmpty:
|
|
2710
|
+
'Sem candidatos: configure pelo menos 1 candidato para permitir o roteamento.',
|
|
2711
|
+
intentCandidatesAdd: 'Adicionar candidato',
|
|
2712
|
+
intentCandidateRemove: 'Remover candidato',
|
|
2713
|
+
intentCandidateEnable: 'Ativar',
|
|
2714
|
+
intentCandidateUnset: 'Ativada sem modelo: excluída do roteamento; escolher um modelo ativa',
|
|
2715
|
+
intentCandidatePresetBadge: 'Preset',
|
|
2716
|
+
intentCandidateNotePlaceholder:
|
|
2717
|
+
'descrição de capacidade em uma linha (para o classificador)',
|
|
2718
|
+
intentCacheTtlLabel: 'Janela de reuso de cache (segundos)',
|
|
2719
|
+
intentCacheTtlHelp:
|
|
2720
|
+
'Dentro desta janela, confiança baixa mantém o modelo atual e preserva o cache de prompt do fornecedor.',
|
|
2721
|
+
intentSwitchConfidenceLabel: 'Confiança para troca (0–100)',
|
|
2722
|
+
intentSwitchConfidenceHelp:
|
|
2723
|
+
'Só com essa confiança de classificação é permitido sair do modelo atual.',
|
|
2724
|
+
intentTimeoutLabel: 'Tempo limite da classificação (ms)',
|
|
2725
|
+
intentTimeoutHelp:
|
|
2726
|
+
'Orçamento da chamada ao classificador; tempo esgotado ou falha usa a reserva (fail-open).',
|
|
2727
|
+
intentHistoryMessagesLabel: 'Mensagens de histórico (1–20)',
|
|
2728
|
+
intentHistoryMessagesHelp:
|
|
2729
|
+
'Quantas mensagens recentes são entregues ao classificador.',
|
|
2730
|
+
intentHistoryMaxCharsLabel: 'Orçamento de caracteres do trecho',
|
|
2731
|
+
intentContextWindowLabel: 'Janela de contexto',
|
|
2732
|
+
intentContextWindowHelp: 'Janela de contexto (tokens) declarada pelo pseudo-modelo Auto; controla a exibição de uso e o limiar de compressão. Se 0,8× a janela ficar abaixo do limiar force, o valor efetivo é elevado automaticamente para que o handover suave dispare primeiro.',
|
|
2733
|
+
intentHistoryMaxCharsHelp:
|
|
2734
|
+
'Limite total de caracteres do trecho da conversa (200–20000).',
|
|
2735
|
+
intentFallbackRouteLabel: 'Modelo de reserva',
|
|
2736
|
+
intentFallbackRouteHelp:
|
|
2737
|
+
'Se a classificação falhar: último modelo roteado → reserva → primeiro candidato; vazio = primeiro candidato.',
|
|
2738
|
+
intentRouteUnset: 'Não definido',
|
|
2739
|
+
intentEffortFollow: 'padrão do modelo',
|
|
2740
|
+
intentDockAuto: 'Auto',
|
|
2741
|
+
intentDockUnrouted: 'sem rota',
|
|
2742
|
+
intentDockUsing: 'em uso',
|
|
2743
|
+
intentDockDecided: 'decidido',
|
|
2744
|
+
intentDockUnconfigured:
|
|
2745
|
+
'O roteamento por intenção não está totalmente configurado',
|
|
2746
|
+
intentDockCache: 'Janela de cache: {seconds}s restantes',
|
|
2747
|
+
intentDockOverflowRisk: 'O candidato {model} tem uma janela de {window}k abaixo da linha de handover; pode transbordar perto do limite.',
|
|
2748
|
+
intentDockTipCandidates: 'candidatos: {count}',
|
|
2749
|
+
intentDockTipOn: 'ligado',
|
|
2750
|
+
intentDockTipOff: 'desligado',
|
|
2751
|
+
intentModelLabelTrigger: 'Auto',
|
|
2752
|
+
intentModelLabelFull: 'Auto (roteamento por intenção)',
|
|
1921
2753
|
},
|
|
1922
2754
|
ru: {
|
|
1923
2755
|
badge: 'Автономная команда',
|
|
@@ -2055,6 +2887,75 @@ window.__ModuleLoader__.load({
|
|
|
2055
2887
|
uiLocaleLabel: 'Язык интерфейса',
|
|
2056
2888
|
uiLocaleHelp: '«Авто» следует языку приложения DeepSeek Harness; выбор конкретного языка принудительно переводит на него интерфейс плагина.',
|
|
2057
2889
|
uiLocaleAuto: 'Авто (следовать DSH)',
|
|
2890
|
+
intentSectionTitle: 'Маршрутизация по намерению',
|
|
2891
|
+
intentSectionDescription:
|
|
2892
|
+
'При выборе псевдомодели «Авто» небольшой классификатор направляет каждый ход к лучшему кандидату по содержанию диалога; с окном повторного использования кэша и порогом уверенности.',
|
|
2893
|
+
intentEnabledLabel: 'Включить маршрутизацию по намерению',
|
|
2894
|
+
intentEnabledHelp:
|
|
2895
|
+
'Главный выключатель; выкл. = выбор «Авто» сразу идёт по резервной цепочке без вызова классификатора.',
|
|
2896
|
+
intentClassifierRouteLabel: 'Модель-классификатор',
|
|
2897
|
+
intentClassifierRouteHelp:
|
|
2898
|
+
'Маршрут (provider + model) небольшой модели, классифицирующей каждый ход.',
|
|
2899
|
+
intentClassifierEffortLabel: 'Усилие рассуждения классификатора',
|
|
2900
|
+
intentClassifierEffortHelp:
|
|
2901
|
+
'Необязательное фиксированное reasoning effort; важнее значения адаптера по умолчанию.',
|
|
2902
|
+
intentClassifierKindLabel: 'Тип классификатора',
|
|
2903
|
+
intentClassifierKindHelp:
|
|
2904
|
+
'chat = диалоговая модель из каталога LLM; systemone = тип решения с собственными endpoint + моделью.',
|
|
2905
|
+
intentClassifierKindChat: 'Диалоговая модель (каталог LLM)',
|
|
2906
|
+
intentClassifierKindSystemone: 'Решение (свой endpoint+модель)',
|
|
2907
|
+
intentClassifierEndpointLabel: 'Endpoint решений',
|
|
2908
|
+
intentClassifierEndpointHelp: 'Полный URL /v1/systemone, вызываемый напрямую, когда тип классификатора — endpoint решений (без LLM-провайдера)',
|
|
2909
|
+
intentSystemoneModelLabel: 'Модель endpoint решений',
|
|
2910
|
+
intentSystemoneModelHelp: 'Параметр model, отправляемый на endpoint решений (напр. tev1:4b-mxfp8); свободный текст, запись в списке провайдеров не нужна',
|
|
2911
|
+
intentEndpointUnset: 'Не задано',
|
|
2912
|
+
intentCandidatesLabel: 'Кандидаты',
|
|
2913
|
+
intentCandidatesHelp:
|
|
2914
|
+
'Модели, к которым возможна маршрутизация; note — описание возможностей одной строкой для классификатора.',
|
|
2915
|
+
intentCandidatesEmpty:
|
|
2916
|
+
'Нет кандидатов: для маршрутизации нужен хотя бы 1 кандидат.',
|
|
2917
|
+
intentCandidatesAdd: 'Добавить кандидата',
|
|
2918
|
+
intentCandidateRemove: 'Удалить кандидата',
|
|
2919
|
+
intentCandidateEnable: 'Включить',
|
|
2920
|
+
intentCandidateUnset: 'Включено, но модель не выбрана: исключено из маршрутизации; активируется после выбора модели',
|
|
2921
|
+
intentCandidatePresetBadge: 'Пресет',
|
|
2922
|
+
intentCandidateNotePlaceholder:
|
|
2923
|
+
'описание возможностей одной строкой (для классификатора)',
|
|
2924
|
+
intentCacheTtlLabel: 'Окно повторного использования кэша (секунды)',
|
|
2925
|
+
intentCacheTtlHelp:
|
|
2926
|
+
'Внутри окна при низкой уверенности сохраняется текущая модель — prompt-кэш провайдера не разрывается.',
|
|
2927
|
+
intentSwitchConfidenceLabel: 'Уверенность переключения (0–100)',
|
|
2928
|
+
intentSwitchConfidenceHelp:
|
|
2929
|
+
'Только при такой уверенности классификации можно покинуть текущую модель.',
|
|
2930
|
+
intentTimeoutLabel: 'Тайм-аут классификации (мс)',
|
|
2931
|
+
intentTimeoutHelp:
|
|
2932
|
+
'Бюджет вызова классификатора; тайм-аут или сбой — резервная цепочка (fail-open).',
|
|
2933
|
+
intentHistoryMessagesLabel: 'Сообщений истории (1–20)',
|
|
2934
|
+
intentHistoryMessagesHelp:
|
|
2935
|
+
'Сколько последних сообщений передаётся классификатору.',
|
|
2936
|
+
intentHistoryMaxCharsLabel: 'Лимит символов выборки',
|
|
2937
|
+
intentContextWindowLabel: 'Окно контекста',
|
|
2938
|
+
intentContextWindowHelp: 'Окно контекста (токены), объявляемое псевдомоделью Auto; определяет отображение использования и порог сжатия. Если 0,8× окна опускается ниже порога force, действующее значение автоматически поднимается, чтобы плавная передача сработала раньше.',
|
|
2939
|
+
intentHistoryMaxCharsHelp:
|
|
2940
|
+
'Общий лимит символов выборки диалога (200–20000).',
|
|
2941
|
+
intentFallbackRouteLabel: 'Резервная модель',
|
|
2942
|
+
intentFallbackRouteHelp:
|
|
2943
|
+
'При сбое классификации: прошлая модель → резерв → первый кандидат; пусто = первый кандидат.',
|
|
2944
|
+
intentRouteUnset: 'Не задано',
|
|
2945
|
+
intentEffortFollow: 'по умолчанию модели',
|
|
2946
|
+
intentDockAuto: 'Авто',
|
|
2947
|
+
intentDockUnrouted: 'не маршрутизировано',
|
|
2948
|
+
intentDockUsing: 'в работе',
|
|
2949
|
+
intentDockDecided: 'решено',
|
|
2950
|
+
intentDockUnconfigured:
|
|
2951
|
+
'Маршрутизация по намерению настроена не полностью',
|
|
2952
|
+
intentDockCache: 'Окно кэша: осталось {seconds} с',
|
|
2953
|
+
intentDockOverflowRisk: 'Кандидат {model} с окном {window}k ниже линии передачи; возможно переполнение у лимита.',
|
|
2954
|
+
intentDockTipCandidates: 'кандидаты: {count}',
|
|
2955
|
+
intentDockTipOn: 'вкл',
|
|
2956
|
+
intentDockTipOff: 'выкл',
|
|
2957
|
+
intentModelLabelTrigger: 'Авто',
|
|
2958
|
+
intentModelLabelFull: 'Авто (маршрутизация по намерению)',
|
|
2058
2959
|
},
|
|
2059
2960
|
};
|
|
2060
2961
|
|
|
@@ -2307,11 +3208,17 @@ window.__ModuleLoader__.load({
|
|
|
2307
3208
|
* 1. Guidance chip — DSH refuses to send pasted images while the
|
|
2308
3209
|
* session model has no image input; switchman's unlock is the
|
|
2309
3210
|
* /vision command backed by the vision pool. The chip renders
|
|
2310
|
-
*
|
|
2311
|
-
* false
|
|
2312
|
-
*
|
|
2313
|
-
*
|
|
2314
|
-
*
|
|
3211
|
+
* ONLY when the Host probe positively confirms a text-only
|
|
3212
|
+
* model (imageCapable === false); null (unresolved — no llm
|
|
3213
|
+
* service, unknown session, fresh auto session with no routing
|
|
3214
|
+
* decision yet) stays silent, because claiming "not supported"
|
|
3215
|
+
* without a verdict was this feature's own bug (it warned on
|
|
3216
|
+
* vision-capable models too). Refreshes IMMEDIATELY on a model
|
|
3217
|
+
* switch (the modelSelection projection's `next` flips the moment
|
|
3218
|
+
* the dropdown pick lands) and after each send (an auto session's
|
|
3219
|
+
* decided route may change) via staggered catch-up fetches, with
|
|
3220
|
+
* the slow 15s poll only as a fallback; any failure renders
|
|
3221
|
+
* nothing.
|
|
2315
3222
|
* 2. Reactive auto-conversion (v1) — when a plain send is refused
|
|
2316
3223
|
* host-side (session/attachment-invalid), rewrite the draft as
|
|
2317
3224
|
* "/vision <original text>" and resubmit once; the claimed
|
|
@@ -2320,13 +3227,19 @@ window.__ModuleLoader__.load({
|
|
|
2320
3227
|
* the authoritative image-gate signal; the probe's
|
|
2321
3228
|
* imageCapable only brakes the conversion when it positively
|
|
2322
3229
|
* says true. Session-scoped dock entries receive the standard
|
|
2323
|
-
* kit (inputActions, useInput, useSession)
|
|
2324
|
-
* loop safety = one conversion per promptError
|
|
2325
|
-
* identity + never converting a draft that already
|
|
2326
|
-
* with "/". */
|
|
2327
|
-
function VisionDockEntry({ t, sessionId, inputActions, useInput, useSession }) {
|
|
3230
|
+
* kit (inputActions, useInput, useSession, useProjection)
|
|
3231
|
+
* automatically; loop safety = one conversion per promptError
|
|
3232
|
+
* object identity + never converting a draft that already
|
|
3233
|
+
* starts with "/". */
|
|
3234
|
+
function VisionDockEntry({ t, sessionId, inputActions, useInput, useSession, useProjection }) {
|
|
2328
3235
|
const [vision, setVision] = React.useState(null);
|
|
2329
3236
|
const [notice, setNotice] = React.useState(false);
|
|
3237
|
+
// Refresh pulse — bumped by model-selection changes (composer
|
|
3238
|
+
// dropdown) and send bursts (draft dropping to empty), mirroring
|
|
3239
|
+
// IntentDockEntry's burst mechanism so the verdict updates
|
|
3240
|
+
// immediately on a model switch instead of waiting out the
|
|
3241
|
+
// 15s fallback poll. Restarts the probe effect below.
|
|
3242
|
+
const [pulse, setPulse] = React.useState(0);
|
|
2330
3243
|
const handled = React.useRef(null);
|
|
2331
3244
|
const latest = React.useRef({ vision: null, draft: '', attachmentIds: [] });
|
|
2332
3245
|
React.useEffect(() => {
|
|
@@ -2337,12 +3250,22 @@ window.__ModuleLoader__.load({
|
|
|
2337
3250
|
// imageCapable from that session's model selection (subagent
|
|
2338
3251
|
// sessions included); without it the Host falls back to the
|
|
2339
3252
|
// root session and mislabels text-only children. Restarting
|
|
2340
|
-
// the loop on `sessionId` re-probes immediately on a
|
|
3253
|
+
// the loop on `sessionId`/`pulse` re-probes immediately on a
|
|
3254
|
+
// switch or a model-selection change.
|
|
2341
3255
|
const query =
|
|
2342
3256
|
typeof sessionId === 'string' && sessionId !== ''
|
|
2343
3257
|
? `?session=${encodeURIComponent(sessionId)}`
|
|
2344
3258
|
: '';
|
|
2345
3259
|
const tick = async () => {
|
|
3260
|
+
// One fetch at a time; a catch-up landing mid-flight
|
|
3261
|
+
// queues exactly one short-delay re-run instead of
|
|
3262
|
+
// forking a second poll loop (same guard as the intent
|
|
3263
|
+
// chip).
|
|
3264
|
+
if (inflight) {
|
|
3265
|
+
rerun = true;
|
|
3266
|
+
return;
|
|
3267
|
+
}
|
|
3268
|
+
inflight = true;
|
|
2346
3269
|
let next = null;
|
|
2347
3270
|
try {
|
|
2348
3271
|
const reply = await api.get(`vision-state${query}`);
|
|
@@ -2354,16 +3277,32 @@ window.__ModuleLoader__.load({
|
|
|
2354
3277
|
} catch {
|
|
2355
3278
|
next = null;
|
|
2356
3279
|
}
|
|
3280
|
+
inflight = false;
|
|
2357
3281
|
if (stopped) return;
|
|
2358
3282
|
setVision(next);
|
|
2359
|
-
timer = setTimeout(tick, 15_000);
|
|
3283
|
+
timer = setTimeout(tick, rerun ? 400 : 15_000);
|
|
3284
|
+
rerun = false;
|
|
2360
3285
|
};
|
|
3286
|
+
let inflight = false;
|
|
3287
|
+
let rerun = false;
|
|
2361
3288
|
void tick();
|
|
3289
|
+
// Catch-up fetches after the trigger — the model/selection
|
|
3290
|
+
// event needs one append round-trip before the Host's
|
|
3291
|
+
// observeSession read reflects it, and auto sessions may
|
|
3292
|
+
// re-decide the route during prepareCall; 4s covers both.
|
|
3293
|
+
const catchups = [400, 1800, 4000].map((ms) =>
|
|
3294
|
+
setTimeout(() => {
|
|
3295
|
+
if (stopped) return;
|
|
3296
|
+
if (timer !== null) clearTimeout(timer);
|
|
3297
|
+
void tick();
|
|
3298
|
+
}, ms),
|
|
3299
|
+
);
|
|
2362
3300
|
return () => {
|
|
2363
3301
|
stopped = true;
|
|
2364
3302
|
if (timer !== null) clearTimeout(timer);
|
|
3303
|
+
for (const c of catchups) clearTimeout(c);
|
|
2365
3304
|
};
|
|
2366
|
-
}, [sessionId]);
|
|
3305
|
+
}, [sessionId, pulse]);
|
|
2367
3306
|
// Standard kit props are guaranteed by the session scope, but the
|
|
2368
3307
|
// hooks must be called unconditionally — degrade to no-op sources
|
|
2369
3308
|
// if a future shell ever mounts this entry outside that scope.
|
|
@@ -2371,11 +3310,45 @@ window.__ModuleLoader__.load({
|
|
|
2371
3310
|
typeof useInput === 'function' ? useInput : () => () => ({ draft: '', attachmentIds: [] });
|
|
2372
3311
|
const sessionHook =
|
|
2373
3312
|
typeof useSession === 'function' ? useSession : () => () => null;
|
|
3313
|
+
const projectionHook =
|
|
3314
|
+
typeof useProjection === 'function' ? useProjection : () => () => undefined;
|
|
2374
3315
|
const input = inputHook((s) => s) ?? {};
|
|
2375
3316
|
const promptError = sessionHook((s) => s.promptError);
|
|
3317
|
+
// Wire view of the durable model-selection fold (same source the
|
|
3318
|
+
// composer dropdown reads); `next` flips the moment a
|
|
3319
|
+
// model/selection event lands, before any request runs.
|
|
3320
|
+
const selection = projectionHook('modelSelection');
|
|
3321
|
+
const draft = typeof input.draft === 'string' ? input.draft : '';
|
|
3322
|
+
// Pulse trigger 1: any model-selection change (dropdown pick).
|
|
3323
|
+
// The key string covers next+lastUsed so a re-render with a new
|
|
3324
|
+
// object identity but the same selection does not pulse; the ref
|
|
3325
|
+
// seeds from the first render's key so mounting with an existing
|
|
3326
|
+
// selection does not pulse either (same seeding pattern as
|
|
3327
|
+
// prevDraft below and IntentDockEntry's burst ref).
|
|
3328
|
+
const selectionKey =
|
|
3329
|
+
selection && typeof selection === 'object'
|
|
3330
|
+
? [
|
|
3331
|
+
selection.next?.provider ?? '',
|
|
3332
|
+
selection.next?.model ?? '',
|
|
3333
|
+
selection.next?.reasoningEffort ?? '',
|
|
3334
|
+
selection.lastUsed?.provider ?? '',
|
|
3335
|
+
selection.lastUsed?.model ?? '',
|
|
3336
|
+
].join('|')
|
|
3337
|
+
: null;
|
|
3338
|
+
const prevSelectionKey = React.useRef(selectionKey);
|
|
3339
|
+
// Pulse trigger 2: send burst (draft dropping to empty) — an
|
|
3340
|
+
// auto session's decided route may have changed.
|
|
3341
|
+
const prevDraft = React.useRef(draft);
|
|
3342
|
+
React.useEffect(() => {
|
|
3343
|
+
const selectionChanged = prevSelectionKey.current !== selectionKey;
|
|
3344
|
+
const sendBurst = prevDraft.current !== '' && draft === '';
|
|
3345
|
+
prevSelectionKey.current = selectionKey;
|
|
3346
|
+
prevDraft.current = draft;
|
|
3347
|
+
if (selectionChanged || sendBurst) setPulse((n) => n + 1);
|
|
3348
|
+
}, [selectionKey, draft]);
|
|
2376
3349
|
latest.current = {
|
|
2377
3350
|
vision,
|
|
2378
|
-
draft
|
|
3351
|
+
draft,
|
|
2379
3352
|
attachmentIds: Array.isArray(input.attachmentIds) ? input.attachmentIds : [],
|
|
2380
3353
|
};
|
|
2381
3354
|
React.useEffect(() => {
|
|
@@ -2450,11 +3423,13 @@ window.__ModuleLoader__.load({
|
|
|
2450
3423
|
t('visionAutoConverted'),
|
|
2451
3424
|
)
|
|
2452
3425
|
: null,
|
|
2453
|
-
// Hint
|
|
2454
|
-
//
|
|
2455
|
-
//
|
|
2456
|
-
//
|
|
2457
|
-
|
|
3426
|
+
// Hint ONLY on a positively confirmed text-only model
|
|
3427
|
+
// (imageCapable === false). null means "unknown" (no llm
|
|
3428
|
+
// service, unreadable session, auto session with no routing
|
|
3429
|
+
// decision yet) — render nothing rather than a false
|
|
3430
|
+
// "not supported" claim; the refusal-driven auto-conversion
|
|
3431
|
+
// above still covers genuinely text-only sends.
|
|
3432
|
+
vision !== null && vision.imageCapable === false
|
|
2458
3433
|
? h(
|
|
2459
3434
|
'p',
|
|
2460
3435
|
{
|
|
@@ -2474,6 +3449,248 @@ window.__ModuleLoader__.load({
|
|
|
2474
3449
|
);
|
|
2475
3450
|
}
|
|
2476
3451
|
|
|
3452
|
+
/** Composer dock chip for intent routing (conversation.input.dock,
|
|
3453
|
+
* order 89 — one slot above the vision entry's 90): shows where the
|
|
3454
|
+
* session's auto-selected pseudo-model currently routes AND the
|
|
3455
|
+
* last decision's recommendation with its confidence as two labeled
|
|
3456
|
+
* segments (they coincide — decide-and-apply — but the labels make
|
|
3457
|
+
* "in use" vs "decided" explicit). Polls the Host intent-state
|
|
3458
|
+
* route every 15s exactly like the vision entry; a failed fetch or
|
|
3459
|
+
* a non-auto session renders null (fail-silent).
|
|
3460
|
+
* Pure display — no interactive controls (reset arrives later). */
|
|
3461
|
+
function IntentDockEntry({ t, sessionId, useInput }) {
|
|
3462
|
+
const [intent, setIntent] = React.useState(null);
|
|
3463
|
+
// Send-burst detection: the composer draft dropping from non-empty
|
|
3464
|
+
// to empty marks the moment a message leaves — the Host classifies
|
|
3465
|
+
// DURING that turn's prepareCall, so a few staggered catch-up
|
|
3466
|
+
// fetches right after make the chip flip in well under a second
|
|
3467
|
+
// instead of waiting for the 15s poll.
|
|
3468
|
+
const inputHook =
|
|
3469
|
+
typeof useInput === 'function' ? useInput : () => () => ({ draft: '' });
|
|
3470
|
+
const input = inputHook((s) => s) ?? {};
|
|
3471
|
+
const draft = typeof input.draft === 'string' ? input.draft : '';
|
|
3472
|
+
const prevDraft = React.useRef(draft);
|
|
3473
|
+
const [burst, setBurst] = React.useState(0);
|
|
3474
|
+
React.useEffect(() => {
|
|
3475
|
+
const was = prevDraft.current;
|
|
3476
|
+
prevDraft.current = draft;
|
|
3477
|
+
if (was !== '' && draft === '') setBurst((n) => n + 1);
|
|
3478
|
+
}, [draft]);
|
|
3479
|
+
React.useEffect(() => {
|
|
3480
|
+
let stopped = false;
|
|
3481
|
+
let timer = null;
|
|
3482
|
+
let inflight = false;
|
|
3483
|
+
let rerun = false;
|
|
3484
|
+
// Session-scoped slot: pass the scope's identity so the Host
|
|
3485
|
+
// resolves intent-state for THIS session (restart on switch).
|
|
3486
|
+
const query =
|
|
3487
|
+
typeof sessionId === 'string' && sessionId !== ''
|
|
3488
|
+
? `?session=${encodeURIComponent(sessionId)}`
|
|
3489
|
+
: '';
|
|
3490
|
+
const tick = async () => {
|
|
3491
|
+
// One fetch at a time; a catch-up landing mid-flight
|
|
3492
|
+
// queues exactly one short-delay re-run instead of
|
|
3493
|
+
// forking a second poll loop.
|
|
3494
|
+
if (inflight) {
|
|
3495
|
+
rerun = true;
|
|
3496
|
+
return;
|
|
3497
|
+
}
|
|
3498
|
+
inflight = true;
|
|
3499
|
+
let next = null;
|
|
3500
|
+
try {
|
|
3501
|
+
const reply = await api.get(`intent-state${query}`);
|
|
3502
|
+
const value = reply?.ok ? reply.value : null;
|
|
3503
|
+
// Shape per design §5; anything unexpected degrades to
|
|
3504
|
+
// null (no chip), never to a wrong label.
|
|
3505
|
+
if (
|
|
3506
|
+
value &&
|
|
3507
|
+
typeof value === 'object' &&
|
|
3508
|
+
value.autoSelected === true
|
|
3509
|
+
)
|
|
3510
|
+
next = {
|
|
3511
|
+
enabled: value.enabled === true,
|
|
3512
|
+
configured: value.configured === true,
|
|
3513
|
+
model:
|
|
3514
|
+
value.current &&
|
|
3515
|
+
typeof value.current === 'object' &&
|
|
3516
|
+
typeof value.current.model === 'string' &&
|
|
3517
|
+
value.current.model !== ''
|
|
3518
|
+
? value.current.model
|
|
3519
|
+
: null,
|
|
3520
|
+
confidence:
|
|
3521
|
+
value.lastDecision &&
|
|
3522
|
+
typeof value.lastDecision === 'object' &&
|
|
3523
|
+
typeof value.lastDecision.confidence === 'number'
|
|
3524
|
+
? value.lastDecision.confidence
|
|
3525
|
+
: null,
|
|
3526
|
+
decisionModel:
|
|
3527
|
+
// lastDecision.best is "provider/model"; display the
|
|
3528
|
+
// model part only, matching the routed model above.
|
|
3529
|
+
value.lastDecision &&
|
|
3530
|
+
typeof value.lastDecision === 'object' &&
|
|
3531
|
+
typeof value.lastDecision.best === 'string' &&
|
|
3532
|
+
value.lastDecision.best !== ''
|
|
3533
|
+
? value.lastDecision.best.slice(value.lastDecision.best.indexOf('/') + 1)
|
|
3534
|
+
: null,
|
|
3535
|
+
cacheSeconds:
|
|
3536
|
+
typeof value.cacheWindowRemainingMs === 'number' &&
|
|
3537
|
+
value.cacheWindowRemainingMs > 0
|
|
3538
|
+
? Math.round(value.cacheWindowRemainingMs / 1000)
|
|
3539
|
+
: null,
|
|
3540
|
+
// context.overflowRisk passthrough: keep the
|
|
3541
|
+
// risk line only when the Host also named the
|
|
3542
|
+
// narrowest candidate and its window (the
|
|
3543
|
+
// tooltip needs both); anything missing
|
|
3544
|
+
// degrades to null like every other segment.
|
|
3545
|
+
overflowRisk:
|
|
3546
|
+
value.context &&
|
|
3547
|
+
typeof value.context === 'object' &&
|
|
3548
|
+
value.context.overflowRisk === true &&
|
|
3549
|
+
typeof value.context.minCandidateWindow === 'number' &&
|
|
3550
|
+
value.context.minCandidateWindow > 0 &&
|
|
3551
|
+
typeof value.context.minCandidateModel === 'string' &&
|
|
3552
|
+
value.context.minCandidateModel !== ''
|
|
3553
|
+
? {
|
|
3554
|
+
model: value.context.minCandidateModel,
|
|
3555
|
+
windowK: Math.round(
|
|
3556
|
+
value.context.minCandidateWindow / 1000,
|
|
3557
|
+
),
|
|
3558
|
+
}
|
|
3559
|
+
: null,
|
|
3560
|
+
candidates: Array.isArray(value.candidates)
|
|
3561
|
+
? value.candidates.length
|
|
3562
|
+
: 0,
|
|
3563
|
+
};
|
|
3564
|
+
} catch {
|
|
3565
|
+
next = null;
|
|
3566
|
+
}
|
|
3567
|
+
inflight = false;
|
|
3568
|
+
if (stopped) return;
|
|
3569
|
+
setIntent(next);
|
|
3570
|
+
timer = setTimeout(tick, rerun ? 400 : 15_000);
|
|
3571
|
+
rerun = false;
|
|
3572
|
+
};
|
|
3573
|
+
void tick();
|
|
3574
|
+
// Catch-up fetches after each send (burst) — the decision
|
|
3575
|
+
// lands during prepareCall, typically within milliseconds
|
|
3576
|
+
// for a local classifier; 4s covers a slow chat-channel one.
|
|
3577
|
+
const catchups = [400, 1800, 4000].map((ms) =>
|
|
3578
|
+
setTimeout(() => {
|
|
3579
|
+
if (stopped) return;
|
|
3580
|
+
if (timer !== null) clearTimeout(timer);
|
|
3581
|
+
void tick();
|
|
3582
|
+
}, ms),
|
|
3583
|
+
);
|
|
3584
|
+
return () => {
|
|
3585
|
+
stopped = true;
|
|
3586
|
+
if (timer !== null) clearTimeout(timer);
|
|
3587
|
+
for (const c of catchups) clearTimeout(c);
|
|
3588
|
+
};
|
|
3589
|
+
}, [sessionId, burst]);
|
|
3590
|
+
if (intent === null) return null;
|
|
3591
|
+
const tipLines = [
|
|
3592
|
+
`${t('intentEnabledLabel')}: ${t(
|
|
3593
|
+
intent.enabled ? 'intentDockTipOn' : 'intentDockTipOff',
|
|
3594
|
+
)}`,
|
|
3595
|
+
intent.configured ? null : t('intentDockUnconfigured'),
|
|
3596
|
+
t('intentDockTipCandidates', { count: intent.candidates }),
|
|
3597
|
+
intent.cacheSeconds !== null
|
|
3598
|
+
? t('intentDockCache', { seconds: intent.cacheSeconds })
|
|
3599
|
+
: null,
|
|
3600
|
+
intent.overflowRisk !== null
|
|
3601
|
+
? t('intentDockOverflowRisk', {
|
|
3602
|
+
model: intent.overflowRisk.model,
|
|
3603
|
+
window: intent.overflowRisk.windowK,
|
|
3604
|
+
})
|
|
3605
|
+
: null,
|
|
3606
|
+
].filter((line) => line !== null);
|
|
3607
|
+
// Same dock-column formula as VisionDockEntry: keep the chip's
|
|
3608
|
+
// left edge tracking the centered composer card in both shell
|
|
3609
|
+
// states (hero narrows the stack, active does not).
|
|
3610
|
+
return h(
|
|
3611
|
+
'div',
|
|
3612
|
+
{
|
|
3613
|
+
'data-dsh-switchman': 'intent-dock-column',
|
|
3614
|
+
style: {
|
|
3615
|
+
boxSizing: 'border-box',
|
|
3616
|
+
width: 'calc(100% - var(--dsh-composer-side-clearance, 16px) - var(--dsh-composer-side-clearance, 16px))',
|
|
3617
|
+
maxWidth: 'var(--dsh-composer-card-max-width, 780px)',
|
|
3618
|
+
margin: '0 auto',
|
|
3619
|
+
flex: 'none',
|
|
3620
|
+
display: 'flex',
|
|
3621
|
+
flexDirection: 'column',
|
|
3622
|
+
gap: 'var(--dsh-composer-stack-gap, 6px)',
|
|
3623
|
+
},
|
|
3624
|
+
},
|
|
3625
|
+
h(
|
|
3626
|
+
'p',
|
|
3627
|
+
{
|
|
3628
|
+
'data-dsh-switchman': 'intent-dock-chip',
|
|
3629
|
+
title: tipLines.join(' · '),
|
|
3630
|
+
style: {
|
|
3631
|
+
margin: '0',
|
|
3632
|
+
padding: '2px 0',
|
|
3633
|
+
color: intent.configured
|
|
3634
|
+
? 'var(--dsw-alias-label-secondary)'
|
|
3635
|
+
: 'var(--dsw-alias-state-error-primary)',
|
|
3636
|
+
fontSize: '12px',
|
|
3637
|
+
lineHeight: '1.6',
|
|
3638
|
+
},
|
|
3639
|
+
},
|
|
3640
|
+
'⚡ ',
|
|
3641
|
+
t('intentDockAuto'),
|
|
3642
|
+
' · ',
|
|
3643
|
+
// Dual display: the route in use RIGHT NOW and the last
|
|
3644
|
+
// decision's recommendation with its confidence — they
|
|
3645
|
+
// coincide after every send (decide-and-apply), but the
|
|
3646
|
+
// explicit labels answer "is this what I'm using or what
|
|
3647
|
+
// it suggests?" without guessing.
|
|
3648
|
+
intent.model !== null
|
|
3649
|
+
? [
|
|
3650
|
+
`${t('intentDockUsing')} ${intent.model}`,
|
|
3651
|
+
intent.decisionModel !== null
|
|
3652
|
+
? `${t('intentDockDecided')} ${intent.decisionModel}${
|
|
3653
|
+
intent.confidence !== null ? ` (${intent.confidence}%)` : ''
|
|
3654
|
+
}`
|
|
3655
|
+
: null,
|
|
3656
|
+
]
|
|
3657
|
+
.filter((part) => part !== null)
|
|
3658
|
+
.join(' · ')
|
|
3659
|
+
: t('intentDockUnrouted'),
|
|
3660
|
+
intent.configured ? null : ` · ${t('intentDockUnconfigured')}`,
|
|
3661
|
+
),
|
|
3662
|
+
);
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3665
|
+
/** Composer model-label override (conversation.input.model.label):
|
|
3666
|
+
* SINGLE seat — once registered, this component renders EVERY
|
|
3667
|
+
* model's label in trigger, menu, and option rows; the owner's
|
|
3668
|
+
* `fallback` (model.name) applies only while the seat is
|
|
3669
|
+
* unregistered. So: the pseudo provider 'dsh-switchman-auto'
|
|
3670
|
+
* presents as the short 「自动」 in the trigger caption and
|
|
3671
|
+
* 「自动(意图识别)」 in the root menu's current value and each
|
|
3672
|
+
* option row; every other provider renders the catalog model name
|
|
3673
|
+
* verbatim. Returning null here would blank the label (single
|
|
3674
|
+
* seats have no per-dispatch fallback), which is why the fallback
|
|
3675
|
+
* body is rendered explicitly. Fail-silent by contract. */
|
|
3676
|
+
function IntentModelLabel({ provider, model, placement, t }) {
|
|
3677
|
+
const fallback =
|
|
3678
|
+
typeof model?.name === 'string' && model.name !== ''
|
|
3679
|
+
? model.name
|
|
3680
|
+
: '';
|
|
3681
|
+
try {
|
|
3682
|
+
if (provider?.id !== 'dsh-switchman-auto')
|
|
3683
|
+
return h('span', null, fallback);
|
|
3684
|
+
if (placement === 'trigger')
|
|
3685
|
+
return h('span', null, t('intentModelLabelTrigger'));
|
|
3686
|
+
if (placement === 'menu' || placement === 'option')
|
|
3687
|
+
return h('span', null, t('intentModelLabelFull'));
|
|
3688
|
+
return h('span', null, fallback);
|
|
3689
|
+
} catch {
|
|
3690
|
+
return h('span', null, fallback);
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
|
|
2477
3694
|
/** Home-sidebar panel icon (sidebar.panellist contract): the shell
|
|
2478
3695
|
* row renderer passes { size, active } and colors the glyph via the
|
|
2479
3696
|
* row's currentColor, so only size matters here. 16×16 stroke
|
|
@@ -2788,11 +4005,52 @@ window.__ModuleLoader__.load({
|
|
|
2788
4005
|
margin: '5px 0',
|
|
2789
4006
|
accentColor: 'var(--dsw-alias-brand-primary)',
|
|
2790
4007
|
},
|
|
4008
|
+
/** One intent-candidate editor row: index, provider, model,
|
|
4009
|
+
* effort, note, remove — all free-typed columns shrink to fit. */
|
|
4010
|
+
intentCandidateRow: {
|
|
4011
|
+
display: 'grid',
|
|
4012
|
+
gridTemplateColumns:
|
|
4013
|
+
'20px 20px minmax(0, 1fr) minmax(0, 1fr) minmax(0, 0.7fr) auto',
|
|
4014
|
+
gap: '6px',
|
|
4015
|
+
alignItems: 'center',
|
|
4016
|
+
padding: '2px 0',
|
|
4017
|
+
},
|
|
4018
|
+
intentCandidateCard: {
|
|
4019
|
+
display: 'flex',
|
|
4020
|
+
flexDirection: 'column',
|
|
4021
|
+
gap: '6px',
|
|
4022
|
+
padding: '8px 0',
|
|
4023
|
+
},
|
|
4024
|
+
intentCandidateCaption: {
|
|
4025
|
+
fontSize: '12px',
|
|
4026
|
+
opacity: 0.75,
|
|
4027
|
+
},
|
|
4028
|
+
intentCandidatePreset: {
|
|
4029
|
+
fontSize: '11px',
|
|
4030
|
+
lineHeight: '20px',
|
|
4031
|
+
padding: '0 8px',
|
|
4032
|
+
borderRadius: '999px',
|
|
4033
|
+
border: '1px solid var(--dsw-alias-border-primary, rgba(128, 128, 128, 0.4))',
|
|
4034
|
+
whiteSpace: 'nowrap',
|
|
4035
|
+
textAlign: 'center',
|
|
4036
|
+
},
|
|
4037
|
+
intentCandidateNote: {
|
|
4038
|
+
width: '100%',
|
|
4039
|
+
boxSizing: 'border-box',
|
|
4040
|
+
minHeight: '56px',
|
|
4041
|
+
resize: 'vertical',
|
|
4042
|
+
padding: '6px 8px',
|
|
4043
|
+
font: 'inherit',
|
|
4044
|
+
lineHeight: 1.5,
|
|
4045
|
+
},
|
|
2791
4046
|
};
|
|
2792
4047
|
|
|
2793
4048
|
/** One settings row: label + help on the left, control + status lines
|
|
2794
4049
|
* (current value, suggestion, validation) on the right. */
|
|
2795
|
-
function SettingsRow({ id, label, help, control, lines }) {
|
|
4050
|
+
function SettingsRow({ id, label, help, control, lines, hidden }) {
|
|
4051
|
+
// Rows irrelevant to the selected classifier channel are omitted
|
|
4052
|
+
// entirely (React drops null children).
|
|
4053
|
+
if (hidden) return null;
|
|
2796
4054
|
return h(
|
|
2797
4055
|
'div',
|
|
2798
4056
|
{ style: STYLE.row },
|
|
@@ -3391,16 +4649,21 @@ window.__ModuleLoader__.load({
|
|
|
3391
4649
|
(values.langConversation !== '' &&
|
|
3392
4650
|
!isCandidate(values.langConversation));
|
|
3393
4651
|
/** Per-field numeric validity: raw text (when present) must be a
|
|
3394
|
-
* whole number at or above the schema minimum.
|
|
4652
|
+
* whole number at or above the schema minimum. The intent rows
|
|
4653
|
+
* share the same raw-text overlay and save gate. */
|
|
3395
4654
|
const numberInvalid = Object.fromEntries(
|
|
3396
|
-
WM_NUMBER_FIELDS.map(
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
4655
|
+
[...WM_NUMBER_FIELDS, ...INTENT_NUMBER_FIELDS].map(
|
|
4656
|
+
({ field, min, max }) => {
|
|
4657
|
+
const raw = numText[field];
|
|
4658
|
+
return [
|
|
4659
|
+
field,
|
|
4660
|
+
raw !== undefined &&
|
|
4661
|
+
(!/^\d+$/.test(raw.trim()) ||
|
|
4662
|
+
Number(raw.trim()) < min ||
|
|
4663
|
+
Number(raw.trim()) > (max ?? Infinity)),
|
|
4664
|
+
];
|
|
4665
|
+
},
|
|
4666
|
+
),
|
|
3404
4667
|
);
|
|
3405
4668
|
const anyNumberInvalid = Object.values(numberInvalid).some(Boolean);
|
|
3406
4669
|
/** Ported hard rule (opencode config.ts): strict soft < hard < force. */
|
|
@@ -3638,6 +4901,35 @@ window.__ModuleLoader__.load({
|
|
|
3638
4901
|
wmSubagentCap: values.wmSubagentCap,
|
|
3639
4902
|
teamsMode: values.teamsMode,
|
|
3640
4903
|
syncWhitelist: values.syncWhitelist,
|
|
4904
|
+
// Intent recognition (design: intent-routing.md §2): route
|
|
4905
|
+
// objects materialize {provider, model} like the pool fields;
|
|
4906
|
+
// candidate effort/note are always sent (blank string = unset,
|
|
4907
|
+
// the Host coerces per its schema).
|
|
4908
|
+
intentEnabled: values.intentEnabled,
|
|
4909
|
+
intentClassifierRoute: {
|
|
4910
|
+
provider: values.intentClassifierRoute.provider,
|
|
4911
|
+
model: values.intentClassifierRoute.model,
|
|
4912
|
+
},
|
|
4913
|
+
intentClassifierEffort: values.intentClassifierEffort,
|
|
4914
|
+
intentClassifierKind: values.intentClassifierKind,
|
|
4915
|
+
intentClassifierEndpoint: values.intentClassifierEndpoint,
|
|
4916
|
+
intentClassifierSystemoneModel: values.intentClassifierSystemoneModel,
|
|
4917
|
+
// Full row shape (id/preset/enabled included) through the SAME
|
|
4918
|
+
// sanitizer readCurrent uses — desired must stay structurally
|
|
4919
|
+
// identical to current (toggle/preset persistence + clean
|
|
4920
|
+
// dirty-check). Identified rows without a model survive
|
|
4921
|
+
// (presets awaiting configuration, half-edited custom rows);
|
|
4922
|
+
// only anonymous empty junk is dropped.
|
|
4923
|
+
intentCandidates: values.intentCandidates
|
|
4924
|
+
.map(sanitizeIntentCandidate)
|
|
4925
|
+
.filter((entry) => entry !== null),
|
|
4926
|
+
...Object.fromEntries(
|
|
4927
|
+
INTENT_NUMBER_FIELDS.map(({ field }) => [field, values[field]]),
|
|
4928
|
+
),
|
|
4929
|
+
intentFallbackRoute: {
|
|
4930
|
+
provider: values.intentFallbackRoute.provider,
|
|
4931
|
+
model: values.intentFallbackRoute.model,
|
|
4932
|
+
},
|
|
3641
4933
|
};
|
|
3642
4934
|
if (sameValues(current, desired)) {
|
|
3643
4935
|
// Whitespace-only differences: nothing to write, close the draft.
|
|
@@ -3852,6 +5144,8 @@ window.__ModuleLoader__.load({
|
|
|
3852
5144
|
const wmId = `${headingId}-wm`;
|
|
3853
5145
|
// ---- teams section ids ----
|
|
3854
5146
|
const teamsId = `${headingId}-teams`;
|
|
5147
|
+
// ---- intent section id ----
|
|
5148
|
+
const intentId = `${headingId}-intent`;
|
|
3855
5149
|
|
|
3856
5150
|
/** One watermark number input: raw-text overlay over the draft
|
|
3857
5151
|
* value (0 renders as the placeholder for the follow-force row),
|
|
@@ -3898,6 +5192,214 @@ window.__ModuleLoader__.load({
|
|
|
3898
5192
|
return lines;
|
|
3899
5193
|
};
|
|
3900
5194
|
|
|
5195
|
+
// ---- intent section controls ----
|
|
5196
|
+
/** One intent number input: the watermark rows' raw-text overlay
|
|
5197
|
+
* pattern under the intent section id (invalid text shows the
|
|
5198
|
+
* shared minimum hint and blocks saving via `invalid`). */
|
|
5199
|
+
const intentNumberControl = (spec) => {
|
|
5200
|
+
const { field } = spec;
|
|
5201
|
+
const shown = numText[field] ?? String(values[field]);
|
|
5202
|
+
return h('input', {
|
|
5203
|
+
type: 'text',
|
|
5204
|
+
inputMode: 'numeric',
|
|
5205
|
+
id: `${intentId}-${field}`,
|
|
5206
|
+
value: shown,
|
|
5207
|
+
onChange: (event) => {
|
|
5208
|
+
editNumber(field, event.target.value);
|
|
5209
|
+
},
|
|
5210
|
+
onBlur: blurNumber(field),
|
|
5211
|
+
disabled: !editable,
|
|
5212
|
+
'aria-label': t(INTENT_ROW_KEYS[field].label),
|
|
5213
|
+
style: numberInvalid[field]
|
|
5214
|
+
? {
|
|
5215
|
+
...STYLE.control,
|
|
5216
|
+
borderColor: 'var(--dsw-alias-state-error-primary)',
|
|
5217
|
+
color: 'var(--dsw-alias-state-error-primary)',
|
|
5218
|
+
}
|
|
5219
|
+
: STYLE.control,
|
|
5220
|
+
});
|
|
5221
|
+
};
|
|
5222
|
+
|
|
5223
|
+
/** Status lines under one intent number row: minimum hint. */
|
|
5224
|
+
const intentNumberLines = (spec) =>
|
|
5225
|
+
numberInvalid[spec.field]
|
|
5226
|
+
? [{ text: t('wmNumberInvalid', { min: spec.min }), invalid: true }]
|
|
5227
|
+
: [];
|
|
5228
|
+
|
|
5229
|
+
/** Intent option sets fed by the DSH model catalog (the same
|
|
5230
|
+
* source the shipped model-selection page uses). A stored
|
|
5231
|
+
* provider/model that left the catalog stays selectable so a
|
|
5232
|
+
* controlled select never misaligns. */
|
|
5233
|
+
const intentProviders = () => [
|
|
5234
|
+
...new Set(
|
|
5235
|
+
[...catalogIndex.values()].map((meta) => meta.provider),
|
|
5236
|
+
),
|
|
5237
|
+
].sort();
|
|
5238
|
+
const intentProviderName = (provider) => {
|
|
5239
|
+
for (const meta of catalogIndex.values())
|
|
5240
|
+
if (meta.provider === provider)
|
|
5241
|
+
return meta.providerName ?? provider;
|
|
5242
|
+
return provider;
|
|
5243
|
+
};
|
|
5244
|
+
const intentModelsFor = (provider) =>
|
|
5245
|
+
[...catalogIndex.values()].filter(
|
|
5246
|
+
(meta) => meta.provider === provider,
|
|
5247
|
+
);
|
|
5248
|
+
const intentProviderOptions = (current) => {
|
|
5249
|
+
const providers = intentProviders();
|
|
5250
|
+
if (current !== '' && !providers.includes(current))
|
|
5251
|
+
providers.push(current);
|
|
5252
|
+
return [
|
|
5253
|
+
h('option', { value: '' }, t('intentRouteUnset')),
|
|
5254
|
+
...providers.map((provider) =>
|
|
5255
|
+
h(
|
|
5256
|
+
'option',
|
|
5257
|
+
{ key: provider, value: provider },
|
|
5258
|
+
intentProviderName(provider),
|
|
5259
|
+
),
|
|
5260
|
+
),
|
|
5261
|
+
];
|
|
5262
|
+
};
|
|
5263
|
+
const intentModelOptions = (provider, current) => {
|
|
5264
|
+
const metas = intentModelsFor(provider);
|
|
5265
|
+
return [
|
|
5266
|
+
h('option', { value: '' }, t('intentRouteUnset')),
|
|
5267
|
+
...metas.map((meta) =>
|
|
5268
|
+
h(
|
|
5269
|
+
'option',
|
|
5270
|
+
{ key: meta.model, value: meta.model },
|
|
5271
|
+
meta.modelName ?? meta.model,
|
|
5272
|
+
),
|
|
5273
|
+
),
|
|
5274
|
+
provider !== '' &&
|
|
5275
|
+
current !== '' &&
|
|
5276
|
+
!metas.some((meta) => meta.model === current)
|
|
5277
|
+
? h('option', { value: current }, current)
|
|
5278
|
+
: null,
|
|
5279
|
+
];
|
|
5280
|
+
};
|
|
5281
|
+
/** One {provider, model} intent route editor as two linked
|
|
5282
|
+
* selects; switching providers drops a model the new provider
|
|
5283
|
+
* does not offer. An untyped route stays the frozen
|
|
5284
|
+
* empty-route default. */
|
|
5285
|
+
const intentRouteControl = (field, label) => {
|
|
5286
|
+
const route = values[field];
|
|
5287
|
+
return h(
|
|
5288
|
+
'div',
|
|
5289
|
+
{ style: { display: 'flex', gap: '6px', minWidth: 0 } },
|
|
5290
|
+
h(
|
|
5291
|
+
'select',
|
|
5292
|
+
{
|
|
5293
|
+
...selectProps(
|
|
5294
|
+
`${intentId}-${field}-provider`,
|
|
5295
|
+
route.provider,
|
|
5296
|
+
(next) => {
|
|
5297
|
+
const kept = intentModelsFor(next).some(
|
|
5298
|
+
(meta) => meta.model === route.model,
|
|
5299
|
+
);
|
|
5300
|
+
edit(field, {
|
|
5301
|
+
provider: next,
|
|
5302
|
+
model: kept ? route.model : '',
|
|
5303
|
+
});
|
|
5304
|
+
},
|
|
5305
|
+
),
|
|
5306
|
+
'aria-label': `${label} · provider`,
|
|
5307
|
+
},
|
|
5308
|
+
...intentProviderOptions(route.provider),
|
|
5309
|
+
),
|
|
5310
|
+
h(
|
|
5311
|
+
'select',
|
|
5312
|
+
{
|
|
5313
|
+
...selectProps(
|
|
5314
|
+
`${intentId}-${field}-model`,
|
|
5315
|
+
route.model,
|
|
5316
|
+
(next) => {
|
|
5317
|
+
edit(field, { ...route, model: next });
|
|
5318
|
+
},
|
|
5319
|
+
),
|
|
5320
|
+
disabled: !editable || route.provider === '',
|
|
5321
|
+
'aria-label': `${label} · model`,
|
|
5322
|
+
},
|
|
5323
|
+
...intentModelOptions(route.provider, route.model),
|
|
5324
|
+
),
|
|
5325
|
+
);
|
|
5326
|
+
};
|
|
5327
|
+
/** Reasoning-effort select for one intent route: the model's
|
|
5328
|
+
* wire efforts when the catalog reports them, the EFFORTS
|
|
5329
|
+
* fallback otherwise; '' = adapter default. A stored value
|
|
5330
|
+
* missing from the list stays selectable. */
|
|
5331
|
+
const intentEffortSelect = (
|
|
5332
|
+
id,
|
|
5333
|
+
route,
|
|
5334
|
+
value,
|
|
5335
|
+
onCommit,
|
|
5336
|
+
ariaLabel,
|
|
5337
|
+
) => {
|
|
5338
|
+
const meta = catalogIndex.get(routeKey(route));
|
|
5339
|
+
const wire =
|
|
5340
|
+
meta &&
|
|
5341
|
+
Array.isArray(meta.reasoning?.efforts) &&
|
|
5342
|
+
meta.reasoning.efforts.length > 0
|
|
5343
|
+
? meta.reasoning.efforts
|
|
5344
|
+
: EFFORTS.map((effort) => ({ id: effort }));
|
|
5345
|
+
const seen = new Set(wire.map((entry) => entry.id));
|
|
5346
|
+
const pending =
|
|
5347
|
+
typeof value === 'string' && value !== '' && !seen.has(value)
|
|
5348
|
+
? [{ id: value }]
|
|
5349
|
+
: [];
|
|
5350
|
+
return h(
|
|
5351
|
+
'select',
|
|
5352
|
+
{
|
|
5353
|
+
id,
|
|
5354
|
+
value,
|
|
5355
|
+
onChange: (event) => onCommit(event.target.value),
|
|
5356
|
+
disabled: !editable,
|
|
5357
|
+
style: STYLE.effortSelect,
|
|
5358
|
+
'aria-label': ariaLabel,
|
|
5359
|
+
},
|
|
5360
|
+
h('option', { value: '' }, t('intentEffortFollow')),
|
|
5361
|
+
...[...wire, ...pending].map((entry) =>
|
|
5362
|
+
h('option', { value: entry.id }, entry.name ?? entry.id),
|
|
5363
|
+
),
|
|
5364
|
+
);
|
|
5365
|
+
};
|
|
5366
|
+
|
|
5367
|
+
/** Candidates list editing: the whole array is one draft field;
|
|
5368
|
+
* rows are addressed by position (the classifier reads them as
|
|
5369
|
+
* #1..#n, so order is meaningful). */
|
|
5370
|
+
const editIntentCandidate = (index, patch) => {
|
|
5371
|
+
edit(
|
|
5372
|
+
'intentCandidates',
|
|
5373
|
+
values.intentCandidates.map((entry, position) =>
|
|
5374
|
+
position === index ? { ...entry, ...patch } : entry,
|
|
5375
|
+
),
|
|
5376
|
+
);
|
|
5377
|
+
};
|
|
5378
|
+
const addIntentCandidate = () => {
|
|
5379
|
+
// Client-generated id keeps the half-edited row alive across
|
|
5380
|
+
// save/snapshot round-trips (host keeps identified empty rows).
|
|
5381
|
+
edit('intentCandidates', [
|
|
5382
|
+
...values.intentCandidates,
|
|
5383
|
+
{
|
|
5384
|
+
id: `u${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`,
|
|
5385
|
+
preset: false,
|
|
5386
|
+
enabled: true,
|
|
5387
|
+
provider: '',
|
|
5388
|
+
model: '',
|
|
5389
|
+
effort: '',
|
|
5390
|
+
note: '',
|
|
5391
|
+
},
|
|
5392
|
+
]);
|
|
5393
|
+
};
|
|
5394
|
+
const removeIntentCandidate = (index) => {
|
|
5395
|
+
edit(
|
|
5396
|
+
'intentCandidates',
|
|
5397
|
+
values.intentCandidates.filter(
|
|
5398
|
+
(_entry, position) => position !== index,
|
|
5399
|
+
),
|
|
5400
|
+
);
|
|
5401
|
+
};
|
|
5402
|
+
|
|
3901
5403
|
/** Catalog status line: loading / error + retry / partial / empty. */
|
|
3902
5404
|
const catalogNotice =
|
|
3903
5405
|
catalog.status === 'loading'
|
|
@@ -4307,6 +5809,358 @@ window.__ModuleLoader__.load({
|
|
|
4307
5809
|
],
|
|
4308
5810
|
}),
|
|
4309
5811
|
),
|
|
5812
|
+
h(
|
|
5813
|
+
'section',
|
|
5814
|
+
{
|
|
5815
|
+
'aria-labelledby': intentId,
|
|
5816
|
+
style: { ...STYLE.section, ...STYLE.sectionDivider },
|
|
5817
|
+
},
|
|
5818
|
+
h(
|
|
5819
|
+
'h3',
|
|
5820
|
+
{ id: intentId, style: STYLE.heading },
|
|
5821
|
+
t('intentSectionTitle'),
|
|
5822
|
+
),
|
|
5823
|
+
h('p', { style: STYLE.description }, t('intentSectionDescription')),
|
|
5824
|
+
h(
|
|
5825
|
+
'div',
|
|
5826
|
+
{ style: STYLE.rows },
|
|
5827
|
+
h(SettingsRow, {
|
|
5828
|
+
key: 'intentEnabled',
|
|
5829
|
+
id: `${intentId}-enabled`,
|
|
5830
|
+
label: t('intentEnabledLabel'),
|
|
5831
|
+
help: t('intentEnabledHelp'),
|
|
5832
|
+
control: h('input', {
|
|
5833
|
+
type: 'checkbox',
|
|
5834
|
+
id: `${intentId}-enabled`,
|
|
5835
|
+
checked: values.intentEnabled,
|
|
5836
|
+
onChange: () => {
|
|
5837
|
+
edit('intentEnabled', !values.intentEnabled);
|
|
5838
|
+
},
|
|
5839
|
+
disabled: !editable,
|
|
5840
|
+
style: STYLE.checkbox,
|
|
5841
|
+
}),
|
|
5842
|
+
lines: [],
|
|
5843
|
+
}),
|
|
5844
|
+
h(SettingsRow, {
|
|
5845
|
+
key: 'intentClassifierRoute',
|
|
5846
|
+
hidden: values.intentClassifierKind === 'systemone',
|
|
5847
|
+
id: `${intentId}-classifier-route`,
|
|
5848
|
+
label: t('intentClassifierRouteLabel'),
|
|
5849
|
+
help: t('intentClassifierRouteHelp'),
|
|
5850
|
+
control: intentRouteControl(
|
|
5851
|
+
'intentClassifierRoute',
|
|
5852
|
+
t('intentClassifierRouteLabel'),
|
|
5853
|
+
),
|
|
5854
|
+
lines:
|
|
5855
|
+
values.intentClassifierRoute.provider === '' ||
|
|
5856
|
+
values.intentClassifierRoute.model === ''
|
|
5857
|
+
? [
|
|
5858
|
+
{
|
|
5859
|
+
text: t('intentRouteUnset'),
|
|
5860
|
+
// The classifier is load-bearing only
|
|
5861
|
+
// while routing is on.
|
|
5862
|
+
invalid: values.intentEnabled,
|
|
5863
|
+
},
|
|
5864
|
+
]
|
|
5865
|
+
: [
|
|
5866
|
+
{
|
|
5867
|
+
text: t('currentValue', {
|
|
5868
|
+
value: `${values.intentClassifierRoute.provider}/${values.intentClassifierRoute.model}`,
|
|
5869
|
+
}),
|
|
5870
|
+
invalid: false,
|
|
5871
|
+
},
|
|
5872
|
+
],
|
|
5873
|
+
}),
|
|
5874
|
+
h(SettingsRow, {
|
|
5875
|
+
key: 'intentClassifierEffort',
|
|
5876
|
+
hidden: values.intentClassifierKind === 'systemone',
|
|
5877
|
+
id: `${intentId}-classifier-effort`,
|
|
5878
|
+
label: t('intentClassifierEffortLabel'),
|
|
5879
|
+
help: t('intentClassifierEffortHelp'),
|
|
5880
|
+
control: intentEffortSelect(
|
|
5881
|
+
`${intentId}-classifier-effort`,
|
|
5882
|
+
values.intentClassifierRoute,
|
|
5883
|
+
values.intentClassifierEffort,
|
|
5884
|
+
(next) => edit('intentClassifierEffort', next),
|
|
5885
|
+
t('intentClassifierEffortLabel'),
|
|
5886
|
+
),
|
|
5887
|
+
lines: [],
|
|
5888
|
+
}),
|
|
5889
|
+
h(SettingsRow, {
|
|
5890
|
+
key: 'intentClassifierKind',
|
|
5891
|
+
id: `${intentId}-kind`,
|
|
5892
|
+
label: t('intentClassifierKindLabel'),
|
|
5893
|
+
help: t('intentClassifierKindHelp'),
|
|
5894
|
+
control: h(
|
|
5895
|
+
'select',
|
|
5896
|
+
selectProps(
|
|
5897
|
+
`${intentId}-kind`,
|
|
5898
|
+
values.intentClassifierKind,
|
|
5899
|
+
(kind) => {
|
|
5900
|
+
if (INTENT_KINDS.includes(kind))
|
|
5901
|
+
edit('intentClassifierKind', kind);
|
|
5902
|
+
},
|
|
5903
|
+
),
|
|
5904
|
+
...INTENT_KINDS.map((kind) =>
|
|
5905
|
+
h(
|
|
5906
|
+
'option',
|
|
5907
|
+
{ key: kind, value: kind },
|
|
5908
|
+
t(
|
|
5909
|
+
`intentClassifierKind${kind[0].toUpperCase()}${kind.slice(1)}`,
|
|
5910
|
+
),
|
|
5911
|
+
),
|
|
5912
|
+
),
|
|
5913
|
+
),
|
|
5914
|
+
lines: [],
|
|
5915
|
+
}),
|
|
5916
|
+
h(SettingsRow, {
|
|
5917
|
+
key: 'intentClassifierEndpoint',
|
|
5918
|
+
hidden: values.intentClassifierKind !== 'systemone',
|
|
5919
|
+
id: `${intentId}-classifier-endpoint`,
|
|
5920
|
+
label: t('intentClassifierEndpointLabel'),
|
|
5921
|
+
help: t('intentClassifierEndpointHelp'),
|
|
5922
|
+
control: h('input', {
|
|
5923
|
+
type: 'text',
|
|
5924
|
+
value: values.intentClassifierEndpoint,
|
|
5925
|
+
placeholder: 'http://127.0.0.1:11434/v1/systemone',
|
|
5926
|
+
onChange: (event) =>
|
|
5927
|
+
edit('intentClassifierEndpoint', event.target.value),
|
|
5928
|
+
disabled: !editable,
|
|
5929
|
+
'aria-label': t('intentClassifierEndpointLabel'),
|
|
5930
|
+
style: STYLE.control,
|
|
5931
|
+
}),
|
|
5932
|
+
lines:
|
|
5933
|
+
values.intentClassifierKind === 'systemone' &&
|
|
5934
|
+
values.intentClassifierEndpoint.trim() === ''
|
|
5935
|
+
? [
|
|
5936
|
+
{
|
|
5937
|
+
text: t('intentEndpointUnset'),
|
|
5938
|
+
// Load-bearing only for that kind.
|
|
5939
|
+
invalid: values.intentEnabled,
|
|
5940
|
+
},
|
|
5941
|
+
]
|
|
5942
|
+
: [],
|
|
5943
|
+
}),
|
|
5944
|
+
h(SettingsRow, {
|
|
5945
|
+
key: 'intentClassifierSystemoneModel',
|
|
5946
|
+
hidden: values.intentClassifierKind !== 'systemone',
|
|
5947
|
+
id: `${intentId}-systemone-model`,
|
|
5948
|
+
label: t('intentSystemoneModelLabel'),
|
|
5949
|
+
help: t('intentSystemoneModelHelp'),
|
|
5950
|
+
control: h('input', {
|
|
5951
|
+
type: 'text',
|
|
5952
|
+
value: values.intentClassifierSystemoneModel,
|
|
5953
|
+
placeholder: 'tev1:4b-mxfp8',
|
|
5954
|
+
onChange: (event) =>
|
|
5955
|
+
edit('intentClassifierSystemoneModel', event.target.value),
|
|
5956
|
+
disabled: !editable,
|
|
5957
|
+
'aria-label': t('intentSystemoneModelLabel'),
|
|
5958
|
+
style: STYLE.control,
|
|
5959
|
+
}),
|
|
5960
|
+
lines:
|
|
5961
|
+
values.intentClassifierKind === 'systemone' &&
|
|
5962
|
+
values.intentClassifierSystemoneModel.trim() === ''
|
|
5963
|
+
? [
|
|
5964
|
+
{
|
|
5965
|
+
text: t('intentEndpointUnset'),
|
|
5966
|
+
// Load-bearing only for that kind.
|
|
5967
|
+
invalid: values.intentEnabled,
|
|
5968
|
+
},
|
|
5969
|
+
]
|
|
5970
|
+
: [],
|
|
5971
|
+
}),
|
|
5972
|
+
),
|
|
5973
|
+
h('h4', { style: STYLE.subheading }, t('intentCandidatesLabel')),
|
|
5974
|
+
h('p', { style: STYLE.description }, t('intentCandidatesHelp')),
|
|
5975
|
+
values.intentCandidates.length === 0
|
|
5976
|
+
? h('p', { style: STYLE.progress }, t('intentCandidatesEmpty'))
|
|
5977
|
+
: h(
|
|
5978
|
+
'div',
|
|
5979
|
+
{ style: STYLE.rankList },
|
|
5980
|
+
...values.intentCandidates.map((entry, index) =>
|
|
5981
|
+
h(
|
|
5982
|
+
'div',
|
|
5983
|
+
{ key: entry.id ?? index, style: STYLE.intentCandidateCard },
|
|
5984
|
+
h(
|
|
5985
|
+
'div',
|
|
5986
|
+
{ style: STYLE.intentCandidateRow },
|
|
5987
|
+
h('input', {
|
|
5988
|
+
type: 'checkbox',
|
|
5989
|
+
checked: entry.enabled !== false,
|
|
5990
|
+
onChange: (event) => {
|
|
5991
|
+
editIntentCandidate(index, {
|
|
5992
|
+
enabled: event.target.checked,
|
|
5993
|
+
});
|
|
5994
|
+
},
|
|
5995
|
+
disabled: !editable,
|
|
5996
|
+
'aria-label': `${t('intentCandidateEnable')} · ${t('intentCandidatesLabel')} ${index + 1}`,
|
|
5997
|
+
}),
|
|
5998
|
+
h(
|
|
5999
|
+
'span',
|
|
6000
|
+
{ style: STYLE.rankIndex },
|
|
6001
|
+
String(index + 1),
|
|
6002
|
+
),
|
|
6003
|
+
h(
|
|
6004
|
+
'select',
|
|
6005
|
+
{
|
|
6006
|
+
...selectProps(
|
|
6007
|
+
`${intentId}-candidate-${index}-provider`,
|
|
6008
|
+
entry.provider,
|
|
6009
|
+
(next) => {
|
|
6010
|
+
const kept = intentModelsFor(
|
|
6011
|
+
next,
|
|
6012
|
+
).some(
|
|
6013
|
+
(meta) =>
|
|
6014
|
+
meta.model ===
|
|
6015
|
+
entry.model,
|
|
6016
|
+
);
|
|
6017
|
+
editIntentCandidate(index, {
|
|
6018
|
+
provider: next,
|
|
6019
|
+
model: kept
|
|
6020
|
+
? entry.model
|
|
6021
|
+
: '',
|
|
6022
|
+
});
|
|
6023
|
+
},
|
|
6024
|
+
),
|
|
6025
|
+
'aria-label': `${t('intentCandidatesLabel')} ${index + 1} · provider`,
|
|
6026
|
+
},
|
|
6027
|
+
...intentProviderOptions(entry.provider),
|
|
6028
|
+
),
|
|
6029
|
+
h(
|
|
6030
|
+
'select',
|
|
6031
|
+
{
|
|
6032
|
+
...selectProps(
|
|
6033
|
+
`${intentId}-candidate-${index}-model`,
|
|
6034
|
+
entry.model,
|
|
6035
|
+
(next) => {
|
|
6036
|
+
editIntentCandidate(index, {
|
|
6037
|
+
model: next,
|
|
6038
|
+
});
|
|
6039
|
+
},
|
|
6040
|
+
),
|
|
6041
|
+
disabled:
|
|
6042
|
+
!editable || entry.provider === '',
|
|
6043
|
+
'aria-label': `${t('intentCandidatesLabel')} ${index + 1} · model`,
|
|
6044
|
+
},
|
|
6045
|
+
...intentModelOptions(
|
|
6046
|
+
entry.provider,
|
|
6047
|
+
entry.model,
|
|
6048
|
+
),
|
|
6049
|
+
),
|
|
6050
|
+
intentEffortSelect(
|
|
6051
|
+
`${intentId}-candidate-${index}-effort`,
|
|
6052
|
+
entry,
|
|
6053
|
+
entry.effort ?? '',
|
|
6054
|
+
(next) => {
|
|
6055
|
+
editIntentCandidate(index, {
|
|
6056
|
+
effort: next,
|
|
6057
|
+
});
|
|
6058
|
+
},
|
|
6059
|
+
`${t('intentCandidatesLabel')} ${index + 1} · ${t('intentClassifierEffortLabel')}`,
|
|
6060
|
+
),
|
|
6061
|
+
entry.preset === true
|
|
6062
|
+
? h(
|
|
6063
|
+
'span',
|
|
6064
|
+
{ style: STYLE.intentCandidatePreset },
|
|
6065
|
+
t('intentCandidatePresetBadge'),
|
|
6066
|
+
)
|
|
6067
|
+
: h(
|
|
6068
|
+
'button',
|
|
6069
|
+
{
|
|
6070
|
+
type: 'button',
|
|
6071
|
+
onClick: () => {
|
|
6072
|
+
removeIntentCandidate(index);
|
|
6073
|
+
},
|
|
6074
|
+
disabled: !editable,
|
|
6075
|
+
title: t('intentCandidateRemove'),
|
|
6076
|
+
'aria-label': t('intentCandidateRemove'),
|
|
6077
|
+
style: STYLE.iconButton,
|
|
6078
|
+
},
|
|
6079
|
+
'✕',
|
|
6080
|
+
),
|
|
6081
|
+
),
|
|
6082
|
+
entry.preset === true
|
|
6083
|
+
? h(
|
|
6084
|
+
'span',
|
|
6085
|
+
{ style: STYLE.intentCandidateCaption },
|
|
6086
|
+
entry.label ?? entry.id,
|
|
6087
|
+
)
|
|
6088
|
+
: null,
|
|
6089
|
+
h('textarea', {
|
|
6090
|
+
value: entry.note ?? '',
|
|
6091
|
+
placeholder: t('intentCandidateNotePlaceholder'),
|
|
6092
|
+
readOnly: entry.preset === true,
|
|
6093
|
+
onChange: (event) =>
|
|
6094
|
+
editIntentCandidate(index, {
|
|
6095
|
+
note: event.target.value,
|
|
6096
|
+
}),
|
|
6097
|
+
disabled: !editable,
|
|
6098
|
+
'aria-label': `${t('intentCandidatesLabel')} ${index + 1} · note`,
|
|
6099
|
+
style: {
|
|
6100
|
+
...STYLE.intentCandidateNote,
|
|
6101
|
+
...(entry.preset === true
|
|
6102
|
+
? { opacity: 0.75, cursor: 'default' }
|
|
6103
|
+
: {}),
|
|
6104
|
+
},
|
|
6105
|
+
}),
|
|
6106
|
+
entry.enabled !== false && entry.model.trim() === ''
|
|
6107
|
+
? h(
|
|
6108
|
+
'p',
|
|
6109
|
+
{ style: STYLE.progress },
|
|
6110
|
+
t('intentCandidateUnset'),
|
|
6111
|
+
)
|
|
6112
|
+
: null,
|
|
6113
|
+
),
|
|
6114
|
+
),
|
|
6115
|
+
),
|
|
6116
|
+
h(
|
|
6117
|
+
'button',
|
|
6118
|
+
{
|
|
6119
|
+
type: 'button',
|
|
6120
|
+
onClick: addIntentCandidate,
|
|
6121
|
+
disabled: !editable,
|
|
6122
|
+
style: { ...STYLE.iconButton, marginTop: '6px' },
|
|
6123
|
+
},
|
|
6124
|
+
'+ ',
|
|
6125
|
+
t('intentCandidatesAdd'),
|
|
6126
|
+
),
|
|
6127
|
+
h(
|
|
6128
|
+
'div',
|
|
6129
|
+
{ style: STYLE.rows },
|
|
6130
|
+
h(SettingsRow, {
|
|
6131
|
+
key: 'intentFallbackRoute',
|
|
6132
|
+
id: `${intentId}-fallback`,
|
|
6133
|
+
label: t('intentFallbackRouteLabel'),
|
|
6134
|
+
help: t('intentFallbackRouteHelp'),
|
|
6135
|
+
control: intentRouteControl(
|
|
6136
|
+
'intentFallbackRoute',
|
|
6137
|
+
t('intentFallbackRouteLabel'),
|
|
6138
|
+
),
|
|
6139
|
+
lines:
|
|
6140
|
+
values.intentFallbackRoute.provider === '' ||
|
|
6141
|
+
values.intentFallbackRoute.model === ''
|
|
6142
|
+
? [{ text: t('intentRouteUnset'), invalid: false }]
|
|
6143
|
+
: [
|
|
6144
|
+
{
|
|
6145
|
+
text: t('currentValue', {
|
|
6146
|
+
value: `${values.intentFallbackRoute.provider}/${values.intentFallbackRoute.model}`,
|
|
6147
|
+
}),
|
|
6148
|
+
invalid: false,
|
|
6149
|
+
},
|
|
6150
|
+
],
|
|
6151
|
+
}),
|
|
6152
|
+
...INTENT_NUMBER_FIELDS.map((spec) =>
|
|
6153
|
+
h(SettingsRow, {
|
|
6154
|
+
key: spec.field,
|
|
6155
|
+
id: `${intentId}-${spec.field}`,
|
|
6156
|
+
label: t(INTENT_ROW_KEYS[spec.field].label),
|
|
6157
|
+
help: t(INTENT_ROW_KEYS[spec.field].help),
|
|
6158
|
+
control: intentNumberControl(spec),
|
|
6159
|
+
lines: intentNumberLines(spec),
|
|
6160
|
+
}),
|
|
6161
|
+
),
|
|
6162
|
+
),
|
|
6163
|
+
),
|
|
4310
6164
|
h(
|
|
4311
6165
|
'section',
|
|
4312
6166
|
{
|
|
@@ -4712,6 +6566,33 @@ window.__ModuleLoader__.load({
|
|
|
4712
6566
|
(kit) => h(VisionDockEntry, { ...kit, t }),
|
|
4713
6567
|
),
|
|
4714
6568
|
);
|
|
6569
|
+
// Composer-adjacent intent chip (same dock list, order 89 —
|
|
6570
|
+
// one slot above the vision hint's 90): shows where the
|
|
6571
|
+
// session's auto-selected model currently routes. Same
|
|
6572
|
+
// standard session-kit forwarding as the vision entry.
|
|
6573
|
+
ctx.slots.inject('conversation.input.dock', () =>
|
|
6574
|
+
ctx.slots.register(
|
|
6575
|
+
{
|
|
6576
|
+
name: 'conversation.input.dock',
|
|
6577
|
+
id: 'dsh-switchman-intent',
|
|
6578
|
+
order: 89,
|
|
6579
|
+
},
|
|
6580
|
+
(kit) => h(IntentDockEntry, { ...kit, t }),
|
|
6581
|
+
),
|
|
6582
|
+
);
|
|
6583
|
+
// Composer model label (conversation.input.model.label): the
|
|
6584
|
+
// ui-model-selection shell dispatches EVERY model's label
|
|
6585
|
+
// through this single-seat component with owner props
|
|
6586
|
+
// {provider:{id,name}, model, placement, selected} plus the
|
|
6587
|
+
// standard session kit. Single seats render the owner
|
|
6588
|
+
// fallback only while UNREGISTERED, so the component itself
|
|
6589
|
+
// renders the catalog model name for real providers (see
|
|
6590
|
+
// IntentModelLabel).
|
|
6591
|
+
ctx.slots.inject('conversation.input.model.label', () =>
|
|
6592
|
+
ctx.slots.register({ name: 'conversation.input.model.label' }, (kit) =>
|
|
6593
|
+
h(IntentModelLabel, { ...kit, t }),
|
|
6594
|
+
),
|
|
6595
|
+
);
|
|
4715
6596
|
// Settings page: one settings.section entry. The restricted
|
|
4716
6597
|
// dynamic-module runtime has no configForms service, so there
|
|
4717
6598
|
// is no whileServed gating — the page loads its snapshot from
|