dsh-advisor-group 0.1.0 → 0.1.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 +128 -100
- package/README.zh.md +131 -100
- package/lib/client.js +777 -90
- package/lib/index.d.mts +108 -1
- package/lib/index.mjs +1944 -245
- package/package.json +42 -28
package/lib/client.js
CHANGED
|
@@ -290,6 +290,20 @@ function authHeaders() {
|
|
|
290
290
|
const token = advisorGroupToken();
|
|
291
291
|
return token ? { "x-advisor-group-token": token } : {};
|
|
292
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Browser-side twin of `sanitizeAdvisorContent` (host module cannot be
|
|
295
|
+
* imported by the client bundle). The SSE live overlay streams the RAW model
|
|
296
|
+
* output, whose length usually EXCEEDS the sanitized durable message — so the
|
|
297
|
+
* merge below must sanitize whichever content it picks or the raw tool-call
|
|
298
|
+
* markup would keep rendering until the overlay is gone.
|
|
299
|
+
*/
|
|
300
|
+
const CLIENT_RAW_TOOL_CALL_BLOCK = /<(?:[||]{1,2})?(?:tool_calls|DSML)[\s\S]*?<\/(?:[||]{1,2})?(?:tool_calls|DSML)\s*>/g;
|
|
301
|
+
const CLIENT_RAW_TOOL_CALL_TAIL = /<(?:[||]{1,2})?(?:tool_calls|DSML)[\s\S]{0,60}?(?:<invoke|<parameter|<[||]{1,2}DSML)[\s\S]*$/g;
|
|
302
|
+
const CLIENT_EMPTY_BODY_NOTICE = "(顾问未生成正文:输出仅为工具调用描述,请参考其思维链与工具调用记录。)";
|
|
303
|
+
function sanitizeClientContent(content) {
|
|
304
|
+
if (!content.trim()) return content;
|
|
305
|
+
return content.replace(CLIENT_RAW_TOOL_CALL_BLOCK, "").replace(CLIENT_RAW_TOOL_CALL_TAIL, "").trim() || CLIENT_EMPTY_BODY_NOTICE;
|
|
306
|
+
}
|
|
293
307
|
function locationOf(context) {
|
|
294
308
|
return context.start?.location ?? context.matches[0]?.location ?? { kind: "unresolved" };
|
|
295
309
|
}
|
|
@@ -312,7 +326,7 @@ const advisorGroupDefinition = {
|
|
|
312
326
|
id: event.data.sessionId,
|
|
313
327
|
role: "start"
|
|
314
328
|
};
|
|
315
|
-
if (event.type === "advisor-group/message" || event.type === "advisor-group/delta" || event.type === "advisor-group/end") return {
|
|
329
|
+
if (event.type === "advisor-group/message" || event.type === "advisor-group/delta" || event.type === "advisor-group/end" || event.type === "advisor-group/resume") return {
|
|
316
330
|
id: event.data.sessionId,
|
|
317
331
|
role: "update"
|
|
318
332
|
};
|
|
@@ -385,9 +399,13 @@ const advisorGroupDefinition = {
|
|
|
385
399
|
}
|
|
386
400
|
if (match.event.type === "advisor-group/end") return {
|
|
387
401
|
...context.state,
|
|
388
|
-
status: "completed",
|
|
402
|
+
status: match.event.data.summary.stopped ? "cancelled" : "completed",
|
|
389
403
|
summary: match.event.data.summary
|
|
390
404
|
};
|
|
405
|
+
if (match.event.type === "advisor-group/resume") return {
|
|
406
|
+
...context.state,
|
|
407
|
+
status: "running"
|
|
408
|
+
};
|
|
391
409
|
return context.state;
|
|
392
410
|
},
|
|
393
411
|
publication: (match) => match.event.type === "advisor-group/message" || match.event.type === "advisor-group/delta" ? "animation-frame" : "immediate",
|
|
@@ -465,13 +483,6 @@ const headerStyle = {
|
|
|
465
483
|
letterSpacing: 1,
|
|
466
484
|
fontSize: 11
|
|
467
485
|
};
|
|
468
|
-
const questionStyle = {
|
|
469
|
-
color: "#d9f99d",
|
|
470
|
-
fontWeight: 700,
|
|
471
|
-
whiteSpace: "pre-wrap",
|
|
472
|
-
wordBreak: "break-word",
|
|
473
|
-
marginBottom: 6
|
|
474
|
-
};
|
|
475
486
|
const advisorsLineStyle = {
|
|
476
487
|
fontSize: 11,
|
|
477
488
|
color: "#4ade80",
|
|
@@ -507,70 +518,278 @@ const waitingStyle = {
|
|
|
507
518
|
color: "#fbbf24",
|
|
508
519
|
marginTop: 6
|
|
509
520
|
};
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
521
|
+
const noticeStyle = {
|
|
522
|
+
color: "#fda4af",
|
|
523
|
+
marginTop: 6
|
|
524
|
+
};
|
|
525
|
+
/** Internal announcements that must never reach the user (the tool list). */
|
|
526
|
+
const INTERNAL_TOOL_STEP_NAMES = /* @__PURE__ */ new Set(["⚙️ tools", "⚙ tools"]);
|
|
527
|
+
/** True when the tool result represents a failure — the wrapper prefix, or a
|
|
528
|
+
* JSON payload carrying `error`/`detail`/HTTP error code (unconfigured/401
|
|
529
|
+
* tools), robust to nested objects before the error key. */
|
|
530
|
+
function isFailedToolResult(text) {
|
|
531
|
+
if (text.startsWith("(工具执行失败")) return true;
|
|
532
|
+
try {
|
|
533
|
+
const parsed = JSON.parse(text);
|
|
534
|
+
if (parsed && (parsed.error !== void 0 || parsed.detail !== void 0)) return true;
|
|
535
|
+
if (parsed && typeof parsed.code === "number" && parsed.code >= 400 && parsed.code <= 599) return true;
|
|
536
|
+
} catch {}
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Pair call/result tool steps into one `ToolStepView` per invocation, in
|
|
541
|
+
* stream order, and drop internal announcements (「本次可用」).
|
|
542
|
+
*/
|
|
543
|
+
function buildToolStepViews(steps) {
|
|
544
|
+
const out = [];
|
|
545
|
+
let last;
|
|
546
|
+
for (const step of steps ?? []) {
|
|
547
|
+
if (INTERNAL_TOOL_STEP_NAMES.has(step.name)) continue;
|
|
548
|
+
if (step.kind === "call") {
|
|
549
|
+
last = {
|
|
550
|
+
name: step.name,
|
|
551
|
+
input: step.text,
|
|
552
|
+
atMs: step.atMs
|
|
553
|
+
};
|
|
554
|
+
out.push(last);
|
|
555
|
+
} else if (step.kind === "result") {
|
|
556
|
+
if (last && last.output === void 0 && last.input !== void 0) {
|
|
557
|
+
last.output = step.text;
|
|
558
|
+
if (isFailedToolResult(step.text)) last.failed = true;
|
|
559
|
+
} else out.push({
|
|
560
|
+
name: step.name,
|
|
561
|
+
output: step.text,
|
|
562
|
+
atMs: step.atMs,
|
|
563
|
+
...isFailedToolResult(step.text) ? { failed: true } : {}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return out.filter((view) => view.input !== void 0 || view.output !== void 0);
|
|
568
|
+
}
|
|
569
|
+
const stepRowShellStyle = {
|
|
570
|
+
border: "1px solid rgba(34,197,94,0.28)",
|
|
571
|
+
borderRadius: 3,
|
|
572
|
+
margin: "5px 0",
|
|
573
|
+
background: "rgba(34,197,94,0.05)",
|
|
574
|
+
overflow: "hidden"
|
|
575
|
+
};
|
|
576
|
+
const stepRowHeaderStyle = (color) => ({
|
|
577
|
+
width: "100%",
|
|
578
|
+
display: "flex",
|
|
579
|
+
alignItems: "center",
|
|
580
|
+
gap: 8,
|
|
581
|
+
cursor: "pointer",
|
|
582
|
+
background: "transparent",
|
|
583
|
+
border: "none",
|
|
584
|
+
color,
|
|
585
|
+
fontSize: 12,
|
|
586
|
+
textAlign: "left",
|
|
587
|
+
padding: "4px 8px"
|
|
588
|
+
});
|
|
589
|
+
const stepRowBodyStyle = {
|
|
590
|
+
borderTop: "1px dashed rgba(47,158,68,0.45)",
|
|
591
|
+
padding: "5px 8px"
|
|
592
|
+
};
|
|
593
|
+
function StepRow({ title, badges, defaultOpen, color = "#4ade80", body }) {
|
|
594
|
+
const [open, setOpen] = (0, react.useState)(defaultOpen);
|
|
595
|
+
return (0, react.createElement)("div", { style: stepRowShellStyle }, (0, react.createElement)("button", {
|
|
596
|
+
type: "button",
|
|
597
|
+
onClick: () => setOpen((value) => !value),
|
|
598
|
+
style: stepRowHeaderStyle(color)
|
|
599
|
+
}, (0, react.createElement)("span", null, open ? "▾" : "▸"), (0, react.createElement)("span", { style: { fontWeight: 700 } }, title), ...badges ?? []), open ? (0, react.createElement)("div", { style: stepRowBodyStyle }, body) : null);
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Inline thinking annotation: an UNFRAMED block that sits directly above the
|
|
603
|
+
* tool row it led to (each step reads: 💭 思考文本 → ⛭ 工具), with the full
|
|
604
|
+
* segment visible (scrollable) — not a separate bordered card.
|
|
605
|
+
*/
|
|
606
|
+
const thinkingInlineStyle = {
|
|
607
|
+
margin: "4px 0 2px",
|
|
608
|
+
padding: "4px 8px",
|
|
609
|
+
borderLeft: "2px solid rgba(251,191,36,0.45)",
|
|
610
|
+
background: "rgba(217,119,6,0.04)"
|
|
611
|
+
};
|
|
612
|
+
const thinkingTagStyle = {
|
|
613
|
+
color: "#fbbf24",
|
|
614
|
+
fontSize: 11,
|
|
615
|
+
marginBottom: 2,
|
|
616
|
+
fontWeight: 700
|
|
617
|
+
};
|
|
618
|
+
const descriptionBodyStyle = {
|
|
619
|
+
whiteSpace: "pre-wrap",
|
|
620
|
+
wordBreak: "break-word",
|
|
621
|
+
color: "#d9f99d",
|
|
622
|
+
fontSize: 12,
|
|
623
|
+
padding: "2px 6px 4px"
|
|
624
|
+
};
|
|
625
|
+
/**
|
|
626
|
+
* Short "next-action narrative" of a thinking segment — the visible 📋 行动·N
|
|
627
|
+
* block shown directly after the 💭 思考·N panel and before the ⛭ tool row.
|
|
628
|
+
* Standard-agent style: pick the FIRST SHORT sentence that says what the model
|
|
629
|
+
* is about to do (action markers like 我先/接下来/我需要…), else the first
|
|
630
|
+
* non-generic short sentence; hard-cap at ~140 chars. The long internal
|
|
631
|
+
* monologue stays in the thinking panel.
|
|
632
|
+
*/
|
|
633
|
+
function extractActionDescription(segment) {
|
|
634
|
+
const text = segment.trim();
|
|
635
|
+
if (!text) return "";
|
|
636
|
+
const sentences = (text.match(/[^。!?!?]+[。!?!?]?/g) ?? []).map((sentence) => sentence.replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
637
|
+
const cut = (value, max) => value.length > max ? `${value.slice(0, max)}…` : value;
|
|
638
|
+
const ACTION = /(我先|接下来|下一步|下面|准备|打算|我需要|先做|先看|先检查|先核对|先核实|先验证|先确认|先读|先搜索|先检索|先找|计划|步骤|让我先|我会|需要先|先补|先抓|先取|先查|先分析|先整理|先修改|先跟踪)/;
|
|
639
|
+
const GENERIC = /^(让我|我明白|好的|嗯|OK|Okay|我是|我是一位|我的角色|Let me|Sure|The user|用户)/;
|
|
640
|
+
for (const sentence of sentences.slice(0, 5)) if (sentence.length <= 160 && ACTION.test(sentence)) return cut(sentence, 140);
|
|
641
|
+
for (const sentence of sentences.slice(0, 3)) if (sentence.length <= 120 && !GENERIC.test(sentence)) return cut(sentence, 120);
|
|
642
|
+
return cut(sentences[0] ?? text, 100);
|
|
643
|
+
}
|
|
644
|
+
const thinkingBodyStyle = {
|
|
645
|
+
maxHeight: 200,
|
|
646
|
+
overflowY: "auto",
|
|
647
|
+
whiteSpace: "pre-wrap",
|
|
648
|
+
wordBreak: "break-word",
|
|
649
|
+
color: "#fde68a",
|
|
650
|
+
fontSize: 12,
|
|
651
|
+
padding: "4px 6px"
|
|
652
|
+
};
|
|
653
|
+
const toolBodyStyle = {
|
|
654
|
+
fontSize: 12,
|
|
655
|
+
color: "#38bdf8"
|
|
656
|
+
};
|
|
657
|
+
const toolIoTitleStyle = {
|
|
658
|
+
color: "#86efac",
|
|
659
|
+
fontWeight: 700,
|
|
660
|
+
margin: "2px 0 2px"
|
|
661
|
+
};
|
|
662
|
+
const toolIoBodyStyle = {
|
|
663
|
+
whiteSpace: "pre-wrap",
|
|
664
|
+
wordBreak: "break-word",
|
|
665
|
+
color: "var(--dsh-color-muted, #8b90a0)",
|
|
666
|
+
marginBottom: 6
|
|
667
|
+
};
|
|
668
|
+
const badgeStyle = (color) => ({
|
|
669
|
+
color,
|
|
670
|
+
fontSize: 11,
|
|
671
|
+
opacity: .95
|
|
672
|
+
});
|
|
673
|
+
/**
|
|
674
|
+
* Interleave action announcements, thinking segments and tool views in the
|
|
675
|
+
* official DSH block order (text → reasoning → tool-call), then the body.
|
|
676
|
+
* Each round contributes at most one 行动 / 思考 / 工具 item; missing actions
|
|
677
|
+
* are derived from the thinking segment so pre-actionDescriptions logs still
|
|
678
|
+
* render sensibly.
|
|
679
|
+
*/
|
|
680
|
+
function buildStepSequence(actionDescriptions, thinkingSegments, toolViews) {
|
|
681
|
+
const items = [];
|
|
682
|
+
const count = Math.max(actionDescriptions.length, thinkingSegments.length, toolViews.length);
|
|
683
|
+
for (let index = 0; index < count; index += 1) {
|
|
684
|
+
const step = index + 1;
|
|
685
|
+
const described = actionDescriptions[index]?.trim();
|
|
686
|
+
const segment = thinkingSegments[index];
|
|
687
|
+
const action = described || (segment !== void 0 && segment.trim() ? extractActionDescription(segment) : "");
|
|
688
|
+
if (action) items.push({
|
|
689
|
+
kind: "action",
|
|
690
|
+
text: action,
|
|
691
|
+
step
|
|
692
|
+
});
|
|
693
|
+
if (segment !== void 0 && segment.trim()) items.push({
|
|
694
|
+
kind: "thinking",
|
|
695
|
+
segment,
|
|
696
|
+
step
|
|
697
|
+
});
|
|
698
|
+
const view = toolViews[index];
|
|
699
|
+
if (view !== void 0) items.push({
|
|
700
|
+
kind: "tool",
|
|
701
|
+
view
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
items.push({ kind: "body" });
|
|
705
|
+
return items;
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Render one consultation message as a stack of STEP rows (one row per step,
|
|
709
|
+
* appearing in stream order, each independently expandable):
|
|
710
|
+
* 💭 思考·1 → ⛭ 工具×1(输入/输出)→ 💭 思考·2 → ⛭ 工具×2 → … → 📄 正文
|
|
711
|
+
* Main/system messages stay a single collapsible row.
|
|
712
|
+
*/
|
|
713
|
+
function AdvisorSteps({ message }) {
|
|
516
714
|
const isMain = message.role === "main";
|
|
517
715
|
const isSystem = message.role === "system";
|
|
518
|
-
const justify = isMain ? "flex-end" : "flex-start";
|
|
519
|
-
const background = isSystem ? "rgba(217,119,6,0.08)" : isMain ? "rgba(34,197,94,0.14)" : "rgba(34,197,94,0.06)";
|
|
520
|
-
const borderColor = isSystem ? "#b45309" : "#16a34a";
|
|
521
716
|
const label = isMain ? "YOU" : isSystem ? "SYSTEM" : message.advisorName ?? message.advisorId ?? "ADVISOR";
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
background
|
|
533
|
-
} }, (0, react.createElement)("div", { style: {
|
|
534
|
-
fontSize: 11,
|
|
535
|
-
color: isSystem ? "#fcd34d" : "#4ade80",
|
|
536
|
-
marginBottom: 2
|
|
537
|
-
} }, `${label}${message.round ? ` · R${message.round}` : ""}`), hasThinking ? (0, react.createElement)("div", { style: {
|
|
538
|
-
border: "1px dashed #a16207",
|
|
539
|
-
borderRadius: 3,
|
|
540
|
-
marginBottom: 4,
|
|
541
|
-
background: "rgba(217,119,6,0.06)"
|
|
542
|
-
} }, (0, react.createElement)("button", {
|
|
543
|
-
type: "button",
|
|
544
|
-
onClick: () => setThinkingCollapsed((value) => !value),
|
|
545
|
-
style: {
|
|
546
|
-
width: "100%",
|
|
547
|
-
cursor: "pointer",
|
|
548
|
-
background: "transparent",
|
|
549
|
-
border: "none",
|
|
550
|
-
color: "#fbbf24",
|
|
551
|
-
fontSize: 11,
|
|
552
|
-
textAlign: "left",
|
|
553
|
-
padding: "3px 6px"
|
|
554
|
-
}
|
|
555
|
-
}, `💭 ${thinkingCollapsed ? "已思考(点击展开)" : "思维链(点击收起)"} ${thinkingCollapsed ? "▸" : "▾"}`), !thinkingCollapsed ? (0, react.createElement)("div", {
|
|
556
|
-
ref: (el) => {
|
|
557
|
-
thinkingPanelRef.current = el;
|
|
558
|
-
},
|
|
559
|
-
style: {
|
|
560
|
-
maxHeight: 160,
|
|
561
|
-
overflowY: "auto",
|
|
717
|
+
const group = isMain ? "YOU" : isSystem ? "SYSTEM" : `${label} · R${message.round ?? 1}`;
|
|
718
|
+
const truncated = message.truncated;
|
|
719
|
+
const toolViews = buildToolStepViews(message.toolSteps);
|
|
720
|
+
const thinkingSegments = message.thinkingSegments && message.thinkingSegments.length > 0 ? message.thinkingSegments : message.thinking ? [message.thinking] : [];
|
|
721
|
+
const baseColor = isSystem ? "#fcd34d" : "#4ade80";
|
|
722
|
+
if (isMain || isSystem) return (0, react.createElement)(StepRow, {
|
|
723
|
+
title: group,
|
|
724
|
+
defaultOpen: isMain,
|
|
725
|
+
color: baseColor,
|
|
726
|
+
body: (0, react.createElement)("div", { style: {
|
|
562
727
|
whiteSpace: "pre-wrap",
|
|
563
|
-
wordBreak: "break-word"
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
728
|
+
wordBreak: "break-word"
|
|
729
|
+
} }, message.content)
|
|
730
|
+
});
|
|
731
|
+
const sequence = buildStepSequence(message.actionDescriptions ?? [], thinkingSegments, toolViews);
|
|
732
|
+
const live = message;
|
|
733
|
+
const liveToolPhase = live.livePhase === "tool";
|
|
734
|
+
const liveAction = liveToolPhase && live.liveBody?.trim() ? live.liveBody.trim() : void 0;
|
|
735
|
+
const children = [];
|
|
736
|
+
for (const [index, item] of sequence.entries()) {
|
|
737
|
+
if (item.kind === "action") {
|
|
738
|
+
children.push((0, react.createElement)("div", {
|
|
739
|
+
key: `action-${index}`,
|
|
740
|
+
style: thinkingInlineStyle
|
|
741
|
+
}, (0, react.createElement)("div", { style: thinkingTagStyle }, `${group} 📋 行动·${item.step}`), (0, react.createElement)("div", { style: descriptionBodyStyle }, item.text)));
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
if (item.kind === "thinking") {
|
|
745
|
+
children.push((0, react.createElement)(StepRow, {
|
|
746
|
+
key: `think-${index}`,
|
|
747
|
+
title: `${group} 💭 思考·${item.step}`,
|
|
748
|
+
badges: [(0, react.createElement)("span", { style: badgeStyle("#fbbf24") }, `${item.segment.length} 字符`)],
|
|
749
|
+
defaultOpen: false,
|
|
750
|
+
color: "#fbbf24",
|
|
751
|
+
body: (0, react.createElement)("div", { style: thinkingBodyStyle }, item.segment)
|
|
752
|
+
}));
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
if (item.kind === "tool") {
|
|
756
|
+
const view = item.view;
|
|
757
|
+
children.push((0, react.createElement)(StepRow, {
|
|
758
|
+
key: `tool-${index}`,
|
|
759
|
+
title: `${group} ⛭ ${view.name}`,
|
|
760
|
+
badges: [(0, react.createElement)("span", { style: badgeStyle(view.failed ? "#f87171" : "#38bdf8") }, view.output === void 0 ? "调用中…" : view.failed ? "⚠ 失败" : "调用完成")],
|
|
761
|
+
defaultOpen: false,
|
|
762
|
+
color: view.failed ? "#f87171" : "#38bdf8",
|
|
763
|
+
body: (0, react.createElement)("div", null, view.input !== void 0 ? (0, react.createElement)("div", { style: toolBodyStyle }, (0, react.createElement)("div", { style: toolIoTitleStyle }, "输入"), (0, react.createElement)("div", { style: toolIoBodyStyle }, view.input)) : null, view.output !== void 0 ? (0, react.createElement)("div", { style: toolBodyStyle }, (0, react.createElement)("div", { style: toolIoTitleStyle }, "输出"), (0, react.createElement)("div", { style: toolIoBodyStyle }, view.output)) : null)
|
|
764
|
+
}));
|
|
765
|
+
continue;
|
|
568
766
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
767
|
+
if (liveToolPhase) continue;
|
|
768
|
+
children.push((0, react.createElement)(StepRow, {
|
|
769
|
+
key: `body-${index}`,
|
|
770
|
+
title: `${group} 📄 正文`,
|
|
771
|
+
badges: truncated ? [(0, react.createElement)("span", { style: badgeStyle("#f87171") }, truncated.reason === "timeout" ? "⏱ 响应超时截断" : "⚠ 流中断")] : [],
|
|
772
|
+
defaultOpen: true,
|
|
773
|
+
color: "#4ade80",
|
|
774
|
+
body: (0, react.createElement)("div", { style: { wordBreak: "break-word" } }, truncated ? (0, react.createElement)("div", { style: {
|
|
775
|
+
fontSize: 11,
|
|
776
|
+
color: "#f87171",
|
|
777
|
+
border: "1px solid #dc2626",
|
|
778
|
+
borderRadius: 3,
|
|
779
|
+
padding: "2px 6px",
|
|
780
|
+
display: "inline-block",
|
|
781
|
+
marginBottom: 3
|
|
782
|
+
} }, truncated.reason === "timeout" ? "⏱ 响应超时截断 — 思考已收到,正文未完成" : "⚠ 流中断 — 正文可能不完整") : null, renderMarkdown(message.content))
|
|
783
|
+
}));
|
|
784
|
+
}
|
|
785
|
+
if (liveAction !== void 0) children.push((0, react.createElement)("div", {
|
|
786
|
+
key: "live-action",
|
|
787
|
+
style: thinkingInlineStyle
|
|
788
|
+
}, (0, react.createElement)("div", { style: thinkingTagStyle }, `${group} 📋 行动·${toolViews.length + 1}`), (0, react.createElement)("div", { style: descriptionBodyStyle }, liveAction)));
|
|
789
|
+
return (0, react.createElement)("div", null, children);
|
|
573
790
|
}
|
|
791
|
+
/** Auto-hide delay for transient card notices (request failures, resync). */
|
|
792
|
+
const NOTICE_TTL_MS = 6e3;
|
|
574
793
|
function AdvisorGroupNodeView(props) {
|
|
575
794
|
const data = props.node.data;
|
|
576
795
|
const sessionId = data.sessionId;
|
|
@@ -578,10 +797,36 @@ function AdvisorGroupNodeView(props) {
|
|
|
578
797
|
const [contextCollapsed, setContextCollapsed] = (0, react.useState)(true);
|
|
579
798
|
const lastEventIdRef = (0, react.useRef)(0);
|
|
580
799
|
const lastBootIdRef = (0, react.useRef)("");
|
|
800
|
+
const [transientNotice, setTransientNotice] = (0, react.useState)(null);
|
|
801
|
+
const noticeTimerRef = (0, react.useRef)(null);
|
|
802
|
+
const showTransientNotice = (text, kind, ttlMs) => {
|
|
803
|
+
if (noticeTimerRef.current !== null) {
|
|
804
|
+
clearTimeout(noticeTimerRef.current);
|
|
805
|
+
noticeTimerRef.current = null;
|
|
806
|
+
}
|
|
807
|
+
setTransientNotice({
|
|
808
|
+
text,
|
|
809
|
+
kind
|
|
810
|
+
});
|
|
811
|
+
if (ttlMs !== void 0) noticeTimerRef.current = setTimeout(() => {
|
|
812
|
+
noticeTimerRef.current = null;
|
|
813
|
+
setTransientNotice(null);
|
|
814
|
+
}, ttlMs);
|
|
815
|
+
};
|
|
816
|
+
const clearStreamNotice = () => {
|
|
817
|
+
setTransientNotice((prev) => prev && prev.kind === "stream" ? null : prev);
|
|
818
|
+
};
|
|
581
819
|
(0, react.useEffect)(() => {
|
|
582
820
|
if (!sessionId) return;
|
|
583
821
|
const es = new EventSource(`/advisor-group/stream?sessionId=${encodeURIComponent(sessionId)}&lastEventId=${lastEventIdRef.current}&bootId=${encodeURIComponent(lastBootIdRef.current)}&token=${encodeURIComponent(advisorGroupToken())}`);
|
|
822
|
+
es.onopen = () => {
|
|
823
|
+
clearStreamNotice();
|
|
824
|
+
};
|
|
825
|
+
es.onerror = () => {
|
|
826
|
+
showTransientNotice("连接中断,正在重连…", "stream");
|
|
827
|
+
};
|
|
584
828
|
es.onmessage = (event) => {
|
|
829
|
+
clearStreamNotice();
|
|
585
830
|
try {
|
|
586
831
|
const delta = JSON.parse(event.data);
|
|
587
832
|
const bootId = delta.bootId ?? "";
|
|
@@ -593,51 +838,168 @@ function AdvisorGroupNodeView(props) {
|
|
|
593
838
|
const eventId = Number(delta.eventId ?? 0);
|
|
594
839
|
if (eventId > 0 && eventId <= lastEventIdRef.current) return;
|
|
595
840
|
if (eventId > 0) lastEventIdRef.current = eventId;
|
|
841
|
+
const bucket = `${delta.advisorId}::${delta.round ?? 1}`;
|
|
596
842
|
setLive((prev) => {
|
|
597
|
-
const current = prev[
|
|
843
|
+
const current = prev[bucket] ?? {
|
|
598
844
|
content: "",
|
|
599
|
-
thinking: ""
|
|
845
|
+
thinking: "",
|
|
846
|
+
thinkingSegments: [],
|
|
847
|
+
committedLen: 0,
|
|
848
|
+
contentSegments: [],
|
|
849
|
+
committedContentLen: 0,
|
|
850
|
+
phase: "answer",
|
|
851
|
+
finalized: false,
|
|
852
|
+
toolSteps: []
|
|
600
853
|
};
|
|
854
|
+
let thinkingSegments = current.thinkingSegments;
|
|
855
|
+
let committedLen = current.committedLen;
|
|
856
|
+
let contentSegments = current.contentSegments;
|
|
857
|
+
let committedContentLen = current.committedContentLen;
|
|
858
|
+
if (delta.toolStep) {
|
|
859
|
+
const segment = current.thinking.slice(committedLen);
|
|
860
|
+
if (segment.trim()) thinkingSegments = [...current.thinkingSegments, segment];
|
|
861
|
+
committedLen = current.thinking.length;
|
|
862
|
+
const contentSegment = current.content.slice(committedContentLen);
|
|
863
|
+
if (contentSegment.trim()) contentSegments = [...current.contentSegments, contentSegment];
|
|
864
|
+
committedContentLen = current.content.length;
|
|
865
|
+
}
|
|
601
866
|
return {
|
|
602
867
|
...prev,
|
|
603
|
-
[
|
|
868
|
+
[bucket]: {
|
|
604
869
|
content: current.content + (delta.contentDelta ?? ""),
|
|
605
|
-
thinking: current.thinking + (delta.thinkingDelta ?? "")
|
|
870
|
+
thinking: current.thinking + (delta.thinkingDelta ?? ""),
|
|
871
|
+
thinkingSegments,
|
|
872
|
+
committedLen,
|
|
873
|
+
contentSegments,
|
|
874
|
+
committedContentLen,
|
|
875
|
+
phase: delta.phase ?? current.phase,
|
|
876
|
+
finalized: current.finalized || delta.done === true,
|
|
877
|
+
toolSteps: delta.toolStep ? [...current.toolSteps, delta.toolStep] : current.toolSteps
|
|
606
878
|
}
|
|
607
879
|
};
|
|
608
880
|
});
|
|
609
881
|
} catch {}
|
|
610
882
|
};
|
|
611
|
-
es.addEventListener("resync", () => {
|
|
883
|
+
es.addEventListener("resync", (event) => {
|
|
612
884
|
lastEventIdRef.current = 0;
|
|
613
885
|
setLive({});
|
|
886
|
+
let reason = "";
|
|
887
|
+
try {
|
|
888
|
+
reason = JSON.parse(event.data).reason ?? "";
|
|
889
|
+
} catch {}
|
|
890
|
+
showTransientNotice(reason === "restart" ? "服务端已重启,正在补齐对话内容…" : reason === "gap" ? "正在补齐断开期间的内容…" : "正在补齐对话内容…", "stream", NOTICE_TTL_MS);
|
|
614
891
|
});
|
|
615
|
-
return () =>
|
|
892
|
+
return () => {
|
|
893
|
+
es.close();
|
|
894
|
+
if (noticeTimerRef.current !== null) {
|
|
895
|
+
clearTimeout(noticeTimerRef.current);
|
|
896
|
+
noticeTimerRef.current = null;
|
|
897
|
+
}
|
|
898
|
+
};
|
|
616
899
|
}, [sessionId]);
|
|
617
900
|
const mergedMessages = data.messages.map((message) => {
|
|
618
901
|
if (message.role !== "advisor") return message;
|
|
619
902
|
const advisorId = message.advisorId ?? "";
|
|
620
|
-
const streamed = live[advisorId];
|
|
903
|
+
const streamed = live[`${advisorId}::${message.round ?? 1}`];
|
|
621
904
|
if (!streamed) return message;
|
|
622
|
-
|
|
905
|
+
if (streamed.finalized) return message;
|
|
906
|
+
const liveBody = sanitizeClientContent(streamed.content.slice(streamed.committedContentLen));
|
|
907
|
+
const content = liveBody.trim() ? liveBody : sanitizeClientContent(message.content);
|
|
623
908
|
const thinking = (streamed.thinking?.length ?? 0) > (message.thinking?.length ?? 0) ? streamed.thinking : message.thinking;
|
|
909
|
+
const toolSteps = (streamed.toolSteps?.length ?? 0) > (message.toolSteps?.length ?? 0) ? streamed.toolSteps : message.toolSteps ?? [];
|
|
910
|
+
const liveSegments = streamed.thinkingSegments?.length ? streamed.thinkingSegments : [];
|
|
911
|
+
const baseSegments = message.thinkingSegments?.length ? message.thinkingSegments : message.thinking ? [message.thinking] : [];
|
|
912
|
+
const thinkingSegments = liveSegments.length ? liveSegments : baseSegments;
|
|
913
|
+
const liveActions = streamed.contentSegments?.length ? streamed.contentSegments.map((segment) => sanitizeClientContent(segment)) : [];
|
|
914
|
+
const baseActions = message.actionDescriptions?.length ? message.actionDescriptions : [];
|
|
915
|
+
const actionDescriptions = liveActions.length ? liveActions : baseActions;
|
|
624
916
|
return {
|
|
625
917
|
...message,
|
|
626
918
|
content,
|
|
627
|
-
thinking
|
|
919
|
+
thinking,
|
|
920
|
+
thinkingSegments,
|
|
921
|
+
toolSteps,
|
|
922
|
+
actionDescriptions,
|
|
923
|
+
...streamed.phase === "tool" ? {
|
|
924
|
+
liveBody,
|
|
925
|
+
livePhase: streamed.phase
|
|
926
|
+
} : {}
|
|
628
927
|
};
|
|
629
928
|
});
|
|
630
929
|
const shortId = data.sessionId.length > 8 ? data.sessionId.slice(0, 8) : data.sessionId;
|
|
631
|
-
const title = data.status === "completed" ? "ADVISOR GROUP · DONE" : "ADVISOR GROUP · LIVE";
|
|
930
|
+
const title = data.status === "completed" ? "ADVISOR GROUP · DONE" : data.status === "cancelled" ? "ADVISOR GROUP · STOPPED" : "ADVISOR GROUP · LIVE";
|
|
632
931
|
return (0, react.createElement)("div", {
|
|
633
932
|
style: rootStyle,
|
|
634
933
|
role: "log",
|
|
635
934
|
"aria-live": "polite"
|
|
636
|
-
}, (0, react.createElement)("div", { style: scanlineStyle }), (0, react.createElement)("div", { style: headerStyle }, (0, react.createElement)("span", { key: "title" }, title), (0, react.createElement)("span", {
|
|
935
|
+
}, (0, react.createElement)("div", { style: scanlineStyle }), (0, react.createElement)("div", { style: headerStyle }, (0, react.createElement)("span", { key: "title" }, title), (0, react.createElement)("span", {
|
|
936
|
+
key: "stop",
|
|
937
|
+
style: {
|
|
938
|
+
display: "flex",
|
|
939
|
+
alignItems: "center",
|
|
940
|
+
gap: 8
|
|
941
|
+
}
|
|
942
|
+
}, data.status === "running" ? (0, react.createElement)("button", {
|
|
943
|
+
type: "button",
|
|
944
|
+
onClick: () => {
|
|
945
|
+
(async () => {
|
|
946
|
+
try {
|
|
947
|
+
const body = await (await fetch("/advisor-group/stop", {
|
|
948
|
+
method: "POST",
|
|
949
|
+
headers: {
|
|
950
|
+
"content-type": "application/json",
|
|
951
|
+
...authHeaders()
|
|
952
|
+
},
|
|
953
|
+
body: JSON.stringify({ sessionId })
|
|
954
|
+
})).json();
|
|
955
|
+
if (body.ok === false) showTransientNotice(body.error ?? "请求未被服务端接受,请稍后重试。", "action", NOTICE_TTL_MS);
|
|
956
|
+
} catch {
|
|
957
|
+
showTransientNotice("网络异常,请稍后重试。", "action", NOTICE_TTL_MS);
|
|
958
|
+
}
|
|
959
|
+
})();
|
|
960
|
+
},
|
|
961
|
+
style: {
|
|
962
|
+
cursor: "pointer",
|
|
963
|
+
border: "1px solid #e11d48",
|
|
964
|
+
background: "transparent",
|
|
965
|
+
color: "#fda4af",
|
|
966
|
+
borderRadius: 4,
|
|
967
|
+
padding: "2px 8px",
|
|
968
|
+
fontSize: 11
|
|
969
|
+
}
|
|
970
|
+
}, "⏹ 停止") : data.status === "cancelled" ? (0, react.createElement)("button", {
|
|
971
|
+
type: "button",
|
|
972
|
+
onClick: () => {
|
|
973
|
+
(async () => {
|
|
974
|
+
try {
|
|
975
|
+
const body = await (await fetch("/advisor-group/resume", {
|
|
976
|
+
method: "POST",
|
|
977
|
+
headers: {
|
|
978
|
+
"content-type": "application/json",
|
|
979
|
+
...authHeaders()
|
|
980
|
+
},
|
|
981
|
+
body: JSON.stringify({ sessionId })
|
|
982
|
+
})).json();
|
|
983
|
+
if (body.ok === false) showTransientNotice(body.error ?? "请求未被服务端接受,请稍后重试。", "action", NOTICE_TTL_MS);
|
|
984
|
+
} catch {
|
|
985
|
+
showTransientNotice("网络异常,请稍后重试。", "action", NOTICE_TTL_MS);
|
|
986
|
+
}
|
|
987
|
+
})();
|
|
988
|
+
},
|
|
989
|
+
style: {
|
|
990
|
+
cursor: "pointer",
|
|
991
|
+
border: "1px solid #22c55e",
|
|
992
|
+
background: "transparent",
|
|
993
|
+
color: "#86efac",
|
|
994
|
+
borderRadius: 4,
|
|
995
|
+
padding: "2px 8px",
|
|
996
|
+
fontSize: 11
|
|
997
|
+
}
|
|
998
|
+
}, "▶ 继续聊天") : null, (0, react.createElement)("span", { key: "id" }, `#${shortId}`))), data.context ? (0, react.createElement)("div", { style: contextBoxStyle }, (0, react.createElement)("button", {
|
|
637
999
|
type: "button",
|
|
638
1000
|
onClick: () => setContextCollapsed((value) => !value),
|
|
639
1001
|
style: contextToggleStyle
|
|
640
|
-
}, `📋 项目背景 ${contextCollapsed ? "▸" : "▾"}`), !contextCollapsed ? (0, react.createElement)("div", { style: contextBodyStyle }, data.context) : null) : null, (0, react.createElement)("div", { style: advisorsLineStyle }, `ADVISORS: ${data.advisors.map((a) => a.avatar ? `${a.avatar} ${a.name}` : a.name).join(" · ")}`), mergedMessages.map((message, index) => (0, react.createElement)(
|
|
1002
|
+
}, `📋 项目背景 ${contextCollapsed ? "▸" : "▾"}`), !contextCollapsed ? (0, react.createElement)("div", { style: contextBodyStyle }, data.context) : null) : null, (0, react.createElement)("div", { style: advisorsLineStyle }, `ADVISORS: ${data.advisors.map((a) => a.avatar ? `${a.avatar} ${a.name}` : a.name).join(" · ")}`), mergedMessages.map((message, index) => (0, react.createElement)(AdvisorSteps, {
|
|
641
1003
|
key: index,
|
|
642
1004
|
message
|
|
643
1005
|
})), data.status === "completed" && data.summary?.conclusion ? (0, react.createElement)("div", { style: {
|
|
@@ -648,7 +1010,32 @@ function AdvisorGroupNodeView(props) {
|
|
|
648
1010
|
color: "#86efac",
|
|
649
1011
|
fontWeight: 700,
|
|
650
1012
|
marginBottom: 4
|
|
651
|
-
} }, "📌 综合结论"), (0, react.createElement)("div", null, renderMarkdown(data.summary.conclusion))) : null, data.status === "
|
|
1013
|
+
} }, "📌 综合结论"), (0, react.createElement)("div", null, renderMarkdown(data.summary.conclusion))) : null, data.status === "completed" && (data.summary?.riskNotes ?? []).length > 0 ? (0, react.createElement)("div", { style: {
|
|
1014
|
+
borderTop: "1px dashed #2f9e44",
|
|
1015
|
+
marginTop: 8,
|
|
1016
|
+
paddingTop: 8
|
|
1017
|
+
} }, (data.summary?.riskNotes ?? []).map((note, index) => (0, react.createElement)("div", {
|
|
1018
|
+
key: index,
|
|
1019
|
+
style: {
|
|
1020
|
+
color: "#fbbf24",
|
|
1021
|
+
fontSize: 12,
|
|
1022
|
+
marginTop: 2
|
|
1023
|
+
}
|
|
1024
|
+
}, note))) : null, data.status === "cancelled" ? (0, react.createElement)("div", { style: {
|
|
1025
|
+
borderTop: "1px dashed #2f9e44",
|
|
1026
|
+
marginTop: 8,
|
|
1027
|
+
paddingTop: 8
|
|
1028
|
+
} }, (0, react.createElement)("div", { style: {
|
|
1029
|
+
color: "#fde68a",
|
|
1030
|
+
marginBottom: 4
|
|
1031
|
+
} }, "本次讨论已停止;点 ▶ 继续聊天 可从断点续跑。"), (data.summary?.riskNotes ?? []).map((note, index) => (0, react.createElement)("div", {
|
|
1032
|
+
key: index,
|
|
1033
|
+
style: {
|
|
1034
|
+
color: "#fbbf24",
|
|
1035
|
+
fontSize: 12,
|
|
1036
|
+
marginTop: 2
|
|
1037
|
+
}
|
|
1038
|
+
}, note))) : null, data.status === "running" ? (0, react.createElement)("div", { style: waitingStyle }, "▊ AWAITING RESPONSES…") : null, transientNotice ? (0, react.createElement)("div", { style: noticeStyle }, transientNotice.text) : null);
|
|
652
1039
|
}
|
|
653
1040
|
const settingsRootStyle = {
|
|
654
1041
|
fontFamily: "'Inter', 'Segoe UI', system-ui, sans-serif",
|
|
@@ -815,6 +1202,21 @@ const AVATAR_OPTIONS = [
|
|
|
815
1202
|
"🚀",
|
|
816
1203
|
"🛡️"
|
|
817
1204
|
];
|
|
1205
|
+
function defaultJevConfig() {
|
|
1206
|
+
return {
|
|
1207
|
+
enabled: false,
|
|
1208
|
+
provider: "typesafe",
|
|
1209
|
+
model: "jev-latest",
|
|
1210
|
+
baseURL: "",
|
|
1211
|
+
timeoutMs: 1e4,
|
|
1212
|
+
confidenceThreshold: .6,
|
|
1213
|
+
useEnglishState: false
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
const JEV_DEFAULT_ENDPOINTS = {
|
|
1217
|
+
typesafe: "https://api.typesafe.ai/v1/systemone",
|
|
1218
|
+
openrouter: "https://openrouter.ai/api/alpha/decisions"
|
|
1219
|
+
};
|
|
818
1220
|
function AdvisorGroupSettingsTab() {
|
|
819
1221
|
const [config, setConfig] = (0, react.useState)(null);
|
|
820
1222
|
const [providers, setProviders] = (0, react.useState)([]);
|
|
@@ -963,6 +1365,10 @@ function AdvisorGroupSettingsTab() {
|
|
|
963
1365
|
try {
|
|
964
1366
|
const configPayload = {
|
|
965
1367
|
...config,
|
|
1368
|
+
trigger: {
|
|
1369
|
+
...config.trigger,
|
|
1370
|
+
jev: config.trigger.jev ? (({ apiKeyMeta: _meta, ...rest }) => rest)(config.trigger.jev) : defaultJevConfig()
|
|
1371
|
+
},
|
|
966
1372
|
advisors: config.advisors.map(({ apiKeyMetaByProvider: _meta, ...rest }) => rest)
|
|
967
1373
|
};
|
|
968
1374
|
const data = await (await fetch("/advisor-group/config", {
|
|
@@ -1003,11 +1409,43 @@ function AdvisorGroupSettingsTab() {
|
|
|
1003
1409
|
...config,
|
|
1004
1410
|
enabled: e.target.checked
|
|
1005
1411
|
})
|
|
1006
|
-
}), label("启用顾问群")),
|
|
1007
|
-
|
|
1412
|
+
}), label("启用顾问群")), (0, react.createElement)("div", { style: {
|
|
1413
|
+
display: "flex",
|
|
1414
|
+
alignItems: "center",
|
|
1415
|
+
gap: 8,
|
|
1416
|
+
marginTop: 4
|
|
1417
|
+
} }, (0, react.createElement)("input", {
|
|
1418
|
+
type: "checkbox",
|
|
1419
|
+
checked: config.quota.enabled,
|
|
1420
|
+
onChange: (e) => setConfig({
|
|
1421
|
+
...config,
|
|
1422
|
+
quota: {
|
|
1423
|
+
...config.quota,
|
|
1424
|
+
enabled: e.target.checked
|
|
1425
|
+
}
|
|
1426
|
+
})
|
|
1427
|
+
}), label("启用每日咨询上限(成本安全阀)")), (0, react.createElement)("div", { style: { marginTop: 6 } }, label(`每日上限次数(${config.quota.enabled ? "生效中" : "已关闭,不拦截"})`), (0, react.createElement)("input", {
|
|
1428
|
+
type: "number",
|
|
1429
|
+
min: 1,
|
|
1430
|
+
max: 1e5,
|
|
1431
|
+
value: String(config.quota.maxPerDay),
|
|
1432
|
+
disabled: !config.quota.enabled,
|
|
1433
|
+
onChange: (e) => setConfig({
|
|
1434
|
+
...config,
|
|
1435
|
+
quota: {
|
|
1436
|
+
...config.quota,
|
|
1437
|
+
maxPerDay: Math.max(1, Number(e.target.value))
|
|
1438
|
+
}
|
|
1439
|
+
}),
|
|
1440
|
+
style: {
|
|
1441
|
+
...settingsInputStyle,
|
|
1442
|
+
width: 120,
|
|
1443
|
+
marginTop: 4
|
|
1444
|
+
}
|
|
1445
|
+
}))), (0, react.createElement)("div", { style: settingsSectionStyle }, dailyGuard ? (0, react.createElement)("div", { style: {
|
|
1008
1446
|
fontSize: 12,
|
|
1009
|
-
color: dailyGuard.remaining > 0 ? "var(--dsh-color-muted, #8b90a0)" : "#e11d48"
|
|
1010
|
-
} }, dailyGuard.remaining > 0 ? `今日剩余咨询次数:${dailyGuard.remaining} / ${dailyGuard.limit}(UTC 日切)` : `今日咨询次数已用尽(${dailyGuard.used} / ${dailyGuard.limit}),将于 UTC 日切后恢复。`) : null), (0, react.createElement)("div", { style: settingsSectionStyle }, (0, react.createElement)("div", { style: {
|
|
1447
|
+
color: !dailyGuard.enabled || dailyGuard.remaining > 0 ? "var(--dsh-color-muted, #8b90a0)" : "#e11d48"
|
|
1448
|
+
} }, dailyGuard.enabled ? dailyGuard.remaining > 0 ? `今日剩余咨询次数:${dailyGuard.remaining} / ${dailyGuard.limit}(UTC 日切)` : `今日咨询次数已用尽(${dailyGuard.used} / ${dailyGuard.limit}),将于 UTC 日切后恢复。` : `今日已咨询 ${dailyGuard.used} 次(每日上限已关闭)。`) : null), (0, react.createElement)("div", { style: settingsSectionStyle }, (0, react.createElement)("div", { style: {
|
|
1011
1449
|
display: "grid",
|
|
1012
1450
|
gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
|
|
1013
1451
|
gap: 8
|
|
@@ -1040,7 +1478,7 @@ function AdvisorGroupSettingsTab() {
|
|
|
1040
1478
|
min: 1e3,
|
|
1041
1479
|
max: 6e5,
|
|
1042
1480
|
step: 1e3,
|
|
1043
|
-
value: String(config.discussion.advisorTimeoutMs ??
|
|
1481
|
+
value: String(config.discussion.advisorTimeoutMs ?? 6e5),
|
|
1044
1482
|
onChange: (e) => setConfig({
|
|
1045
1483
|
...config,
|
|
1046
1484
|
discussion: {
|
|
@@ -1049,6 +1487,20 @@ function AdvisorGroupSettingsTab() {
|
|
|
1049
1487
|
}
|
|
1050
1488
|
}),
|
|
1051
1489
|
style: settingsInputStyle
|
|
1490
|
+
})), (0, react.createElement)("div", null, label("驱动模型生成超时(毫秒)"), (0, react.createElement)("input", {
|
|
1491
|
+
type: "number",
|
|
1492
|
+
min: 1e3,
|
|
1493
|
+
max: 12e5,
|
|
1494
|
+
step: 1e3,
|
|
1495
|
+
value: String(config.discussion.driverTimeoutMs ?? 6e5),
|
|
1496
|
+
onChange: (e) => setConfig({
|
|
1497
|
+
...config,
|
|
1498
|
+
discussion: {
|
|
1499
|
+
...config.discussion,
|
|
1500
|
+
driverTimeoutMs: Math.max(1e3, Number(e.target.value))
|
|
1501
|
+
}
|
|
1502
|
+
}),
|
|
1503
|
+
style: settingsInputStyle
|
|
1052
1504
|
}))), (0, react.createElement)("div", { style: { marginTop: 8 } }, label("置信度阈值(0-1,低于此值升级顾问群)"), (0, react.createElement)("input", {
|
|
1053
1505
|
type: "number",
|
|
1054
1506
|
min: 0,
|
|
@@ -1063,7 +1515,188 @@ function AdvisorGroupSettingsTab() {
|
|
|
1063
1515
|
}
|
|
1064
1516
|
}),
|
|
1065
1517
|
style: settingsInputStyle
|
|
1066
|
-
}))), (0, react.createElement)("div", { style: settingsSectionStyle }, (0, react.createElement)("div", { style: {
|
|
1518
|
+
}))), (0, react.createElement)("div", { style: settingsSectionStyle }, (0, react.createElement)("strong", null, "Jev 前置分类"), (0, react.createElement)("div", { style: {
|
|
1519
|
+
fontSize: 12,
|
|
1520
|
+
color: "var(--dsh-color-muted, #8b90a0)",
|
|
1521
|
+
margin: "4px 0 8px"
|
|
1522
|
+
} }, "启用后由 Jev 判断是否需要顾问群;未配置或调用失败时自动使用本地分类器。"), (0, react.createElement)("div", { style: {
|
|
1523
|
+
display: "flex",
|
|
1524
|
+
alignItems: "center",
|
|
1525
|
+
gap: 8,
|
|
1526
|
+
marginBottom: 8
|
|
1527
|
+
} }, (0, react.createElement)("input", {
|
|
1528
|
+
type: "checkbox",
|
|
1529
|
+
checked: config.trigger.jev?.enabled ?? false,
|
|
1530
|
+
onChange: (e) => setConfig({
|
|
1531
|
+
...config,
|
|
1532
|
+
trigger: {
|
|
1533
|
+
...config.trigger,
|
|
1534
|
+
jev: {
|
|
1535
|
+
...config.trigger.jev ?? defaultJevConfig(),
|
|
1536
|
+
enabled: e.target.checked
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
})
|
|
1540
|
+
}), label("启用 Jev")), (0, react.createElement)("div", { style: {
|
|
1541
|
+
display: "grid",
|
|
1542
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
|
|
1543
|
+
gap: 8
|
|
1544
|
+
} }, (0, react.createElement)("div", null, label("Jev 渠道"), (0, react.createElement)("select", {
|
|
1545
|
+
value: config.trigger.jev?.provider ?? "typesafe",
|
|
1546
|
+
onChange: (e) => {
|
|
1547
|
+
const current = config.trigger.jev ?? defaultJevConfig();
|
|
1548
|
+
const previousProvider = current.provider ?? "typesafe";
|
|
1549
|
+
const currentURL = current.baseURL ?? "";
|
|
1550
|
+
const usesDefault = !currentURL || currentURL === JEV_DEFAULT_ENDPOINTS[previousProvider];
|
|
1551
|
+
setConfig({
|
|
1552
|
+
...config,
|
|
1553
|
+
trigger: {
|
|
1554
|
+
...config.trigger,
|
|
1555
|
+
jev: {
|
|
1556
|
+
...current,
|
|
1557
|
+
provider: e.target.value,
|
|
1558
|
+
...usesDefault ? { baseURL: JEV_DEFAULT_ENDPOINTS[e.target.value] } : {}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
});
|
|
1562
|
+
},
|
|
1563
|
+
style: settingsInputStyle
|
|
1564
|
+
}, (0, react.createElement)("option", { value: "typesafe" }, "TypeSafe 官方 API"), (0, react.createElement)("option", { value: "openrouter" }, "OpenRouter Decisions API"))), (0, react.createElement)("div", null, label("Jev 模型"), (0, react.createElement)("input", {
|
|
1565
|
+
value: config.trigger.jev?.model ?? "jev-latest",
|
|
1566
|
+
onChange: (e) => setConfig({
|
|
1567
|
+
...config,
|
|
1568
|
+
trigger: {
|
|
1569
|
+
...config.trigger,
|
|
1570
|
+
jev: {
|
|
1571
|
+
...config.trigger.jev ?? defaultJevConfig(),
|
|
1572
|
+
model: e.target.value
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
}),
|
|
1576
|
+
style: settingsInputStyle
|
|
1577
|
+
})), (0, react.createElement)("div", null, label("判断阈值(0-1)"), (0, react.createElement)("input", {
|
|
1578
|
+
type: "number",
|
|
1579
|
+
min: 0,
|
|
1580
|
+
max: 1,
|
|
1581
|
+
step: .05,
|
|
1582
|
+
value: String(config.trigger.jev?.confidenceThreshold ?? .6),
|
|
1583
|
+
onChange: (e) => setConfig({
|
|
1584
|
+
...config,
|
|
1585
|
+
trigger: {
|
|
1586
|
+
...config.trigger,
|
|
1587
|
+
jev: {
|
|
1588
|
+
...config.trigger.jev ?? defaultJevConfig(),
|
|
1589
|
+
confidenceThreshold: Number(e.target.value)
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
}),
|
|
1593
|
+
style: settingsInputStyle
|
|
1594
|
+
})), (0, react.createElement)("div", null, label("高风险阈值(0-1,空=跟随判断阈值)"), (0, react.createElement)("input", {
|
|
1595
|
+
type: "number",
|
|
1596
|
+
min: 0,
|
|
1597
|
+
max: 1,
|
|
1598
|
+
step: .05,
|
|
1599
|
+
placeholder: String(config.trigger.jev?.confidenceThreshold ?? .6),
|
|
1600
|
+
value: config.trigger.jev?.highRiskThreshold !== void 0 ? String(config.trigger.jev.highRiskThreshold) : "",
|
|
1601
|
+
onChange: (e) => setConfig({
|
|
1602
|
+
...config,
|
|
1603
|
+
trigger: {
|
|
1604
|
+
...config.trigger,
|
|
1605
|
+
jev: {
|
|
1606
|
+
...config.trigger.jev ?? defaultJevConfig(),
|
|
1607
|
+
highRiskThreshold: e.target.value === "" ? void 0 : Number(e.target.value)
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
}),
|
|
1611
|
+
style: settingsInputStyle
|
|
1612
|
+
})), (0, react.createElement)("div", null, label("请求超时(毫秒)"), (0, react.createElement)("input", {
|
|
1613
|
+
type: "number",
|
|
1614
|
+
min: 1e3,
|
|
1615
|
+
max: 6e4,
|
|
1616
|
+
step: 1e3,
|
|
1617
|
+
value: String(config.trigger.jev?.timeoutMs ?? 1e4),
|
|
1618
|
+
onChange: (e) => setConfig({
|
|
1619
|
+
...config,
|
|
1620
|
+
trigger: {
|
|
1621
|
+
...config.trigger,
|
|
1622
|
+
jev: {
|
|
1623
|
+
...config.trigger.jev ?? defaultJevConfig(),
|
|
1624
|
+
timeoutMs: Math.max(1e3, Number(e.target.value))
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
}),
|
|
1628
|
+
style: settingsInputStyle
|
|
1629
|
+
}))), (0, react.createElement)("div", { style: { marginTop: 8 } }, label("API 地址(留空使用渠道默认地址)"), (0, react.createElement)("input", {
|
|
1630
|
+
value: config.trigger.jev?.baseURL || JEV_DEFAULT_ENDPOINTS[config.trigger.jev?.provider ?? "typesafe"],
|
|
1631
|
+
placeholder: JEV_DEFAULT_ENDPOINTS[config.trigger.jev?.provider ?? "typesafe"],
|
|
1632
|
+
onChange: (e) => setConfig({
|
|
1633
|
+
...config,
|
|
1634
|
+
trigger: {
|
|
1635
|
+
...config.trigger,
|
|
1636
|
+
jev: {
|
|
1637
|
+
...config.trigger.jev ?? defaultJevConfig(),
|
|
1638
|
+
baseURL: e.target.value
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
}),
|
|
1642
|
+
style: settingsInputStyle
|
|
1643
|
+
})), (0, react.createElement)("div", { style: {
|
|
1644
|
+
display: "grid",
|
|
1645
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
|
|
1646
|
+
gap: 8,
|
|
1647
|
+
marginTop: 8
|
|
1648
|
+
} }, (0, react.createElement)("div", null, label("Jev API Key"), (0, react.createElement)("input", {
|
|
1649
|
+
type: "password",
|
|
1650
|
+
placeholder: config.trigger.jev?.apiKeyMeta?.configured ? `已配置 ••••${config.trigger.jev.apiKeyMeta.last4 ?? ""}` : "输入 API Key",
|
|
1651
|
+
value: config.trigger.jev?.apiKey ?? "",
|
|
1652
|
+
onChange: (e) => setConfig({
|
|
1653
|
+
...config,
|
|
1654
|
+
trigger: {
|
|
1655
|
+
...config.trigger,
|
|
1656
|
+
jev: {
|
|
1657
|
+
...config.trigger.jev ?? defaultJevConfig(),
|
|
1658
|
+
apiKey: e.target.value
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
}),
|
|
1662
|
+
style: settingsInputStyle
|
|
1663
|
+
})), (0, react.createElement)("div", null, label("API Key 环境变量名(可选)"), (0, react.createElement)("input", {
|
|
1664
|
+
placeholder: "TYPESAFE_API_KEY 或 OPENROUTER_API_KEY",
|
|
1665
|
+
value: config.trigger.jev?.apiKeyEnv ?? "",
|
|
1666
|
+
onChange: (e) => setConfig({
|
|
1667
|
+
...config,
|
|
1668
|
+
trigger: {
|
|
1669
|
+
...config.trigger,
|
|
1670
|
+
jev: {
|
|
1671
|
+
...config.trigger.jev ?? defaultJevConfig(),
|
|
1672
|
+
apiKeyEnv: e.target.value
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
}),
|
|
1676
|
+
style: settingsInputStyle
|
|
1677
|
+
}))), (0, react.createElement)("div", { style: {
|
|
1678
|
+
display: "flex",
|
|
1679
|
+
alignItems: "center",
|
|
1680
|
+
gap: 8,
|
|
1681
|
+
marginTop: 8
|
|
1682
|
+
} }, (0, react.createElement)("input", {
|
|
1683
|
+
type: "checkbox",
|
|
1684
|
+
checked: config.trigger.jev?.useEnglishState ?? false,
|
|
1685
|
+
onChange: (e) => setConfig({
|
|
1686
|
+
...config,
|
|
1687
|
+
trigger: {
|
|
1688
|
+
...config.trigger,
|
|
1689
|
+
jev: {
|
|
1690
|
+
...config.trigger.jev ?? defaultJevConfig(),
|
|
1691
|
+
useEnglishState: e.target.checked
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
})
|
|
1695
|
+
}), label("Jev 英文判定(questionEn 概要)")), (0, react.createElement)("div", { style: {
|
|
1696
|
+
fontSize: 12,
|
|
1697
|
+
color: "var(--dsh-color-muted, #8b90a0)",
|
|
1698
|
+
margin: "4px 0 0"
|
|
1699
|
+
} }, "开启后,主模型提供的英文概要(questionEn)会作为 Jev 的判定输入;展示问句、会话记录与重复检测仍用中文。默认关闭。")), (0, react.createElement)("div", { style: settingsSectionStyle }, (0, react.createElement)("div", { style: {
|
|
1067
1700
|
display: "flex",
|
|
1068
1701
|
alignItems: "center",
|
|
1069
1702
|
gap: 8
|
|
@@ -1364,7 +1997,55 @@ function AdvisorGroupSettingsTab() {
|
|
|
1364
1997
|
marginTop: 4,
|
|
1365
1998
|
resize: "vertical"
|
|
1366
1999
|
}
|
|
1367
|
-
}), (
|
|
2000
|
+
}), (() => {
|
|
2001
|
+
const supportsTools = !providers.some((p) => p.id === advisor.provider && p.kind === "llm") || Boolean(advisor.baseURL || advisor.apiKeyEnv);
|
|
2002
|
+
const globalDefault = config.discussion.advisorTools ?? "readonly";
|
|
2003
|
+
const mode = advisor.tools ?? globalDefault;
|
|
2004
|
+
const modeLabel = {
|
|
2005
|
+
readonly: "只读工具",
|
|
2006
|
+
all: "全部会话工具(含可写,慎用)",
|
|
2007
|
+
off: "关闭"
|
|
2008
|
+
};
|
|
2009
|
+
const toolPromptNote = "工具启用时,系统提示词会自动追加:『对于你不了解、不熟悉的内容,尤其是项目背景、代码细节、仓库状态等不在知识范围内的情况,请优先使用联网工具(如 web_search、web_fetch)搜索核实后再作答,不要编造未核实的事实。』";
|
|
2010
|
+
if (supportsTools) return [
|
|
2011
|
+
label(`顾问工具调用范围(未单独设置时跟随全局默认:${modeLabel[globalDefault] ?? globalDefault})`),
|
|
2012
|
+
(0, react.createElement)("select", {
|
|
2013
|
+
"aria-label": "顾问工具调用范围",
|
|
2014
|
+
value: mode,
|
|
2015
|
+
onChange: (e) => updateAdvisor(index, { tools: e.target.value }),
|
|
2016
|
+
style: settingsInputStyle
|
|
2017
|
+
}, (0, react.createElement)("option", { value: "readonly" }, "只读工具"), (0, react.createElement)("option", { value: "all" }, "全部会话工具(含可写,慎用)"), (0, react.createElement)("option", { value: "off" }, "关闭")),
|
|
2018
|
+
mode === "off" ? null : (0, react.createElement)("div", { style: {
|
|
2019
|
+
fontSize: 12,
|
|
2020
|
+
color: "var(--dsh-color-muted, #8b90a0)",
|
|
2021
|
+
border: "1px dashed var(--dsh-color-border, #3a3f4b)",
|
|
2022
|
+
borderRadius: 4,
|
|
2023
|
+
padding: "4px 6px",
|
|
2024
|
+
marginTop: 4
|
|
2025
|
+
} }, toolPromptNote)
|
|
2026
|
+
];
|
|
2027
|
+
return [label("顾问工具调用范围"), (0, react.createElement)("div", { style: {
|
|
2028
|
+
fontSize: 12,
|
|
2029
|
+
color: "var(--dsh-color-muted, #8b90a0)",
|
|
2030
|
+
border: "1px solid var(--dsh-color-border, #3a3f4b)",
|
|
2031
|
+
borderRadius: 4,
|
|
2032
|
+
padding: "4px 6px"
|
|
2033
|
+
} }, "❌ 当前为 DSH 内置通道(复用 DSH 凭据),不支持工具调用;为其配置 API 地址 / Key / 环境变量以走直连通道后即可启用(工具指引提示词也会随之生效)。")];
|
|
2034
|
+
})(), label("思考模式 / 思考强度(未设置 = 跟随模型与供应商默认)"), (0, react.createElement)("div", { style: {
|
|
2035
|
+
display: "flex",
|
|
2036
|
+
gap: 8,
|
|
2037
|
+
marginTop: 4
|
|
2038
|
+
} }, (0, react.createElement)("select", {
|
|
2039
|
+
"aria-label": "思考模式",
|
|
2040
|
+
value: advisor.thinking ?? "",
|
|
2041
|
+
onChange: (e) => updateAdvisor(index, { thinking: e.target.value === "" ? void 0 : e.target.value }),
|
|
2042
|
+
style: settingsInputStyle
|
|
2043
|
+
}, (0, react.createElement)("option", { value: "" }, "思考:默认"), (0, react.createElement)("option", { value: "enabled" }, "思考:开启"), (0, react.createElement)("option", { value: "disabled" }, "思考:关闭")), (0, react.createElement)("select", {
|
|
2044
|
+
"aria-label": "思考强度",
|
|
2045
|
+
value: advisor.reasoningEffort ?? "",
|
|
2046
|
+
onChange: (e) => updateAdvisor(index, { reasoningEffort: e.target.value === "" ? void 0 : e.target.value }),
|
|
2047
|
+
style: settingsInputStyle
|
|
2048
|
+
}, (0, react.createElement)("option", { value: "" }, "强度:默认"), (0, react.createElement)("option", { value: "off" }, "off(不思考)"), (0, react.createElement)("option", { value: "low" }, "low(低)"), (0, react.createElement)("option", { value: "high" }, "high(高)"), (0, react.createElement)("option", { value: "max" }, "max(最大)"))), (0, react.createElement)("div", { style: {
|
|
1368
2049
|
display: "flex",
|
|
1369
2050
|
gap: 8,
|
|
1370
2051
|
marginTop: 4
|
|
@@ -1430,15 +2111,21 @@ function apply(ctx) {
|
|
|
1430
2111
|
key: "advisor-group",
|
|
1431
2112
|
locale: "chat"
|
|
1432
2113
|
}, AdvisorGroupNodeView));
|
|
1433
|
-
ctx.slots.inject("settings.
|
|
1434
|
-
name: "settings.
|
|
1435
|
-
|
|
2114
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
2115
|
+
name: "settings.section",
|
|
2116
|
+
id: "dsh-advisor-group",
|
|
2117
|
+
order: 0,
|
|
2118
|
+
label: "顾问群"
|
|
1436
2119
|
}, AdvisorGroupSettingsTab));
|
|
1437
2120
|
}
|
|
1438
2121
|
|
|
1439
2122
|
//#endregion
|
|
1440
2123
|
exports.advisorGroupDefinition = advisorGroupDefinition;
|
|
1441
2124
|
exports.apply = apply;
|
|
2125
|
+
exports.buildStepSequence = buildStepSequence;
|
|
2126
|
+
exports.buildToolStepViews = buildToolStepViews;
|
|
2127
|
+
exports.extractActionDescription = extractActionDescription;
|
|
1442
2128
|
exports.inject = inject;
|
|
1443
2129
|
exports.name = name;
|
|
2130
|
+
exports.sanitizeClientContent = sanitizeClientContent;
|
|
1444
2131
|
return module.exports; } });
|