@weniatall/dsh-task-models 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -17
- package/cordis.patch.yml +1 -0
- package/lib/client.js +466 -102
- package/lib/core.js +133 -25
- package/lib/index.js +326 -85
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
fallback: "taskRole",
|
|
27
27
|
useJev: true,
|
|
28
28
|
respectExplicitChildModel: true,
|
|
29
|
+
decisionTimeoutMs: 5000,
|
|
29
30
|
classifier: { provider: "", model: "", reasoningEffort: "" },
|
|
30
31
|
};
|
|
31
32
|
const DEFAULT_JEV = {
|
|
@@ -38,15 +39,18 @@
|
|
|
38
39
|
};
|
|
39
40
|
const INTRO_TEXT = "为不同类型的子任务选择不同模型;未命中时保持当前路由。";
|
|
40
41
|
const css = `
|
|
41
|
-
.tm-page{display:flex;flex-direction:column;gap:14px;width:100%;max-width:820px;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family)
|
|
42
|
+
.tm-page{display:flex;flex-direction:column;gap:14px;width:100%;max-width:820px;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family)}
|
|
42
43
|
.tm-page h2,.tm-page h3,.tm-page p{margin:0}.tm-page h2{font:var(--dsw-font-l-20)}.tm-intro{color:var(--dsw-alias-label-tertiary);font:var(--dsw-font-s-14);line-height:20px}
|
|
43
44
|
.tm-list{display:flex;flex-direction:column;gap:10px}.tm-card{display:flex;flex-direction:column;gap:10px;padding:14px;border:1px solid var(--dsw-alias-border-subtle,var(--dsw-alias-border-l1));border-radius:var(--dsw-radius-l,12px);background:var(--dsw-alias-background-base,var(--dsw-alias-bg-module-platform))}
|
|
44
|
-
.tm-card-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.tm-title{font:var(--dsw-font-
|
|
45
|
+
.tm-card-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.tm-title{font:var(--dsw-font-base-strong-16,600 16px/24px var(--dsw-font-family));font-weight:600}.tm-detail{margin-top:3px!important;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
|
45
46
|
.tm-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.tm-field{display:flex;flex-direction:column;gap:5px}.tm-label{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500}
|
|
46
47
|
.tm-select,.tm-input{box-sizing:border-box;width:100%;height:36px;padding:0 10px;border:1px solid var(--dsw-alias-border-default,var(--dsw-alias-border-l2));border-radius:8px;outline:none;background:var(--dsw-alias-background-base,var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-label-primary);font:var(--dsw-font-s-14)}
|
|
47
48
|
.tm-select:focus-visible,.tm-input:focus-visible{border-color:var(--dsw-alias-brand-primary,#4d6bfe);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-brand-primary,#4d6bfe) 18%,transparent)}.tm-select:disabled,.tm-input:disabled{opacity:.55}
|
|
48
|
-
.tm-actions{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:8px}.tm-button{display:inline-flex;align-items:center;justify-content:center;height:36px;padding:0 14px;border:1px solid var(--dsw-alias-border-default,var(--dsw-alias-border-l2));border-radius:18px;background:transparent;color:var(--dsw-alias-label-primary);font:var(--dsw-font-s-14);cursor:pointer}.tm-button:disabled{cursor:default;opacity:.45}.tm-primary{border-color:transparent;background:var(--dsw-alias-brand-primary,#4d6bfe);color
|
|
49
|
-
.tm-feedback{padding:9px 11px;border-radius:8px;font-size:12px;line-height:18px}.tm-feedback.tm-error{background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#d84848) 10%,transparent);color:var(--dsw-alias-state-error-primary,#d84848)}.tm-status{min-height:18px;color:var(--dsw-alias-label-tertiary);font-size:12px}
|
|
49
|
+
.tm-actions{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:8px}.tm-button{display:inline-flex;align-items:center;justify-content:center;height:36px;padding:0 14px;border:1px solid var(--dsw-alias-border-default,var(--dsw-alias-border-l2));border-radius:18px;background:transparent;color:var(--dsw-alias-label-primary);font:var(--dsw-font-s-14);cursor:pointer}.tm-button:disabled{cursor:default;opacity:.45}.tm-primary{border-color:transparent;background:var(--dsw-alias-brand-primary,#4d6bfe);color:var(--dsw-alias-label-primary-foreground,#fff)}.tm-danger{color:var(--dsw-alias-state-error-primary)}
|
|
50
|
+
.tm-feedback{padding:9px 11px;border-radius:8px;font-size:12px;line-height:18px}.tm-feedback.tm-error{background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#d84848) 10%,transparent);color:var(--dsw-alias-state-error-primary,#d84848)}.tm-feedback.tm-warn{background:color-mix(in srgb,var(--dsw-alias-state-warning-primary,#d98e00) 12%,transparent);color:var(--dsw-alias-state-warning-primary,#d98e00);display:flex;align-items:center;justify-content:space-between;gap:8px}.tm-status{min-height:18px;color:var(--dsw-alias-label-tertiary);font-size:12px}
|
|
51
|
+
.tm-preview-result{padding:10px 12px;border-radius:8px;background:color-mix(in srgb,var(--dsw-alias-label-secondary) 8%,transparent);font-size:12px;line-height:20px;display:flex;flex-direction:column;gap:4px}
|
|
52
|
+
.tm-textarea{box-sizing:border-box;width:100%;min-height:72px;padding:8px 10px;border:1px solid var(--dsw-alias-border-default,var(--dsw-alias-border-l2));border-radius:8px;outline:none;background:var(--dsw-alias-background-base,var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-label-primary);font:var(--dsw-font-s-14);resize:vertical}
|
|
53
|
+
.tm-textarea:focus-visible{border-color:var(--dsw-alias-brand-primary,#4d6bfe);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-brand-primary,#4d6bfe) 18%,transparent)}.tm-textarea:disabled{opacity:.55}
|
|
50
54
|
.tm-check{display:flex;align-items:center;gap:8px;color:var(--dsw-alias-label-secondary);font-size:13px}.tm-note{padding:9px 11px;border-radius:8px;background:color-mix(in srgb,var(--dsw-alias-brand-primary,#4d6bfe) 7%,transparent);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
|
|
51
55
|
.tm-grid-3{grid-template-columns:repeat(3,minmax(0,1fr))}.tm-field-actions{justify-content:flex-end}.tm-actions-left{justify-content:flex-start}
|
|
52
56
|
.tm-badge{display:inline-flex;align-items:center;height:20px;padding:0 8px;border-radius:999px;font-size:11px;font-weight:510;white-space:nowrap;flex:none}
|
|
@@ -144,10 +148,173 @@
|
|
|
144
148
|
}
|
|
145
149
|
function validateRouting(routing) {
|
|
146
150
|
if (!FALLBACKS.includes(routing?.fallback)) return "子任务回退方式无效";
|
|
151
|
+
if (routing?.decisionTimeoutMs !== undefined && routing?.decisionTimeoutMs !== null) {
|
|
152
|
+
const ms = Number(routing.decisionTimeoutMs);
|
|
153
|
+
if (!Number.isInteger(ms) || ms < 100 || ms > 60000) return "决策总超时须在 100 到 60000 毫秒之间";
|
|
154
|
+
}
|
|
147
155
|
const classifier = routing?.classifier || {};
|
|
148
156
|
if ((classifier.provider === "") !== (classifier.model === "")) return "轻量分类模型需要同时选择 Provider 与模型";
|
|
149
157
|
return "";
|
|
150
158
|
}
|
|
159
|
+
function buildDraftSection(draft) {
|
|
160
|
+
const routing = draft?.routing;
|
|
161
|
+
const profiles = draft?.profiles;
|
|
162
|
+
const jev = draft?.jev;
|
|
163
|
+
const normalizedProfiles = (Array.isArray(profiles) ? profiles : []).map((row) => ({
|
|
164
|
+
id: normalizeId(row.id),
|
|
165
|
+
name: row.name || "",
|
|
166
|
+
detail: row.detail || "",
|
|
167
|
+
keywords: normalizeKeywords(row.keywords),
|
|
168
|
+
provider: row.provider,
|
|
169
|
+
model: row.model,
|
|
170
|
+
reasoningEffort: row.reasoningEffort || "",
|
|
171
|
+
enabled: row.enabled !== false,
|
|
172
|
+
}));
|
|
173
|
+
const normJev = normalizeJev(jev);
|
|
174
|
+
return {
|
|
175
|
+
routing: {
|
|
176
|
+
...DEFAULT_ROUTING,
|
|
177
|
+
...(routing || {}),
|
|
178
|
+
decisionTimeoutMs: clampNumber(Math.round(Number(routing?.decisionTimeoutMs) || DEFAULT_ROUTING.decisionTimeoutMs), DEFAULT_ROUTING.decisionTimeoutMs, 100, 60000),
|
|
179
|
+
classifier: { ...DEFAULT_ROUTING.classifier, ...(routing?.classifier || {}) },
|
|
180
|
+
},
|
|
181
|
+
profiles: normalizedProfiles,
|
|
182
|
+
jev: {
|
|
183
|
+
enabled: normJev.enabled === true,
|
|
184
|
+
baseURL: normJev.baseURL,
|
|
185
|
+
model: normJev.model,
|
|
186
|
+
minConfidence: clampNumber(normJev.minConfidence, DEFAULT_JEV.minConfidence, 0, 1),
|
|
187
|
+
timeoutMs: clampNumber(normJev.timeoutMs, DEFAULT_JEV.timeoutMs, 100, 60000),
|
|
188
|
+
maxStateChars: clampNumber(normJev.maxStateChars, DEFAULT_JEV.maxStateChars, 500, 100000),
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function reconcileSettings({
|
|
193
|
+
currentRevision,
|
|
194
|
+
currentWritable,
|
|
195
|
+
remoteRevision,
|
|
196
|
+
remoteWritable,
|
|
197
|
+
silent,
|
|
198
|
+
dirty,
|
|
199
|
+
}) {
|
|
200
|
+
if (silent && dirty) {
|
|
201
|
+
const conflict = remoteRevision !== currentRevision;
|
|
202
|
+
return {
|
|
203
|
+
conflict,
|
|
204
|
+
revision: currentRevision,
|
|
205
|
+
writable: currentWritable,
|
|
206
|
+
shouldApplyRemote: false,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
conflict: false,
|
|
211
|
+
revision: remoteRevision,
|
|
212
|
+
writable: remoteWritable,
|
|
213
|
+
shouldApplyRemote: !silent || !dirty,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function formatRoutingDetail(reason) {
|
|
217
|
+
const REASONS = {
|
|
218
|
+
"rules-explicit-label": "规则匹配(显式标签)",
|
|
219
|
+
"rules-keywords": "规则匹配(关键词)",
|
|
220
|
+
"rules-unmatched": "规则未命中",
|
|
221
|
+
"jev-selected": "Jev 决策",
|
|
222
|
+
"llm-selected": "轻量模型分类",
|
|
223
|
+
"no-match": "无规则命中",
|
|
224
|
+
"rules-tie": "规则冲突平局",
|
|
225
|
+
"classifier-unconfigured": "分类模型未配置",
|
|
226
|
+
"jev-low-confidence": "Jev 置信度不足",
|
|
227
|
+
"jev-invalid-response": "Jev 响应无效",
|
|
228
|
+
"jev-unavailable": "Jev 服务不可用",
|
|
229
|
+
"classifier-failed": "轻量模型分类失败",
|
|
230
|
+
"decision-timeout": "决策超时",
|
|
231
|
+
"route-unusable": "目标路由不可用",
|
|
232
|
+
"explicit-child-model": "子代理指定模型",
|
|
233
|
+
"routing-disabled": "任务路由已禁用",
|
|
234
|
+
"no-profiles": "未配置任务模型",
|
|
235
|
+
"internal-role": "内部角色",
|
|
236
|
+
"plan-mode": "计划模式",
|
|
237
|
+
"existing-request": "已有会话延续",
|
|
238
|
+
"no-task": "无任务内容",
|
|
239
|
+
};
|
|
240
|
+
return (reason && REASONS[reason]) || (reason ? String(reason) : "原因未知");
|
|
241
|
+
}
|
|
242
|
+
function formatMemberTitle({ info, detail, pending }) {
|
|
243
|
+
const effective = detail?.effectiveRoute || info;
|
|
244
|
+
const hasEffectiveModel = Boolean(effective?.model);
|
|
245
|
+
if (pending && !hasEffectiveModel && !detail) return "等待子代理首次请求";
|
|
246
|
+
const parts = [];
|
|
247
|
+
if (hasEffectiveModel) {
|
|
248
|
+
const modelText = effective?.provider && effective?.model
|
|
249
|
+
? `${effective.provider}/${effective.model}${effective.reasoningEffort ? ` · ${effective.reasoningEffort}` : ""}`
|
|
250
|
+
: (effective?.model || "");
|
|
251
|
+
if (modelText) parts.push(modelText);
|
|
252
|
+
} else if (detail?.selectedRoute) {
|
|
253
|
+
if (detail.status === "adopted") {
|
|
254
|
+
parts.push(`待请求头确认 (预选: ${detail.selectedRoute.provider}/${detail.selectedRoute.model})`);
|
|
255
|
+
} else {
|
|
256
|
+
parts.push(`未采用候选: ${detail.selectedRoute.provider}/${detail.selectedRoute.model}`);
|
|
257
|
+
}
|
|
258
|
+
} else if (pending) {
|
|
259
|
+
parts.push("等待子代理首次请求");
|
|
260
|
+
}
|
|
261
|
+
if (detail) {
|
|
262
|
+
const profile = detail.profileName || detail.profileId;
|
|
263
|
+
if (profile) parts.push(`类别: ${profile}`);
|
|
264
|
+
if (detail.source) {
|
|
265
|
+
const sourceMap = { rules: "规则", jev: "Jev", llm: "轻量模型", current: "保持当前" };
|
|
266
|
+
parts.push(`来源: ${sourceMap[detail.source] || detail.source}`);
|
|
267
|
+
}
|
|
268
|
+
if (typeof detail.confidence === "number") {
|
|
269
|
+
parts.push(`置信度: ${Math.round(detail.confidence * 100)}%`);
|
|
270
|
+
}
|
|
271
|
+
if (typeof detail.durationMs === "number") {
|
|
272
|
+
parts.push(`耗时: ${detail.durationMs}ms`);
|
|
273
|
+
}
|
|
274
|
+
if (detail.reason) {
|
|
275
|
+
parts.push(`原因: ${formatRoutingDetail(detail.reason)}`);
|
|
276
|
+
}
|
|
277
|
+
} else if (hasEffectiveModel) {
|
|
278
|
+
parts.push("原因未知");
|
|
279
|
+
}
|
|
280
|
+
return parts.join(" · ");
|
|
281
|
+
}
|
|
282
|
+
function formatNextStep(next) {
|
|
283
|
+
const MAP = {
|
|
284
|
+
rules: "规则命中",
|
|
285
|
+
jev: "交由 Jev 决策",
|
|
286
|
+
llm: "交由轻量模型分类",
|
|
287
|
+
current: "保持当前路由",
|
|
288
|
+
};
|
|
289
|
+
return MAP[next] || (next ? String(next) : "未知走向");
|
|
290
|
+
}
|
|
291
|
+
function formatRuleAnalysisSummary(analysis) {
|
|
292
|
+
if (!analysis || typeof analysis !== "object") return "";
|
|
293
|
+
const parts = [];
|
|
294
|
+
if (analysis.source) {
|
|
295
|
+
const srcMap = {
|
|
296
|
+
"explicit-label": "显式标签匹配",
|
|
297
|
+
keywords: "关键词匹配",
|
|
298
|
+
none: "无匹配",
|
|
299
|
+
tie: "平局冲突",
|
|
300
|
+
};
|
|
301
|
+
parts.push(`来源: ${srcMap[analysis.source] || analysis.source}`);
|
|
302
|
+
}
|
|
303
|
+
if (analysis.profileId) {
|
|
304
|
+
parts.push(`候选类型: ${analysis.profileId}`);
|
|
305
|
+
}
|
|
306
|
+
if (Array.isArray(analysis.matches) && analysis.matches.length > 0) {
|
|
307
|
+
const details = analysis.matches.map((m) => {
|
|
308
|
+
const kw = (m.matchedKeywords || []).map((k) => `${k.keyword}(${k.in === "label" ? "标题" : "内容"})`).join("、");
|
|
309
|
+
return `${m.id}(${m.score}分${kw ? `: ${kw}` : ""})`;
|
|
310
|
+
}).join(";");
|
|
311
|
+
parts.push(`命中项: ${details}`);
|
|
312
|
+
}
|
|
313
|
+
if (Array.isArray(analysis.tiedIds) && analysis.tiedIds.length > 0) {
|
|
314
|
+
parts.push(`平局: ${analysis.tiedIds.join("、")}`);
|
|
315
|
+
}
|
|
316
|
+
return parts.join(" | ");
|
|
317
|
+
}
|
|
151
318
|
function validateProfiles(rows) {
|
|
152
319
|
const seen = new Set();
|
|
153
320
|
for (const row of rows || []) {
|
|
@@ -234,9 +401,10 @@
|
|
|
234
401
|
|
|
235
402
|
function TaskProfileCard(props) {
|
|
236
403
|
const {
|
|
237
|
-
profileId, template, profile, modelRows, groups, saving, custom,
|
|
404
|
+
profileId, template, profile, modelRows, groups, saving, writable = true, custom,
|
|
238
405
|
onChangeModel, onSetField, onRemove, onRename,
|
|
239
406
|
} = props;
|
|
407
|
+
const disabled = saving || !writable;
|
|
240
408
|
const selected = profile?.provider && profile?.model
|
|
241
409
|
? modelRows.find(({ group, model }) => group.id === profile.provider && model.id === profile.model)
|
|
242
410
|
: null;
|
|
@@ -259,27 +427,27 @@
|
|
|
259
427
|
react.createElement("h3", { className: "tm-title" }, title),
|
|
260
428
|
react.createElement("p", { className: "tm-detail" }, detail)),
|
|
261
429
|
custom ? react.createElement("button", {
|
|
262
|
-
className: "tm-button tm-danger", type: "button", disabled
|
|
430
|
+
className: "tm-button tm-danger", type: "button", disabled, onClick: () => onRemove(profileId),
|
|
263
431
|
}, "删除") : null),
|
|
264
432
|
custom ? react.createElement("div", { className: "tm-grid" },
|
|
265
433
|
react.createElement("label", { className: "tm-field" },
|
|
266
434
|
react.createElement("span", { className: "tm-label" }, "任务类型 ID"),
|
|
267
435
|
react.createElement("input", {
|
|
268
|
-
className: "tm-input", value: profileId, disabled
|
|
436
|
+
className: "tm-input", value: profileId, disabled,
|
|
269
437
|
onChange: (event) => onRename(profileId, event.target.value),
|
|
270
438
|
"aria-label": `${profileId} 任务类型 ID`,
|
|
271
439
|
})),
|
|
272
440
|
react.createElement("label", { className: "tm-field" },
|
|
273
441
|
react.createElement("span", { className: "tm-label" }, "显示名称"),
|
|
274
442
|
react.createElement("input", {
|
|
275
|
-
className: "tm-input", value: profile?.name ?? "", disabled
|
|
443
|
+
className: "tm-input", value: profile?.name ?? "", disabled,
|
|
276
444
|
onChange: (event) => onSetField(profileId, { name: event.target.value }),
|
|
277
445
|
"aria-label": `${profileId} 显示名称`,
|
|
278
446
|
}))) : null,
|
|
279
447
|
custom ? react.createElement("label", { className: "tm-field" },
|
|
280
448
|
react.createElement("span", { className: "tm-label" }, "任务描述(供 Jev / 轻量模型判断)"),
|
|
281
449
|
react.createElement("input", {
|
|
282
|
-
className: "tm-input", value: profile?.detail ?? "", disabled
|
|
450
|
+
className: "tm-input", value: profile?.detail ?? "", disabled,
|
|
283
451
|
onChange: (event) => onSetField(profileId, { detail: event.target.value }),
|
|
284
452
|
"aria-label": `${profileId} 任务描述`,
|
|
285
453
|
})) : null,
|
|
@@ -287,7 +455,7 @@
|
|
|
287
455
|
react.createElement("label", { className: "tm-field" },
|
|
288
456
|
react.createElement("span", { className: "tm-label" }, "模型"),
|
|
289
457
|
react.createElement("select", {
|
|
290
|
-
className: "tm-select", value: profile?.provider && profile?.model ? modelKey(profile.provider, profile.model) : "", disabled
|
|
458
|
+
className: "tm-select", value: profile?.provider && profile?.model ? modelKey(profile.provider, profile.model) : "", disabled,
|
|
291
459
|
onChange: (event) => onChangeModel(profileId, event.target.value), "aria-label": `${profileId} 任务模型`,
|
|
292
460
|
},
|
|
293
461
|
react.createElement("option", { value: "" }, "不启用(保持当前路由)"),
|
|
@@ -295,7 +463,7 @@
|
|
|
295
463
|
react.createElement("label", { className: "tm-field" },
|
|
296
464
|
react.createElement("span", { className: "tm-label" }, "思考档位"),
|
|
297
465
|
react.createElement("select", {
|
|
298
|
-
className: "tm-select", value: profile?.reasoningEffort || "", disabled: !profile || efforts.length === 0 ||
|
|
466
|
+
className: "tm-select", value: profile?.reasoningEffort || "", disabled: !profile || efforts.length === 0 || disabled,
|
|
299
467
|
onChange: (event) => onSetField(profileId, { reasoningEffort: event.target.value }), "aria-label": `${profileId} 任务思考档位`,
|
|
300
468
|
},
|
|
301
469
|
react.createElement("option", { value: "" }, "模型默认"),
|
|
@@ -303,13 +471,13 @@
|
|
|
303
471
|
react.createElement("label", { className: "tm-field" },
|
|
304
472
|
react.createElement("span", { className: "tm-label" }, "匹配关键词(逗号分隔;留空则只依赖 Jev/轻量模型)"),
|
|
305
473
|
react.createElement("input", {
|
|
306
|
-
className: "tm-input", value: keywords, disabled: !profile ||
|
|
474
|
+
className: "tm-input", value: keywords, disabled: !profile || disabled,
|
|
307
475
|
onChange: (event) => onSetField(profileId, { keywords: splitKeywords(event.target.value) }),
|
|
308
476
|
"aria-label": `${profileId} 匹配关键词`,
|
|
309
477
|
})),
|
|
310
478
|
react.createElement("label", { className: "tm-check" },
|
|
311
479
|
react.createElement("input", {
|
|
312
|
-
type: "checkbox", checked: profile?.enabled !== false && profile !== undefined, disabled: !profile ||
|
|
480
|
+
type: "checkbox", checked: profile?.enabled !== false && profile !== undefined, disabled: !profile || disabled,
|
|
313
481
|
onChange: (event) => onSetField(profileId, { enabled: event.target.checked }),
|
|
314
482
|
}),
|
|
315
483
|
"启用此任务类型"));
|
|
@@ -357,6 +525,15 @@
|
|
|
357
525
|
return value?.models && typeof value.models === "object" ? value.models : {};
|
|
358
526
|
}
|
|
359
527
|
|
|
528
|
+
async function loadRoutingDetails(childIds) {
|
|
529
|
+
if (!Array.isArray(childIds) || childIds.length === 0) return { models: {}, details: {} };
|
|
530
|
+
const value = await settingsRequest("routingDetails", { childIds });
|
|
531
|
+
return {
|
|
532
|
+
models: value?.models && typeof value.models === "object" ? value.models : {},
|
|
533
|
+
details: value?.details && typeof value.details === "object" ? value.details : {},
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
360
537
|
function sameNavigable(next, previous) {
|
|
361
538
|
if (next === previous) return true;
|
|
362
539
|
if (!Array.isArray(next) || !Array.isArray(previous) || next.length !== previous.length) return false;
|
|
@@ -385,7 +562,7 @@
|
|
|
385
562
|
* request. The durable node and Definition stay owned by the host.
|
|
386
563
|
*/
|
|
387
564
|
function WorkflowRunPanel(props) {
|
|
388
|
-
const { node, sessionId, openSession, loadModels, useSessions, useSessionStatus } = props;
|
|
565
|
+
const { node, sessionId, openSession, loadModels, loadDetails, useSessions, useSessionStatus } = props;
|
|
389
566
|
const phases = Array.isArray(node?.data?.phases) ? node.data.phases : [];
|
|
390
567
|
const allMembers = phases.flatMap((phase) => (Array.isArray(phase.members) ? phase.members : []));
|
|
391
568
|
const [runOpen, setRunOpen] = react.useState(node?.data?.status !== "completed");
|
|
@@ -393,6 +570,7 @@
|
|
|
393
570
|
phases.filter((phase) => (phase.members || []).some((member) => member.status !== "completed")).map((phase) => phase.key),
|
|
394
571
|
));
|
|
395
572
|
const [models, setModels] = react.useState({});
|
|
573
|
+
const [details, setDetails] = react.useState({});
|
|
396
574
|
const statuses = typeof useSessionStatus === "function"
|
|
397
575
|
? useSessionStatus((value) => value)
|
|
398
576
|
: new Map();
|
|
@@ -407,14 +585,34 @@
|
|
|
407
585
|
|
|
408
586
|
react.useEffect(() => {
|
|
409
587
|
const childIds = childKey === "" ? [] : childKey.split("\u0000");
|
|
410
|
-
if (childIds.length === 0 || typeof loadModels !== "function") return () => {};
|
|
588
|
+
if (childIds.length === 0 || (typeof loadDetails !== "function" && typeof loadModels !== "function")) return () => {};
|
|
411
589
|
let cancelled = false;
|
|
590
|
+
let inFlight = false;
|
|
412
591
|
const load = async () => {
|
|
592
|
+
if (inFlight) return;
|
|
593
|
+
inFlight = true;
|
|
413
594
|
try {
|
|
414
|
-
|
|
415
|
-
|
|
595
|
+
if (typeof loadDetails === "function") {
|
|
596
|
+
const res = await loadDetails(childIds);
|
|
597
|
+
if (!cancelled && res && typeof res === "object") {
|
|
598
|
+
if (res.models && typeof res.models === "object") setModels((prev) => ({ ...prev, ...res.models }));
|
|
599
|
+
if (res.details && typeof res.details === "object") setDetails((prev) => ({ ...prev, ...res.details }));
|
|
600
|
+
}
|
|
601
|
+
} else if (typeof loadModels === "function") {
|
|
602
|
+
const res = await loadModels(childIds);
|
|
603
|
+
if (!cancelled && res && typeof res === "object") {
|
|
604
|
+
if ("models" in res || "details" in res) {
|
|
605
|
+
if (res.models && typeof res.models === "object") setModels((prev) => ({ ...prev, ...res.models }));
|
|
606
|
+
if (res.details && typeof res.details === "object") setDetails((prev) => ({ ...prev, ...res.details }));
|
|
607
|
+
} else {
|
|
608
|
+
setModels((prev) => ({ ...prev, ...res }));
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
416
612
|
} catch {
|
|
417
613
|
// The model badge is additive; keep the last known values.
|
|
614
|
+
} finally {
|
|
615
|
+
inFlight = false;
|
|
418
616
|
}
|
|
419
617
|
};
|
|
420
618
|
void load();
|
|
@@ -423,7 +621,7 @@
|
|
|
423
621
|
cancelled = true;
|
|
424
622
|
if (timer !== undefined) clearInterval(timer);
|
|
425
623
|
};
|
|
426
|
-
}, [childKey, loadModels, hasRunning]);
|
|
624
|
+
}, [childKey, loadDetails, loadModels, hasRunning]);
|
|
427
625
|
|
|
428
626
|
const toggleRun = () => setRunOpen((value) => !value);
|
|
429
627
|
const togglePhase = (key) => setOpenPhases((current) => {
|
|
@@ -462,10 +660,13 @@
|
|
|
462
660
|
phaseOpen ? react.createElement("div", { className: "tm-wf-members" },
|
|
463
661
|
...members.map((member, memberIndex) => {
|
|
464
662
|
const info = models[member.childId];
|
|
465
|
-
const
|
|
663
|
+
const detail = details[member.childId];
|
|
664
|
+
const effective = detail?.effectiveRoute || info;
|
|
665
|
+
const model = typeof effective?.model === "string" ? effective.model : "";
|
|
466
666
|
const pending = model === "" && member.status === "running";
|
|
467
667
|
const target = navigable.find((entry) => entry.id === member.childId);
|
|
468
668
|
const clickable = target !== undefined && typeof openSession === "function";
|
|
669
|
+
const tooltip = formatMemberTitle({ info, detail, pending });
|
|
469
670
|
const content = [
|
|
470
671
|
react.createElement("span", { className: `tm-wf-dot tm-wf-${member.status || "running"}`, key: "dot" }),
|
|
471
672
|
react.createElement("span", { className: "tm-wf-label", key: "label", title: member.label || "" }, member.label || "空成员名"),
|
|
@@ -473,9 +674,8 @@
|
|
|
473
674
|
? react.createElement("span", {
|
|
474
675
|
className: `tm-wf-model ${pending ? "tm-wf-model-pending" : ""}`,
|
|
475
676
|
key: "model",
|
|
476
|
-
title:
|
|
477
|
-
|
|
478
|
-
: "等待子代理首次请求",
|
|
677
|
+
title: tooltip,
|
|
678
|
+
"aria-label": tooltip,
|
|
479
679
|
}, model || "模型…")
|
|
480
680
|
: null,
|
|
481
681
|
react.createElement("span", { className: "tm-wf-member-status", key: "status" }, statusText(member.status || "running")),
|
|
@@ -502,6 +702,7 @@
|
|
|
502
702
|
const [error, setError] = react.useState("");
|
|
503
703
|
const [writable, setWritable] = react.useState(false);
|
|
504
704
|
const [revision, setRevision] = react.useState(0);
|
|
705
|
+
const [conflict, setConflict] = react.useState(false);
|
|
505
706
|
const [groups, setGroups] = react.useState([]);
|
|
506
707
|
const [failures, setFailures] = react.useState([]);
|
|
507
708
|
const [routing, setRouting] = react.useState({ ...DEFAULT_ROUTING, classifier: { ...DEFAULT_ROUTING.classifier } });
|
|
@@ -513,7 +714,68 @@
|
|
|
513
714
|
const [jevTest, setJevTest] = react.useState("");
|
|
514
715
|
const [saved, setSaved] = react.useState("");
|
|
515
716
|
const [saving, setSaving] = react.useState(false);
|
|
717
|
+
|
|
718
|
+
const [previewLabel, setPreviewLabel] = react.useState("");
|
|
719
|
+
const [previewText, setPreviewText] = react.useState("");
|
|
720
|
+
const [previewRunning, setPreviewRunning] = react.useState(false);
|
|
721
|
+
const [previewResult, setPreviewResult] = react.useState(null);
|
|
722
|
+
const [previewError, setPreviewError] = react.useState("");
|
|
723
|
+
|
|
516
724
|
const dirtyRef = react.useRef(false);
|
|
725
|
+
const revisionRef = react.useRef(0);
|
|
726
|
+
const writableRef = react.useRef(false);
|
|
727
|
+
const previewReqIdRef = react.useRef(0);
|
|
728
|
+
const jevTestReqIdRef = react.useRef(0);
|
|
729
|
+
revisionRef.current = revision;
|
|
730
|
+
writableRef.current = writable;
|
|
731
|
+
|
|
732
|
+
const clearPreview = () => {
|
|
733
|
+
previewReqIdRef.current++;
|
|
734
|
+
setPreviewResult(null);
|
|
735
|
+
setPreviewError("");
|
|
736
|
+
setPreviewRunning(false);
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
const clearJevTest = () => {
|
|
740
|
+
jevTestReqIdRef.current++;
|
|
741
|
+
setJevTest("");
|
|
742
|
+
setJevTesting(false);
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
const updateRouting = (updater) => {
|
|
746
|
+
clearPreview();
|
|
747
|
+
dirtyRef.current = true;
|
|
748
|
+
setRouting(updater);
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
const updateProfiles = (updater) => {
|
|
752
|
+
clearPreview();
|
|
753
|
+
dirtyRef.current = true;
|
|
754
|
+
setProfiles(updater);
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
const updateJev = (updater) => {
|
|
758
|
+
clearPreview();
|
|
759
|
+
clearJevTest();
|
|
760
|
+
dirtyRef.current = true;
|
|
761
|
+
setJev(updater);
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
const updateJevKey = (value) => {
|
|
765
|
+
clearJevTest();
|
|
766
|
+
dirtyRef.current = true;
|
|
767
|
+
setJevKey(value);
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
const changePreviewLabel = (value) => {
|
|
771
|
+
clearPreview();
|
|
772
|
+
setPreviewLabel(value);
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
const changePreviewText = (value) => {
|
|
776
|
+
clearPreview();
|
|
777
|
+
setPreviewText(value);
|
|
778
|
+
};
|
|
517
779
|
|
|
518
780
|
const load = async (silent = false) => {
|
|
519
781
|
if (!silent) {
|
|
@@ -539,26 +801,47 @@
|
|
|
539
801
|
const nextRouting = {
|
|
540
802
|
...DEFAULT_ROUTING,
|
|
541
803
|
...(settingsView.value?.routing || {}),
|
|
804
|
+
decisionTimeoutMs: Number.isFinite(Number(settingsView.value?.routing?.decisionTimeoutMs))
|
|
805
|
+
? clampNumber(Math.round(Number(settingsView.value?.routing?.decisionTimeoutMs)), DEFAULT_ROUTING.decisionTimeoutMs, 100, 60000)
|
|
806
|
+
: DEFAULT_ROUTING.decisionTimeoutMs,
|
|
542
807
|
classifier: { ...DEFAULT_ROUTING.classifier, ...(settingsView.value?.routing?.classifier || {}) },
|
|
543
808
|
};
|
|
544
809
|
const nextProfiles = normalizeProfiles(settingsView.value?.profiles);
|
|
545
810
|
const nextJev = normalizeJev(settingsView.value?.jev);
|
|
546
|
-
setWritable(settingsView.writable);
|
|
547
|
-
setRevision(settingsView.revision);
|
|
548
811
|
setGroups(modelsValue.groups || []);
|
|
549
812
|
setFailures(modelsValue.failures || []);
|
|
550
|
-
|
|
813
|
+
|
|
814
|
+
const reconciliation = reconcileSettings({
|
|
815
|
+
currentRevision: revisionRef.current,
|
|
816
|
+
currentWritable: writableRef.current,
|
|
817
|
+
remoteRevision: settingsView.revision,
|
|
818
|
+
remoteWritable: settingsView.writable,
|
|
819
|
+
silent,
|
|
820
|
+
dirty: dirtyRef.current,
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
setConflict(reconciliation.conflict);
|
|
824
|
+
setWritable(reconciliation.writable);
|
|
825
|
+
setRevision(reconciliation.revision);
|
|
826
|
+
|
|
827
|
+
if (reconciliation.shouldApplyRemote) {
|
|
828
|
+
clearPreview();
|
|
829
|
+
clearJevTest();
|
|
551
830
|
setRouting(nextRouting);
|
|
552
831
|
setProfiles(nextProfiles);
|
|
553
832
|
setJev(nextJev);
|
|
554
833
|
setSaved(JSON.stringify({ routing: nextRouting, profiles: nextProfiles, jev: nextJev }));
|
|
555
834
|
}
|
|
556
835
|
settingsRequest("jevStatus", {}, props.api)
|
|
557
|
-
.then((value) =>
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
836
|
+
.then((value) => {
|
|
837
|
+
if (value && typeof value?.configured === "boolean") {
|
|
838
|
+
setJevStatus({
|
|
839
|
+
configured: value.configured,
|
|
840
|
+
model: typeof value?.model === "string" ? value.model : "",
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
})
|
|
844
|
+
.catch(() => {});
|
|
562
845
|
setStatus("ready");
|
|
563
846
|
} catch (cause) {
|
|
564
847
|
if (!silent) {
|
|
@@ -589,7 +872,7 @@
|
|
|
589
872
|
const statusText = statusMessage(status, writable, dirty);
|
|
590
873
|
|
|
591
874
|
const setProfile = (id, patch) => {
|
|
592
|
-
|
|
875
|
+
updateProfiles((current) => {
|
|
593
876
|
const target = normalizeId(id);
|
|
594
877
|
const index = current.findIndex((entry) => normalizeId(entry.id) === target);
|
|
595
878
|
if (patch === null) return current.filter((entry) => normalizeId(entry.id) !== target);
|
|
@@ -615,12 +898,12 @@
|
|
|
615
898
|
let suffix = 1;
|
|
616
899
|
while (byProfile.has(`custom-${suffix}`)) suffix++;
|
|
617
900
|
const id = `custom-${suffix}`;
|
|
618
|
-
|
|
901
|
+
updateProfiles((current) => [...current, normalizeProfile({ id, name: `自定义任务 ${suffix}`, provider: "", model: "" })]);
|
|
619
902
|
};
|
|
620
903
|
|
|
621
904
|
const renameProfile = (oldId, nextValue) => {
|
|
622
905
|
const target = normalizeId(oldId);
|
|
623
|
-
|
|
906
|
+
updateProfiles((current) => current.map((entry) => normalizeId(entry.id) === target
|
|
624
907
|
? normalizeProfile({ ...entry, id: normalizeId(nextValue) })
|
|
625
908
|
: entry));
|
|
626
909
|
};
|
|
@@ -630,11 +913,11 @@
|
|
|
630
913
|
const changeClassifier = (value) => {
|
|
631
914
|
const selected = parseModelKey(value);
|
|
632
915
|
if (!selected) {
|
|
633
|
-
|
|
916
|
+
updateRouting((current) => ({ ...current, classifier: { provider: "", model: "", reasoningEffort: "" } }));
|
|
634
917
|
return;
|
|
635
918
|
}
|
|
636
919
|
const row = modelRows.find(({ group, model }) => group.id === selected.provider && model.id === selected.model);
|
|
637
|
-
|
|
920
|
+
updateRouting((current) => ({
|
|
638
921
|
...current,
|
|
639
922
|
classifier: {
|
|
640
923
|
provider: selected.provider,
|
|
@@ -647,24 +930,60 @@
|
|
|
647
930
|
const refreshJevStatus = async () => {
|
|
648
931
|
try {
|
|
649
932
|
const value = await settingsRequest("jevStatus", {}, props.api);
|
|
650
|
-
|
|
933
|
+
if (value && typeof value?.configured === "boolean") {
|
|
934
|
+
setJevStatus({ configured: value.configured, model: typeof value?.model === "string" ? value.model : "" });
|
|
935
|
+
}
|
|
651
936
|
} catch {
|
|
652
|
-
|
|
937
|
+
// 网络 status 失败不能误改已保存状态
|
|
653
938
|
}
|
|
654
939
|
};
|
|
655
940
|
|
|
656
941
|
const testJev = async () => {
|
|
942
|
+
const reqId = ++jevTestReqIdRef.current;
|
|
657
943
|
setJevTesting(true);
|
|
658
944
|
setJevTest("");
|
|
659
945
|
try {
|
|
660
946
|
const payload = { ...(jevKey.trim() === "" ? {} : { apiKey: jevKey.trim() }), jev: normalizeJev(jev) };
|
|
661
947
|
const result = await settingsRequest("testJev", payload, props.api);
|
|
662
|
-
|
|
663
|
-
|
|
948
|
+
if (jevTestReqIdRef.current === reqId) {
|
|
949
|
+
setJevTest(`✓ Jev 连接测试成功${result?.latencyMs === undefined ? "" : `(${result.latencyMs}ms)`}`);
|
|
950
|
+
}
|
|
664
951
|
} catch (cause) {
|
|
665
|
-
|
|
952
|
+
if (jevTestReqIdRef.current === reqId) {
|
|
953
|
+
setJevTest(`Jev 测试失败:${cause instanceof Error ? cause.message : String(cause)}`);
|
|
954
|
+
}
|
|
666
955
|
} finally {
|
|
667
|
-
|
|
956
|
+
if (jevTestReqIdRef.current === reqId) {
|
|
957
|
+
setJevTesting(false);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
const runPreview = async () => {
|
|
963
|
+
if (validation || previewRunning || (!previewLabel.trim() && !previewText.trim())) return;
|
|
964
|
+
const reqId = ++previewReqIdRef.current;
|
|
965
|
+
setPreviewRunning(true);
|
|
966
|
+
setPreviewError("");
|
|
967
|
+
setPreviewResult(null);
|
|
968
|
+
try {
|
|
969
|
+
const section = buildDraftSection({ routing, profiles, jev });
|
|
970
|
+
const res = await settingsRequest("previewRules", {
|
|
971
|
+
label: previewLabel.slice(0, 300),
|
|
972
|
+
text: previewText.slice(0, 6000),
|
|
973
|
+
section,
|
|
974
|
+
}, props.api);
|
|
975
|
+
if (previewReqIdRef.current === reqId) {
|
|
976
|
+
setPreviewResult(res);
|
|
977
|
+
}
|
|
978
|
+
} catch {
|
|
979
|
+
// error不暴露原文任务或Key
|
|
980
|
+
if (previewReqIdRef.current === reqId) {
|
|
981
|
+
setPreviewError("规则试运行请求失败");
|
|
982
|
+
}
|
|
983
|
+
} finally {
|
|
984
|
+
if (previewReqIdRef.current === reqId) {
|
|
985
|
+
setPreviewRunning(false);
|
|
986
|
+
}
|
|
668
987
|
}
|
|
669
988
|
};
|
|
670
989
|
|
|
@@ -677,32 +996,8 @@
|
|
|
677
996
|
await settingsRequest("saveJevKey", { apiKey: jevKey.trim() }, props.api);
|
|
678
997
|
setJevKey("");
|
|
679
998
|
}
|
|
680
|
-
const normalizedProfiles = profiles.map((row) => ({
|
|
681
|
-
id: normalizeId(row.id),
|
|
682
|
-
name: row.name || "",
|
|
683
|
-
detail: row.detail || "",
|
|
684
|
-
keywords: normalizeKeywords(row.keywords),
|
|
685
|
-
provider: row.provider,
|
|
686
|
-
model: row.model,
|
|
687
|
-
reasoningEffort: row.reasoningEffort || "",
|
|
688
|
-
enabled: row.enabled !== false,
|
|
689
|
-
}));
|
|
690
999
|
await settingsRequest("replace", {
|
|
691
|
-
section: {
|
|
692
|
-
routing: {
|
|
693
|
-
...routing,
|
|
694
|
-
classifier: { ...routing.classifier },
|
|
695
|
-
},
|
|
696
|
-
profiles: normalizedProfiles,
|
|
697
|
-
jev: {
|
|
698
|
-
enabled: jev.enabled === true,
|
|
699
|
-
baseURL: jev.baseURL,
|
|
700
|
-
model: jev.model,
|
|
701
|
-
minConfidence: clampNumber(jev.minConfidence, DEFAULT_JEV.minConfidence, 0, 1),
|
|
702
|
-
timeoutMs: clampNumber(jev.timeoutMs, DEFAULT_JEV.timeoutMs, 100, 60000),
|
|
703
|
-
maxStateChars: clampNumber(jev.maxStateChars, DEFAULT_JEV.maxStateChars, 500, 100000),
|
|
704
|
-
},
|
|
705
|
-
},
|
|
1000
|
+
section: buildDraftSection({ routing, profiles, jev }),
|
|
706
1001
|
expectedRevision: revision,
|
|
707
1002
|
}, props.api);
|
|
708
1003
|
await load(false);
|
|
@@ -726,39 +1021,56 @@
|
|
|
726
1021
|
react.createElement("h2", null, "任务模型"),
|
|
727
1022
|
react.createElement("p", { className: "tm-intro" }, INTRO_TEXT),
|
|
728
1023
|
feedback ? react.createElement("div", { className: "tm-feedback tm-error", role: "alert" }, feedback) : null,
|
|
1024
|
+
conflict ? react.createElement("div", { className: "tm-feedback tm-warn", role: "alert" },
|
|
1025
|
+
react.createElement("span", null, "外部配置已更新,当前草稿存在冲突。"),
|
|
1026
|
+
react.createElement("button", {
|
|
1027
|
+
className: "tm-button", type: "button", style: { height: 26, padding: "0 10px", fontSize: 12 },
|
|
1028
|
+
onClick: () => load(false),
|
|
1029
|
+
}, "重新加载最新配置")) : null,
|
|
729
1030
|
react.createElement("section", { className: "tm-card" },
|
|
730
1031
|
react.createElement("div", null,
|
|
731
1032
|
react.createElement("h3", { className: "tm-title" }, "子任务路由"),
|
|
732
1033
|
react.createElement("p", { className: "tm-detail" }, "对所有会话委派的子任务生效;不要求任何 Agent Preset,也不会改写主会话模型。")),
|
|
733
1034
|
react.createElement("label", { className: "tm-check" },
|
|
734
1035
|
react.createElement("input", {
|
|
735
|
-
type: "checkbox", checked: routing.enabled, disabled: saving,
|
|
736
|
-
onChange: (event) =>
|
|
1036
|
+
type: "checkbox", checked: routing.enabled, disabled: saving || !writable,
|
|
1037
|
+
onChange: (event) => updateRouting((current) => ({ ...current, enabled: event.target.checked })),
|
|
737
1038
|
}),
|
|
738
1039
|
"启用按任务类型路由子任务"),
|
|
739
1040
|
react.createElement("div", { className: "tm-grid" },
|
|
740
1041
|
react.createElement("label", { className: "tm-field" },
|
|
741
1042
|
react.createElement("span", { className: "tm-label" }, "规则未命中时"),
|
|
742
1043
|
react.createElement("select", {
|
|
743
|
-
className: "tm-select", value: routing.fallback, disabled: saving,
|
|
744
|
-
onChange: (event) =>
|
|
1044
|
+
className: "tm-select", value: routing.fallback, disabled: saving || !writable,
|
|
1045
|
+
onChange: (event) => updateRouting((current) => ({ ...current, fallback: event.target.value })),
|
|
745
1046
|
},
|
|
746
1047
|
react.createElement("option", { value: "taskRole" }, "保持当前路由"),
|
|
747
1048
|
react.createElement("option", { value: "llm" }, "用轻量模型分类"))),
|
|
748
|
-
react.createElement("
|
|
749
|
-
react.createElement("span", { className: "tm-label" }, "
|
|
750
|
-
react.createElement("
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
1049
|
+
react.createElement("label", { className: "tm-field" },
|
|
1050
|
+
react.createElement("span", { className: "tm-label" }, "决策总超时(毫秒,100-60000)"),
|
|
1051
|
+
react.createElement("input", {
|
|
1052
|
+
className: "tm-input", type: "number", min: 100, max: 60000, step: 100,
|
|
1053
|
+
value: routing.decisionTimeoutMs ?? DEFAULT_ROUTING.decisionTimeoutMs, disabled: saving || !writable,
|
|
1054
|
+
onChange: (event) => updateRouting((current) => ({
|
|
1055
|
+
...current,
|
|
1056
|
+
decisionTimeoutMs: clampNumber(Math.round(Number(event.target.value) || DEFAULT_ROUTING.decisionTimeoutMs), DEFAULT_ROUTING.decisionTimeoutMs, 100, 60000),
|
|
1057
|
+
})),
|
|
1058
|
+
"aria-label": "决策总超时",
|
|
1059
|
+
}))),
|
|
1060
|
+
react.createElement("div", { className: "tm-field" },
|
|
1061
|
+
react.createElement("span", { className: "tm-label" }, "决策选项"),
|
|
1062
|
+
react.createElement("label", { className: "tm-check" },
|
|
1063
|
+
react.createElement("input", {
|
|
1064
|
+
type: "checkbox", checked: routing.useJev, disabled: saving || !writable,
|
|
1065
|
+
onChange: (event) => updateRouting((current) => ({ ...current, useJev: event.target.checked })),
|
|
1066
|
+
}),
|
|
1067
|
+
"规则未命中时优先问 Jev"),
|
|
1068
|
+
react.createElement("label", { className: "tm-check" },
|
|
1069
|
+
react.createElement("input", {
|
|
1070
|
+
type: "checkbox", checked: routing.respectExplicitChildModel, disabled: saving || !writable,
|
|
1071
|
+
onChange: (event) => updateRouting((current) => ({ ...current, respectExplicitChildModel: event.target.checked })),
|
|
1072
|
+
}),
|
|
1073
|
+
"尊重子任务显式选择的模型")),
|
|
762
1074
|
react.createElement("p", { className: "tm-note" }, "任务类型按子代理首次请求决定一次,后续请求沿用同一模型。规则并列时不会按配置顺序随机选择。")),
|
|
763
1075
|
react.createElement("div", { className: "tm-card" },
|
|
764
1076
|
react.createElement("div", null,
|
|
@@ -768,7 +1080,7 @@
|
|
|
768
1080
|
react.createElement("label", { className: "tm-field" },
|
|
769
1081
|
react.createElement("span", { className: "tm-label" }, "分类模型"),
|
|
770
1082
|
react.createElement("select", {
|
|
771
|
-
className: "tm-select", value: classifier.provider && classifier.model ? modelKey(classifier.provider, classifier.model) : "", disabled: saving,
|
|
1083
|
+
className: "tm-select", value: classifier.provider && classifier.model ? modelKey(classifier.provider, classifier.model) : "", disabled: saving || !writable,
|
|
772
1084
|
onChange: (event) => changeClassifier(event.target.value),
|
|
773
1085
|
},
|
|
774
1086
|
react.createElement("option", { value: "" }, "不配置"),
|
|
@@ -777,8 +1089,8 @@
|
|
|
777
1089
|
react.createElement("label", { className: "tm-field" },
|
|
778
1090
|
react.createElement("span", { className: "tm-label" }, "分类思考档位"),
|
|
779
1091
|
react.createElement("select", {
|
|
780
|
-
className: "tm-select", value: classifier.reasoningEffort || "", disabled: classifierEfforts.length === 0 || saving,
|
|
781
|
-
onChange: (event) =>
|
|
1092
|
+
className: "tm-select", value: classifier.reasoningEffort || "", disabled: classifierEfforts.length === 0 || saving || !writable,
|
|
1093
|
+
onChange: (event) => updateRouting((current) => ({
|
|
782
1094
|
...current,
|
|
783
1095
|
classifier: { ...current.classifier, reasoningEffort: event.target.value },
|
|
784
1096
|
})),
|
|
@@ -794,6 +1106,7 @@
|
|
|
794
1106
|
modelRows,
|
|
795
1107
|
groups,
|
|
796
1108
|
saving,
|
|
1109
|
+
writable,
|
|
797
1110
|
custom: false,
|
|
798
1111
|
onChangeModel: changeProfileModel,
|
|
799
1112
|
onSetField: setProfileField,
|
|
@@ -808,6 +1121,7 @@
|
|
|
808
1121
|
modelRows,
|
|
809
1122
|
groups,
|
|
810
1123
|
saving,
|
|
1124
|
+
writable,
|
|
811
1125
|
custom: true,
|
|
812
1126
|
onChangeModel: changeProfileModel,
|
|
813
1127
|
onSetField: setProfileField,
|
|
@@ -816,59 +1130,106 @@
|
|
|
816
1130
|
}))),
|
|
817
1131
|
react.createElement("div", { className: "tm-actions" },
|
|
818
1132
|
react.createElement("button", { className: "tm-button", type: "button", disabled: saving || !writable, onClick: addProfile }, "添加任务类型")),
|
|
1133
|
+
react.createElement("section", { className: "tm-card" },
|
|
1134
|
+
react.createElement("div", null,
|
|
1135
|
+
react.createElement("h3", { className: "tm-title" }, "规则试运行"),
|
|
1136
|
+
react.createElement("p", { className: "tm-detail" }, "输入子任务标题与描述,测试规则匹配与路由决策走向(含未保存草稿);不调用任何模型或写配置。")),
|
|
1137
|
+
react.createElement("label", { className: "tm-field" },
|
|
1138
|
+
react.createElement("span", { className: "tm-label" }, "子任务 Label / 标题(最多 300 字符)"),
|
|
1139
|
+
react.createElement("input", {
|
|
1140
|
+
className: "tm-input",
|
|
1141
|
+
maxLength: 300,
|
|
1142
|
+
value: previewLabel,
|
|
1143
|
+
disabled: previewRunning || saving,
|
|
1144
|
+
placeholder: "例如:实现用户登录校验",
|
|
1145
|
+
onChange: (event) => changePreviewLabel(event.target.value),
|
|
1146
|
+
"aria-label": "试运行任务 Label",
|
|
1147
|
+
})),
|
|
1148
|
+
react.createElement("label", { className: "tm-field" },
|
|
1149
|
+
react.createElement("span", { className: "tm-label" }, "子任务内容 / 描述(最多 6000 字符)"),
|
|
1150
|
+
react.createElement("textarea", {
|
|
1151
|
+
className: "tm-textarea",
|
|
1152
|
+
maxLength: 6000,
|
|
1153
|
+
value: previewText,
|
|
1154
|
+
disabled: previewRunning || saving,
|
|
1155
|
+
placeholder: "例如:任务详细描述或提示词内容…",
|
|
1156
|
+
onChange: (event) => changePreviewText(event.target.value),
|
|
1157
|
+
"aria-label": "试运行任务内容",
|
|
1158
|
+
})),
|
|
1159
|
+
react.createElement("div", { className: "tm-actions tm-actions-left" },
|
|
1160
|
+
react.createElement("button", {
|
|
1161
|
+
className: "tm-button tm-primary",
|
|
1162
|
+
type: "button",
|
|
1163
|
+
disabled: Boolean(validation) || previewRunning || saving || (!previewLabel.trim() && !previewText.trim()),
|
|
1164
|
+
onClick: runPreview,
|
|
1165
|
+
}, previewRunning ? "试运行中…" : "试运行规则")),
|
|
1166
|
+
previewError ? react.createElement("p", { className: "tm-feedback tm-error", role: "alert" }, previewError) : null,
|
|
1167
|
+
previewResult ? react.createElement("div", { className: "tm-preview-result" },
|
|
1168
|
+
react.createElement("div", null,
|
|
1169
|
+
react.createElement("strong", null, "走向:"),
|
|
1170
|
+
formatNextStep(previewResult.next),
|
|
1171
|
+
" · ",
|
|
1172
|
+
react.createElement("span", null, formatRoutingDetail(previewResult.reason))),
|
|
1173
|
+
previewResult.profile ? react.createElement("div", null,
|
|
1174
|
+
react.createElement("strong", null, "目标模型:"),
|
|
1175
|
+
`${previewResult.profile.name || previewResult.profile.id} (${previewResult.profile.provider}/${previewResult.profile.model}${previewResult.profile.reasoningEffort ? ` · ${previewResult.profile.reasoningEffort}` : ""})`) : null,
|
|
1176
|
+
formatRuleAnalysisSummary(previewResult.ruleAnalysis) ? react.createElement("div", null,
|
|
1177
|
+
react.createElement("strong", null, "规则分析:"),
|
|
1178
|
+
formatRuleAnalysisSummary(previewResult.ruleAnalysis)) : null
|
|
1179
|
+
) : null),
|
|
819
1180
|
react.createElement("section", { className: "tm-card" },
|
|
820
1181
|
react.createElement("div", { className: "tm-card-head" },
|
|
821
1182
|
react.createElement("div", null,
|
|
822
1183
|
react.createElement("h3", { className: "tm-title" }, "Jev 决策"),
|
|
823
1184
|
react.createElement("p", { className: "tm-detail" }, "可选。规则未命中时由 TypeSafe Jev 在已配置任务类型中选择。")),
|
|
824
1185
|
jevStatus.configured
|
|
825
|
-
? react.createElement("span", { className: "tm-badge tm-badge-ok" }, "
|
|
1186
|
+
? react.createElement("span", { className: "tm-badge tm-badge-ok" }, "已配置")
|
|
826
1187
|
: react.createElement("span", { className: "tm-badge tm-badge-warn" }, "未配置")),
|
|
827
1188
|
react.createElement("label", { className: "tm-check" },
|
|
828
1189
|
react.createElement("input", {
|
|
829
|
-
type: "checkbox", checked: jev.enabled, disabled: saving,
|
|
830
|
-
onChange: (event) =>
|
|
1190
|
+
type: "checkbox", checked: jev.enabled, disabled: saving || !writable,
|
|
1191
|
+
onChange: (event) => updateJev((current) => ({ ...current, enabled: event.target.checked })),
|
|
831
1192
|
}),
|
|
832
1193
|
"启用 Jev 决策"),
|
|
833
1194
|
react.createElement("div", { className: "tm-grid" },
|
|
834
1195
|
react.createElement("label", { className: "tm-field" },
|
|
835
1196
|
react.createElement("span", { className: "tm-label" }, "API Key"),
|
|
836
1197
|
react.createElement("input", {
|
|
837
|
-
className: "tm-input", type: "password", value: jevKey, disabled: saving,
|
|
1198
|
+
className: "tm-input", type: "password", value: jevKey, disabled: saving || !writable,
|
|
838
1199
|
placeholder: jevStatus.configured ? "已保存,留空不修改" : "ts-...",
|
|
839
|
-
onChange: (event) =>
|
|
1200
|
+
onChange: (event) => updateJevKey(event.target.value),
|
|
840
1201
|
})),
|
|
841
1202
|
react.createElement("label", { className: "tm-field" },
|
|
842
1203
|
react.createElement("span", { className: "tm-label" }, "Base URL"),
|
|
843
1204
|
react.createElement("input", {
|
|
844
|
-
className: "tm-input", value: jev.baseURL, disabled: saving,
|
|
845
|
-
onChange: (event) =>
|
|
1205
|
+
className: "tm-input", value: jev.baseURL, disabled: saving || !writable,
|
|
1206
|
+
onChange: (event) => updateJev((current) => ({ ...current, baseURL: event.target.value })),
|
|
846
1207
|
}))),
|
|
847
1208
|
react.createElement("div", { className: "tm-grid tm-grid-3" },
|
|
848
1209
|
react.createElement("label", { className: "tm-field" },
|
|
849
1210
|
react.createElement("span", { className: "tm-label" }, "模型"),
|
|
850
1211
|
react.createElement("input", {
|
|
851
|
-
className: "tm-input", value: jev.model, disabled: saving,
|
|
852
|
-
onChange: (event) =>
|
|
1212
|
+
className: "tm-input", value: jev.model, disabled: saving || !writable,
|
|
1213
|
+
onChange: (event) => updateJev((current) => ({ ...current, model: event.target.value })),
|
|
853
1214
|
})),
|
|
854
1215
|
react.createElement("label", { className: "tm-field" },
|
|
855
1216
|
react.createElement("span", { className: "tm-label" }, "最低置信度"),
|
|
856
1217
|
react.createElement("input", {
|
|
857
|
-
className: "tm-input", type: "number", min: 0, max: 1, step: 0.05, value: jev.minConfidence, disabled: saving,
|
|
858
|
-
onChange: (event) =>
|
|
1218
|
+
className: "tm-input", type: "number", min: 0, max: 1, step: 0.05, value: jev.minConfidence, disabled: saving || !writable,
|
|
1219
|
+
onChange: (event) => updateJev((current) => ({ ...current, minConfidence: clampNumber(event.target.value, DEFAULT_JEV.minConfidence, 0, 1) })),
|
|
859
1220
|
})),
|
|
860
1221
|
react.createElement("label", { className: "tm-field" },
|
|
861
1222
|
react.createElement("span", { className: "tm-label" }, "超时(毫秒)"),
|
|
862
1223
|
react.createElement("input", {
|
|
863
|
-
className: "tm-input", type: "number", min: 100, max: 60000, step: 100, value: jev.timeoutMs, disabled: saving,
|
|
864
|
-
onChange: (event) =>
|
|
1224
|
+
className: "tm-input", type: "number", min: 100, max: 60000, step: 100, value: jev.timeoutMs, disabled: saving || !writable,
|
|
1225
|
+
onChange: (event) => updateJev((current) => ({ ...current, timeoutMs: clampNumber(event.target.value, DEFAULT_JEV.timeoutMs, 100, 60000) })),
|
|
865
1226
|
}))),
|
|
866
1227
|
react.createElement("div", { className: "tm-grid" },
|
|
867
1228
|
react.createElement("label", { className: "tm-field" },
|
|
868
1229
|
react.createElement("span", { className: "tm-label" }, "发送上下文字符上限"),
|
|
869
1230
|
react.createElement("input", {
|
|
870
|
-
className: "tm-input", type: "number", min: 500, max: 100000, step: 500, value: jev.maxStateChars, disabled: saving,
|
|
871
|
-
onChange: (event) =>
|
|
1231
|
+
className: "tm-input", type: "number", min: 500, max: 100000, step: 500, value: jev.maxStateChars, disabled: saving || !writable,
|
|
1232
|
+
onChange: (event) => updateJev((current) => ({ ...current, maxStateChars: clampNumber(event.target.value, DEFAULT_JEV.maxStateChars, 500, 100000) })),
|
|
872
1233
|
})),
|
|
873
1234
|
react.createElement("div", { className: "tm-field tm-field-actions" },
|
|
874
1235
|
react.createElement("span", { className: "tm-label" }, "连接"),
|
|
@@ -911,6 +1272,7 @@
|
|
|
911
1272
|
priority: -1,
|
|
912
1273
|
inject: () => ({
|
|
913
1274
|
loadModels: loadChildModels,
|
|
1275
|
+
loadDetails: loadRoutingDetails,
|
|
914
1276
|
openSession: (target) => {
|
|
915
1277
|
if (typeof ctx.uiWorkspace?.openSession === "function") ctx.uiWorkspace.openSession(target);
|
|
916
1278
|
},
|
|
@@ -926,6 +1288,8 @@
|
|
|
926
1288
|
splitKeywords, normalizeKeywords, rowsFromGroups, taskTemplateOf, profileDefaults, normalizeProfile,
|
|
927
1289
|
normalizeProfiles, profilesById, validateRouting, validateProfiles, normalizeJev, validateJev, clampNumber,
|
|
928
1290
|
statusMessage, createSettingsRequest, TaskProfileCard,
|
|
1291
|
+
buildDraftSection, reconcileSettings, formatRoutingDetail, formatMemberTitle,
|
|
1292
|
+
formatNextStep, formatRuleAnalysisSummary,
|
|
929
1293
|
};
|
|
930
1294
|
return module.exports;
|
|
931
1295
|
},
|