dsh-advisor-group 0.0.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/LICENSE +15 -0
- package/README.md +98 -0
- package/README.zh.md +103 -0
- package/cordis.patch.yml +10 -0
- package/lib/client.js +1847 -0
- package/lib/index.d.mts +252 -0
- package/lib/index.mjs +3871 -0
- package/package.json +87 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,1847 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({ id: "dsh-advisor-group", factory: (require) => {
|
|
2
|
+
var module = { exports: {} }; var exports = module.exports;
|
|
3
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
4
|
+
let react = require("react");
|
|
5
|
+
|
|
6
|
+
//#region src/client/markdown.ts
|
|
7
|
+
const inlineCodeStyle = {
|
|
8
|
+
fontFamily: "'Courier New', ui-monospace, SFMono-Regular, monospace",
|
|
9
|
+
fontSize: 12,
|
|
10
|
+
background: "rgba(34,197,94,0.10)",
|
|
11
|
+
border: "1px solid rgba(34,197,94,0.25)",
|
|
12
|
+
borderRadius: 3,
|
|
13
|
+
padding: "0 3px"
|
|
14
|
+
};
|
|
15
|
+
const codeBlockStyle = {
|
|
16
|
+
fontFamily: "'Courier New', ui-monospace, SFMono-Regular, monospace",
|
|
17
|
+
fontSize: 12,
|
|
18
|
+
lineHeight: 1.45,
|
|
19
|
+
whiteSpace: "pre-wrap",
|
|
20
|
+
wordBreak: "break-word"
|
|
21
|
+
};
|
|
22
|
+
const preStyle = {
|
|
23
|
+
margin: "4px 0",
|
|
24
|
+
padding: "6px 8px",
|
|
25
|
+
border: "1px dashed #2f9e44",
|
|
26
|
+
borderRadius: 3,
|
|
27
|
+
background: "rgba(0,0,0,0.35)",
|
|
28
|
+
overflowX: "auto"
|
|
29
|
+
};
|
|
30
|
+
const linkStyle = {
|
|
31
|
+
color: "#86efac",
|
|
32
|
+
textDecoration: "underline"
|
|
33
|
+
};
|
|
34
|
+
const quoteStyle = {
|
|
35
|
+
margin: "4px 0",
|
|
36
|
+
padding: "2px 8px",
|
|
37
|
+
borderLeft: "3px solid #4ade80",
|
|
38
|
+
color: "#a7f3d0",
|
|
39
|
+
background: "rgba(34,197,94,0.06)",
|
|
40
|
+
borderRadius: 2
|
|
41
|
+
};
|
|
42
|
+
const tableStyle = {
|
|
43
|
+
borderCollapse: "collapse",
|
|
44
|
+
margin: "4px 0",
|
|
45
|
+
width: "100%",
|
|
46
|
+
fontSize: 12
|
|
47
|
+
};
|
|
48
|
+
const thStyle = {
|
|
49
|
+
border: "1px solid #2f9e44",
|
|
50
|
+
padding: "3px 6px",
|
|
51
|
+
textAlign: "left",
|
|
52
|
+
color: "#d9f99d",
|
|
53
|
+
background: "rgba(34,197,94,0.08)"
|
|
54
|
+
};
|
|
55
|
+
const tdStyle = {
|
|
56
|
+
border: "1px solid #2f9e44",
|
|
57
|
+
padding: "3px 6px",
|
|
58
|
+
verticalAlign: "top"
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Allow only safe link protocols. `javascript:` / `data:` / `vbscript:` are
|
|
62
|
+
* refused and rendered as plain text instead of an anchor.
|
|
63
|
+
*/
|
|
64
|
+
function sanitizeLinkUrl(url) {
|
|
65
|
+
const trimmed = url.trim();
|
|
66
|
+
if (/^https?:\/\//i.test(trimmed)) return trimmed;
|
|
67
|
+
if (/^mailto:/i.test(trimmed)) return trimmed;
|
|
68
|
+
if (trimmed.startsWith("/") || trimmed.startsWith("#") || trimmed.startsWith("?")) return trimmed;
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
function inlineNodes(text) {
|
|
72
|
+
const nodes = [];
|
|
73
|
+
const regex = /(\*\*([^*]+)\*\*)|(\*([^*]+)\*)|(`([^`]+)`)|(\[([^\]]+)\]\(([^)]+)\))/g;
|
|
74
|
+
let lastIndex = 0;
|
|
75
|
+
let key = 0;
|
|
76
|
+
for (const match of text.matchAll(regex)) {
|
|
77
|
+
const index = match.index ?? 0;
|
|
78
|
+
if (index > lastIndex) nodes.push(text.slice(lastIndex, index));
|
|
79
|
+
if (match[1] !== void 0) nodes.push((0, react.createElement)("strong", { key: `b${key}` }, match[2]));
|
|
80
|
+
else if (match[3] !== void 0) nodes.push((0, react.createElement)("em", { key: `i${key}` }, match[4]));
|
|
81
|
+
else if (match[5] !== void 0) nodes.push((0, react.createElement)("code", {
|
|
82
|
+
key: `c${key}`,
|
|
83
|
+
style: inlineCodeStyle
|
|
84
|
+
}, match[6]));
|
|
85
|
+
else if (match[7] !== void 0) {
|
|
86
|
+
const safeUrl = sanitizeLinkUrl(match[9] ?? "");
|
|
87
|
+
if (safeUrl === null) nodes.push(match[7]);
|
|
88
|
+
else nodes.push((0, react.createElement)("a", {
|
|
89
|
+
key: `a${key}`,
|
|
90
|
+
href: safeUrl,
|
|
91
|
+
target: "_blank",
|
|
92
|
+
rel: "noreferrer",
|
|
93
|
+
style: linkStyle
|
|
94
|
+
}, match[8]));
|
|
95
|
+
}
|
|
96
|
+
lastIndex = index + match[0].length;
|
|
97
|
+
key += 1;
|
|
98
|
+
}
|
|
99
|
+
if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
|
|
100
|
+
return nodes;
|
|
101
|
+
}
|
|
102
|
+
function isBlockStart(line) {
|
|
103
|
+
const trimmed = line.trim();
|
|
104
|
+
return trimmed.startsWith("```") || /^#{1,4}\s+/.test(line) || /^[-*]\s+/.test(line) || /^\d+\.\s+/.test(line) || line.startsWith(">") || /^(-{3,}|\*{3,})$/.test(trimmed) || /^\|.*\|/.test(trimmed);
|
|
105
|
+
}
|
|
106
|
+
function isTableSeparator(line) {
|
|
107
|
+
return /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(line);
|
|
108
|
+
}
|
|
109
|
+
function splitTableRow(line) {
|
|
110
|
+
let row = line.trim();
|
|
111
|
+
if (row.startsWith("|")) row = row.slice(1);
|
|
112
|
+
if (row.endsWith("|")) row = row.slice(0, -1);
|
|
113
|
+
return row.split("|").map((cell) => cell.trim());
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Tiny markdown renderer covering what advisor models actually emit: headings,
|
|
117
|
+
* bold/italic/inline code, fenced code blocks, lists, blockquotes, links,
|
|
118
|
+
* tables and horizontal rules. Text is emitted exclusively as React text
|
|
119
|
+
* nodes (never as HTML), so LLM output is escaped by React; the only extra
|
|
120
|
+
* sanitization needed is the link protocol whitelist above.
|
|
121
|
+
*/
|
|
122
|
+
function renderMarkdown(content) {
|
|
123
|
+
const lines = content.split("\n");
|
|
124
|
+
const blocks = [];
|
|
125
|
+
let blockKey = 0;
|
|
126
|
+
let i = 0;
|
|
127
|
+
while (i < lines.length) {
|
|
128
|
+
const line = lines[i];
|
|
129
|
+
const trimmed = line.trim();
|
|
130
|
+
if (trimmed === "") {
|
|
131
|
+
i += 1;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (trimmed.startsWith("```")) {
|
|
135
|
+
const code = [];
|
|
136
|
+
i += 1;
|
|
137
|
+
while (i < lines.length && lines[i].trim() !== "```") {
|
|
138
|
+
code.push(lines[i]);
|
|
139
|
+
i += 1;
|
|
140
|
+
}
|
|
141
|
+
i += 1;
|
|
142
|
+
blocks.push((0, react.createElement)("pre", {
|
|
143
|
+
key: `pre${blockKey}`,
|
|
144
|
+
style: preStyle
|
|
145
|
+
}, (0, react.createElement)("code", { style: codeBlockStyle }, code.join("\n"))));
|
|
146
|
+
blockKey += 1;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const heading = /^(#{1,4})\s+(.*)$/.exec(line);
|
|
150
|
+
if (heading) {
|
|
151
|
+
const level = heading[1].length;
|
|
152
|
+
const size = [
|
|
153
|
+
16,
|
|
154
|
+
15,
|
|
155
|
+
14,
|
|
156
|
+
13
|
|
157
|
+
][level - 1] ?? 13;
|
|
158
|
+
blocks.push((0, react.createElement)(`h${level}`, {
|
|
159
|
+
key: `h${blockKey}`,
|
|
160
|
+
style: {
|
|
161
|
+
fontSize: size,
|
|
162
|
+
fontWeight: 700,
|
|
163
|
+
margin: "6px 0 2px",
|
|
164
|
+
color: "#d9f99d",
|
|
165
|
+
lineHeight: 1.4
|
|
166
|
+
}
|
|
167
|
+
}, inlineNodes(heading[2])));
|
|
168
|
+
blockKey += 1;
|
|
169
|
+
i += 1;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (/^(-{3,}|\*{3,})$/.test(trimmed)) {
|
|
173
|
+
blocks.push((0, react.createElement)("hr", {
|
|
174
|
+
key: `hr${blockKey}`,
|
|
175
|
+
style: {
|
|
176
|
+
border: "none",
|
|
177
|
+
borderTop: "1px dashed #2f9e44",
|
|
178
|
+
margin: "6px 0"
|
|
179
|
+
}
|
|
180
|
+
}));
|
|
181
|
+
blockKey += 1;
|
|
182
|
+
i += 1;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (line.startsWith(">")) {
|
|
186
|
+
const quote = [];
|
|
187
|
+
while (i < lines.length && lines[i].startsWith(">")) {
|
|
188
|
+
quote.push(lines[i].replace(/^>\s?/, ""));
|
|
189
|
+
i += 1;
|
|
190
|
+
}
|
|
191
|
+
blocks.push((0, react.createElement)("div", {
|
|
192
|
+
key: `q${blockKey}`,
|
|
193
|
+
style: quoteStyle
|
|
194
|
+
}, renderMarkdown(quote.join("\n"))));
|
|
195
|
+
blockKey += 1;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (/^\|.*\|/.test(trimmed) && i + 1 < lines.length && isTableSeparator(lines[i + 1] ?? "")) {
|
|
199
|
+
const header = splitTableRow(line);
|
|
200
|
+
i += 2;
|
|
201
|
+
const rows = [];
|
|
202
|
+
while (i < lines.length && /^\|.*\|/.test(lines[i].trim())) {
|
|
203
|
+
rows.push(splitTableRow(lines[i]));
|
|
204
|
+
i += 1;
|
|
205
|
+
}
|
|
206
|
+
blocks.push((0, react.createElement)("table", {
|
|
207
|
+
key: `t${blockKey}`,
|
|
208
|
+
style: tableStyle
|
|
209
|
+
}, (0, react.createElement)("thead", { key: `th${blockKey}` }, (0, react.createElement)("tr", { key: `trh${blockKey}` }, header.map((cell, index) => (0, react.createElement)("th", {
|
|
210
|
+
key: `thc${blockKey}-${index}`,
|
|
211
|
+
style: thStyle
|
|
212
|
+
}, inlineNodes(cell))))), (0, react.createElement)("tbody", { key: `tb${blockKey}` }, rows.map((row, rowIndex) => (0, react.createElement)("tr", { key: `tr${blockKey}-${rowIndex}` }, row.map((cell, cellIndex) => (0, react.createElement)("td", {
|
|
213
|
+
key: `td${blockKey}-${rowIndex}-${cellIndex}`,
|
|
214
|
+
style: tdStyle
|
|
215
|
+
}, inlineNodes(cell))))))));
|
|
216
|
+
blockKey += 1;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (/^[-*]\s+/.test(line) || /^\d+\.\s+/.test(line)) {
|
|
220
|
+
const ordered = /^\d+\.\s+/.test(line);
|
|
221
|
+
const items = [];
|
|
222
|
+
while (i < lines.length && (/^[-*]\s+/.test(lines[i]) || /^\d+\.\s+/.test(lines[i]))) {
|
|
223
|
+
items.push(lines[i].replace(/^([-*]|\d+\.)\s+/, ""));
|
|
224
|
+
i += 1;
|
|
225
|
+
}
|
|
226
|
+
blocks.push((0, react.createElement)(ordered ? "ol" : "ul", {
|
|
227
|
+
key: `l${blockKey}`,
|
|
228
|
+
style: {
|
|
229
|
+
margin: "4px 0",
|
|
230
|
+
paddingLeft: 20,
|
|
231
|
+
lineHeight: 1.5
|
|
232
|
+
}
|
|
233
|
+
}, items.map((item, index) => (0, react.createElement)("li", { key: `li${blockKey}-${index}` }, inlineNodes(item)))));
|
|
234
|
+
blockKey += 1;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const paragraph = [];
|
|
238
|
+
while (i < lines.length && lines[i].trim() !== "" && !isBlockStart(lines[i])) {
|
|
239
|
+
paragraph.push(lines[i]);
|
|
240
|
+
i += 1;
|
|
241
|
+
}
|
|
242
|
+
blocks.push((0, react.createElement)("p", {
|
|
243
|
+
key: `p${blockKey}`,
|
|
244
|
+
style: {
|
|
245
|
+
margin: "4px 0",
|
|
246
|
+
lineHeight: 1.5
|
|
247
|
+
}
|
|
248
|
+
}, inlineNodes(paragraph.join(" "))));
|
|
249
|
+
blockKey += 1;
|
|
250
|
+
}
|
|
251
|
+
return blocks;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/defaults.ts
|
|
256
|
+
const DEFAULT_ADVISOR_PROMPT = `
|
|
257
|
+
你是顾问群中的资深专家。你的任务不是替用户执行,而是提供高质量的专业意见供主模型参考。
|
|
258
|
+
|
|
259
|
+
回答要求:
|
|
260
|
+
1. 先给出明确结论,再说明理由、依据或经验;
|
|
261
|
+
2. 内容要具体、可执行,避免空话套话;
|
|
262
|
+
3. 明确区分事实、推测和建议;
|
|
263
|
+
4. 如果信息不足,明确说明缺少什么、需要什么信息,不要强行编造;
|
|
264
|
+
5. 涉及专业风险时,主动提示风险点和适用边界;
|
|
265
|
+
6. 在讨论轮次中:
|
|
266
|
+
- 第 1 轮:独立给出你的专业意见;
|
|
267
|
+
- 后续轮次:参考其他顾问的观点,明确表示同意/补充/反对,不要简单重复;
|
|
268
|
+
7. 尽量用分点/结构化输出,方便主模型综合。
|
|
269
|
+
|
|
270
|
+
结尾按约定标注置信度或不确定性。
|
|
271
|
+
`.trim();
|
|
272
|
+
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/client/index.ts
|
|
275
|
+
/**
|
|
276
|
+
* dsh-advisor-group browser half.
|
|
277
|
+
*
|
|
278
|
+
* Renders a durable advisor-group consultation as a retro CRT chat-group card
|
|
279
|
+
* in the DSH web conversation flow. The node is assembled from the durable
|
|
280
|
+
* session events:
|
|
281
|
+
*
|
|
282
|
+
* advisor-group/start -> start
|
|
283
|
+
* advisor-group/message -> update (live bubbles)
|
|
284
|
+
* advisor-group/end -> update (final synopsis)
|
|
285
|
+
*/
|
|
286
|
+
function advisorGroupToken() {
|
|
287
|
+
return globalThis.__ADVISOR_GROUP_TOKEN__ ?? "";
|
|
288
|
+
}
|
|
289
|
+
function authHeaders() {
|
|
290
|
+
const token = advisorGroupToken();
|
|
291
|
+
return token ? { "x-advisor-group-token": token } : {};
|
|
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
|
+
}
|
|
307
|
+
function locationOf(context) {
|
|
308
|
+
return context.start?.location ?? context.matches[0]?.location ?? { kind: "unresolved" };
|
|
309
|
+
}
|
|
310
|
+
function viewData(state) {
|
|
311
|
+
return {
|
|
312
|
+
sessionId: state.sessionId,
|
|
313
|
+
status: state.status,
|
|
314
|
+
question: state.question,
|
|
315
|
+
...state.context === void 0 ? {} : { context: state.context },
|
|
316
|
+
advisors: state.advisors,
|
|
317
|
+
messages: state.messages,
|
|
318
|
+
...state.summary === void 0 ? {} : { summary: state.summary }
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
const advisorGroupDefinition = {
|
|
322
|
+
kind: "advisor-group",
|
|
323
|
+
target: "chat",
|
|
324
|
+
match: (event) => {
|
|
325
|
+
if (event.type === "advisor-group/start") return {
|
|
326
|
+
id: event.data.sessionId,
|
|
327
|
+
role: "start"
|
|
328
|
+
};
|
|
329
|
+
if (event.type === "advisor-group/message" || event.type === "advisor-group/delta" || event.type === "advisor-group/end" || event.type === "advisor-group/resume") return {
|
|
330
|
+
id: event.data.sessionId,
|
|
331
|
+
role: "update"
|
|
332
|
+
};
|
|
333
|
+
return null;
|
|
334
|
+
},
|
|
335
|
+
start: (_context, match) => {
|
|
336
|
+
if (match.event.type !== "advisor-group/start") throw new Error("advisor-group requires advisor-group/start");
|
|
337
|
+
const data = match.event.data;
|
|
338
|
+
return {
|
|
339
|
+
sessionId: data.sessionId,
|
|
340
|
+
turn: data.turn,
|
|
341
|
+
step: data.step,
|
|
342
|
+
question: data.question,
|
|
343
|
+
...data.context === void 0 ? {} : { context: data.context },
|
|
344
|
+
advisors: data.advisors,
|
|
345
|
+
messages: [{
|
|
346
|
+
sessionId: data.sessionId,
|
|
347
|
+
turn: data.turn,
|
|
348
|
+
step: data.step,
|
|
349
|
+
role: "main",
|
|
350
|
+
content: data.question
|
|
351
|
+
}],
|
|
352
|
+
status: "running"
|
|
353
|
+
};
|
|
354
|
+
},
|
|
355
|
+
update: (context, match) => {
|
|
356
|
+
if (match.event.type === "advisor-group/message") {
|
|
357
|
+
const data = match.event.data;
|
|
358
|
+
const existingIndex = context.state.messages.findIndex((message) => message.role === "advisor" && message.advisorId === data.advisorId && (data.round === void 0 || message.round === void 0 || message.round === data.round));
|
|
359
|
+
if (existingIndex >= 0) {
|
|
360
|
+
const messages = [...context.state.messages];
|
|
361
|
+
messages[existingIndex] = {
|
|
362
|
+
...messages[existingIndex],
|
|
363
|
+
...data
|
|
364
|
+
};
|
|
365
|
+
return {
|
|
366
|
+
...context.state,
|
|
367
|
+
messages
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
...context.state,
|
|
372
|
+
messages: [...context.state.messages, data]
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
if (match.event.type === "advisor-group/delta") {
|
|
376
|
+
const delta = match.event.data;
|
|
377
|
+
if (!context.state.messages.find((message) => message.role === "advisor" && message.advisorId === delta.advisorId && (delta.round === void 0 || message.round === void 0 || message.round === delta.round))) return {
|
|
378
|
+
...context.state,
|
|
379
|
+
messages: [...context.state.messages, {
|
|
380
|
+
sessionId: delta.sessionId,
|
|
381
|
+
turn: delta.turn,
|
|
382
|
+
step: delta.step,
|
|
383
|
+
role: "advisor",
|
|
384
|
+
advisorId: delta.advisorId,
|
|
385
|
+
advisorName: delta.advisorName,
|
|
386
|
+
...delta.round === void 0 ? {} : { round: delta.round },
|
|
387
|
+
content: delta.contentDelta ?? "",
|
|
388
|
+
thinking: delta.thinkingDelta ?? ""
|
|
389
|
+
}]
|
|
390
|
+
};
|
|
391
|
+
return {
|
|
392
|
+
...context.state,
|
|
393
|
+
messages: context.state.messages.map((message) => message.role === "advisor" && message.advisorId === delta.advisorId && (delta.round === void 0 || message.round === void 0 || message.round === delta.round) ? {
|
|
394
|
+
...message,
|
|
395
|
+
content: message.content + (delta.contentDelta ?? ""),
|
|
396
|
+
thinking: (message.thinking ?? "") + (delta.thinkingDelta ?? "")
|
|
397
|
+
} : message)
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
if (match.event.type === "advisor-group/end") return {
|
|
401
|
+
...context.state,
|
|
402
|
+
status: match.event.data.summary.stopped ? "cancelled" : "completed",
|
|
403
|
+
summary: match.event.data.summary
|
|
404
|
+
};
|
|
405
|
+
if (match.event.type === "advisor-group/resume") return {
|
|
406
|
+
...context.state,
|
|
407
|
+
status: "running"
|
|
408
|
+
};
|
|
409
|
+
return context.state;
|
|
410
|
+
},
|
|
411
|
+
publication: (match) => match.event.type === "advisor-group/message" || match.event.type === "advisor-group/delta" ? "animation-frame" : "immediate",
|
|
412
|
+
buildLocationData: (context, scope) => {
|
|
413
|
+
if (scope !== "step" || context.state === void 0) return null;
|
|
414
|
+
return {
|
|
415
|
+
kind: "step",
|
|
416
|
+
turn: context.state.turn,
|
|
417
|
+
step: context.state.step,
|
|
418
|
+
key: "advisor-group",
|
|
419
|
+
value: viewData(context.state)
|
|
420
|
+
};
|
|
421
|
+
},
|
|
422
|
+
buildViewNode: (context) => {
|
|
423
|
+
if (context.state === void 0) return null;
|
|
424
|
+
return buildChatNode(context, "advisor-group", context.start?.event.seq ?? context.matches[0]?.event.seq ?? 0, viewData(context.state));
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
/**
|
|
428
|
+
* Build the chat-target view Node for one assembled advisor-group Context.
|
|
429
|
+
*
|
|
430
|
+
* The generic `ConversationNodeDefinition.buildViewNode` only declares the
|
|
431
|
+
* `{ key, kind, id, target, data }` face; DSH's chat snapshot builder reads
|
|
432
|
+
* the chat-specific extras (`anchorSeq` / `location` / `visibility`) at
|
|
433
|
+
* runtime. The factory returns them through a function call so the object is
|
|
434
|
+
* not subject to fresh-literal excess-property checks against the generic
|
|
435
|
+
* type — the same technique the official chat definitions use.
|
|
436
|
+
*/
|
|
437
|
+
function buildChatNode(context, kind, anchorSeq, data) {
|
|
438
|
+
return {
|
|
439
|
+
key: context.key,
|
|
440
|
+
kind,
|
|
441
|
+
id: context.id,
|
|
442
|
+
target: "chat",
|
|
443
|
+
anchorSeq,
|
|
444
|
+
location: locationOf(context),
|
|
445
|
+
visibility: "visible",
|
|
446
|
+
data
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
const rootStyle = {
|
|
450
|
+
boxSizing: "border-box",
|
|
451
|
+
width: "100%",
|
|
452
|
+
margin: "8px 0",
|
|
453
|
+
padding: "10px 12px",
|
|
454
|
+
border: "1px solid #22c55e",
|
|
455
|
+
borderRadius: 4,
|
|
456
|
+
background: "#0a0f0a",
|
|
457
|
+
color: "#b8f5c4",
|
|
458
|
+
fontFamily: "'Courier New', ui-monospace, SFMono-Regular, monospace",
|
|
459
|
+
fontSize: 13,
|
|
460
|
+
lineHeight: 1.5,
|
|
461
|
+
position: "relative",
|
|
462
|
+
overflow: "hidden",
|
|
463
|
+
boxShadow: "0 0 12px rgba(34,197,94,0.22), inset 0 0 24px rgba(34,197,94,0.06)"
|
|
464
|
+
};
|
|
465
|
+
const scanlineStyle = {
|
|
466
|
+
position: "absolute",
|
|
467
|
+
top: 0,
|
|
468
|
+
right: 0,
|
|
469
|
+
bottom: 0,
|
|
470
|
+
left: 0,
|
|
471
|
+
pointerEvents: "none",
|
|
472
|
+
background: "repeating-linear-gradient(0deg, rgba(0,0,0,0.18) 0px, rgba(0,0,0,0.18) 1px, transparent 1px, transparent 3px)"
|
|
473
|
+
};
|
|
474
|
+
const headerStyle = {
|
|
475
|
+
display: "flex",
|
|
476
|
+
justifyContent: "space-between",
|
|
477
|
+
gap: 8,
|
|
478
|
+
borderBottom: "1px dashed #2f9e44",
|
|
479
|
+
paddingBottom: 4,
|
|
480
|
+
marginBottom: 8,
|
|
481
|
+
color: "#86efac",
|
|
482
|
+
textTransform: "uppercase",
|
|
483
|
+
letterSpacing: 1,
|
|
484
|
+
fontSize: 11
|
|
485
|
+
};
|
|
486
|
+
const advisorsLineStyle = {
|
|
487
|
+
fontSize: 11,
|
|
488
|
+
color: "#4ade80",
|
|
489
|
+
marginBottom: 6
|
|
490
|
+
};
|
|
491
|
+
const contextBoxStyle = {
|
|
492
|
+
border: "1px dashed #a16207",
|
|
493
|
+
borderRadius: 3,
|
|
494
|
+
marginBottom: 6,
|
|
495
|
+
background: "rgba(217,119,6,0.06)"
|
|
496
|
+
};
|
|
497
|
+
const contextToggleStyle = {
|
|
498
|
+
width: "100%",
|
|
499
|
+
cursor: "pointer",
|
|
500
|
+
background: "transparent",
|
|
501
|
+
border: "none",
|
|
502
|
+
color: "#fbbf24",
|
|
503
|
+
fontSize: 12,
|
|
504
|
+
textAlign: "left",
|
|
505
|
+
padding: "3px 6px"
|
|
506
|
+
};
|
|
507
|
+
const contextBodyStyle = {
|
|
508
|
+
maxHeight: 180,
|
|
509
|
+
overflowY: "auto",
|
|
510
|
+
whiteSpace: "pre-wrap",
|
|
511
|
+
wordBreak: "break-word",
|
|
512
|
+
padding: "4px 6px",
|
|
513
|
+
color: "#fde68a",
|
|
514
|
+
fontSize: 12,
|
|
515
|
+
borderTop: "1px dashed #a16207"
|
|
516
|
+
};
|
|
517
|
+
const waitingStyle = {
|
|
518
|
+
color: "#fbbf24",
|
|
519
|
+
marginTop: 6
|
|
520
|
+
};
|
|
521
|
+
/** Internal announcements that must never reach the user (the tool list). */
|
|
522
|
+
const INTERNAL_TOOL_STEP_NAMES = /* @__PURE__ */ new Set(["⚙️ tools", "⚙ tools"]);
|
|
523
|
+
/** True when the tool result represents a failure — the wrapper prefix, or a
|
|
524
|
+
* JSON payload carrying `error`/`detail`/HTTP error code (unconfigured/401
|
|
525
|
+
* tools), robust to nested objects before the error key. */
|
|
526
|
+
function isFailedToolResult(text) {
|
|
527
|
+
if (text.startsWith("(工具执行失败")) return true;
|
|
528
|
+
try {
|
|
529
|
+
const parsed = JSON.parse(text);
|
|
530
|
+
if (parsed && (parsed.error !== void 0 || parsed.detail !== void 0)) return true;
|
|
531
|
+
if (parsed && typeof parsed.code === "number" && parsed.code >= 400 && parsed.code <= 599) return true;
|
|
532
|
+
} catch {}
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Pair call/result tool steps into one `ToolStepView` per invocation, in
|
|
537
|
+
* stream order, and drop internal announcements (「本次可用」).
|
|
538
|
+
*/
|
|
539
|
+
function buildToolStepViews(steps) {
|
|
540
|
+
const out = [];
|
|
541
|
+
let last;
|
|
542
|
+
for (const step of steps ?? []) {
|
|
543
|
+
if (INTERNAL_TOOL_STEP_NAMES.has(step.name)) continue;
|
|
544
|
+
if (step.kind === "call") {
|
|
545
|
+
last = {
|
|
546
|
+
name: step.name,
|
|
547
|
+
input: step.text,
|
|
548
|
+
atMs: step.atMs
|
|
549
|
+
};
|
|
550
|
+
out.push(last);
|
|
551
|
+
} else if (step.kind === "result") {
|
|
552
|
+
if (last && last.output === void 0 && last.input !== void 0) {
|
|
553
|
+
last.output = step.text;
|
|
554
|
+
if (isFailedToolResult(step.text)) last.failed = true;
|
|
555
|
+
} else out.push({
|
|
556
|
+
name: step.name,
|
|
557
|
+
output: step.text,
|
|
558
|
+
atMs: step.atMs,
|
|
559
|
+
...isFailedToolResult(step.text) ? { failed: true } : {}
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return out.filter((view) => view.input !== void 0 || view.output !== void 0);
|
|
564
|
+
}
|
|
565
|
+
const stepRowShellStyle = {
|
|
566
|
+
border: "1px solid rgba(34,197,94,0.28)",
|
|
567
|
+
borderRadius: 3,
|
|
568
|
+
margin: "5px 0",
|
|
569
|
+
background: "rgba(34,197,94,0.05)",
|
|
570
|
+
overflow: "hidden"
|
|
571
|
+
};
|
|
572
|
+
const stepRowHeaderStyle = (color) => ({
|
|
573
|
+
width: "100%",
|
|
574
|
+
display: "flex",
|
|
575
|
+
alignItems: "center",
|
|
576
|
+
gap: 8,
|
|
577
|
+
cursor: "pointer",
|
|
578
|
+
background: "transparent",
|
|
579
|
+
border: "none",
|
|
580
|
+
color,
|
|
581
|
+
fontSize: 12,
|
|
582
|
+
textAlign: "left",
|
|
583
|
+
padding: "4px 8px"
|
|
584
|
+
});
|
|
585
|
+
const stepRowBodyStyle = {
|
|
586
|
+
borderTop: "1px dashed rgba(47,158,68,0.45)",
|
|
587
|
+
padding: "5px 8px"
|
|
588
|
+
};
|
|
589
|
+
function StepRow({ title, badges, defaultOpen, color = "#4ade80", body }) {
|
|
590
|
+
const [open, setOpen] = (0, react.useState)(defaultOpen);
|
|
591
|
+
return (0, react.createElement)("div", { style: stepRowShellStyle }, (0, react.createElement)("button", {
|
|
592
|
+
type: "button",
|
|
593
|
+
onClick: () => setOpen((value) => !value),
|
|
594
|
+
style: stepRowHeaderStyle(color)
|
|
595
|
+
}, (0, react.createElement)("span", null, open ? "▾" : "▸"), (0, react.createElement)("span", { style: { fontWeight: 700 } }, title), ...badges ?? []), open ? (0, react.createElement)("div", { style: stepRowBodyStyle }, body) : null);
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Inline thinking annotation: an UNFRAMED block that sits directly above the
|
|
599
|
+
* tool row it led to (each step reads: 💭 思考文本 → ⛭ 工具), with the full
|
|
600
|
+
* segment visible (scrollable) — not a separate bordered card.
|
|
601
|
+
*/
|
|
602
|
+
const thinkingInlineStyle = {
|
|
603
|
+
margin: "4px 0 2px",
|
|
604
|
+
padding: "4px 8px",
|
|
605
|
+
borderLeft: "2px solid rgba(251,191,36,0.45)",
|
|
606
|
+
background: "rgba(217,119,6,0.04)"
|
|
607
|
+
};
|
|
608
|
+
const thinkingTagStyle = {
|
|
609
|
+
color: "#fbbf24",
|
|
610
|
+
fontSize: 11,
|
|
611
|
+
marginBottom: 2,
|
|
612
|
+
fontWeight: 700
|
|
613
|
+
};
|
|
614
|
+
const descriptionBodyStyle = {
|
|
615
|
+
whiteSpace: "pre-wrap",
|
|
616
|
+
wordBreak: "break-word",
|
|
617
|
+
color: "#d9f99d",
|
|
618
|
+
fontSize: 12,
|
|
619
|
+
padding: "2px 6px 4px"
|
|
620
|
+
};
|
|
621
|
+
/**
|
|
622
|
+
* Short "next-action narrative" of a thinking segment — the visible 📋 行动·N
|
|
623
|
+
* block shown directly after the 💭 思考·N panel and before the ⛭ tool row.
|
|
624
|
+
* Standard-agent style: pick the FIRST SHORT sentence that says what the model
|
|
625
|
+
* is about to do (action markers like 我先/接下来/我需要…), else the first
|
|
626
|
+
* non-generic short sentence; hard-cap at ~140 chars. The long internal
|
|
627
|
+
* monologue stays in the thinking panel.
|
|
628
|
+
*/
|
|
629
|
+
function extractActionDescription(segment) {
|
|
630
|
+
const text = segment.trim();
|
|
631
|
+
if (!text) return "";
|
|
632
|
+
const sentences = (text.match(/[^。!?!?]+[。!?!?]?/g) ?? []).map((sentence) => sentence.replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
633
|
+
const cut = (value, max) => value.length > max ? `${value.slice(0, max)}…` : value;
|
|
634
|
+
const ACTION = /(我先|接下来|下一步|下面|准备|打算|我需要|先做|先看|先检查|先核对|先核实|先验证|先确认|先读|先搜索|先检索|先找|计划|步骤|让我先|我会|需要先|先补|先抓|先取|先查|先分析|先整理|先修改|先跟踪)/;
|
|
635
|
+
const GENERIC = /^(让我|我明白|好的|嗯|OK|Okay|我是|我是一位|我的角色|Let me|Sure|The user|用户)/;
|
|
636
|
+
for (const sentence of sentences.slice(0, 5)) if (sentence.length <= 160 && ACTION.test(sentence)) return cut(sentence, 140);
|
|
637
|
+
for (const sentence of sentences.slice(0, 3)) if (sentence.length <= 120 && !GENERIC.test(sentence)) return cut(sentence, 120);
|
|
638
|
+
return cut(sentences[0] ?? text, 100);
|
|
639
|
+
}
|
|
640
|
+
const thinkingBodyStyle = {
|
|
641
|
+
maxHeight: 200,
|
|
642
|
+
overflowY: "auto",
|
|
643
|
+
whiteSpace: "pre-wrap",
|
|
644
|
+
wordBreak: "break-word",
|
|
645
|
+
color: "#fde68a",
|
|
646
|
+
fontSize: 12,
|
|
647
|
+
padding: "4px 6px"
|
|
648
|
+
};
|
|
649
|
+
const toolBodyStyle = {
|
|
650
|
+
fontSize: 12,
|
|
651
|
+
color: "#38bdf8"
|
|
652
|
+
};
|
|
653
|
+
const toolIoTitleStyle = {
|
|
654
|
+
color: "#86efac",
|
|
655
|
+
fontWeight: 700,
|
|
656
|
+
margin: "2px 0 2px"
|
|
657
|
+
};
|
|
658
|
+
const toolIoBodyStyle = {
|
|
659
|
+
whiteSpace: "pre-wrap",
|
|
660
|
+
wordBreak: "break-word",
|
|
661
|
+
color: "var(--dsh-color-muted, #8b90a0)",
|
|
662
|
+
marginBottom: 6
|
|
663
|
+
};
|
|
664
|
+
const badgeStyle = (color) => ({
|
|
665
|
+
color,
|
|
666
|
+
fontSize: 11,
|
|
667
|
+
opacity: .95
|
|
668
|
+
});
|
|
669
|
+
/**
|
|
670
|
+
* Interleave action announcements, thinking segments and tool views in the
|
|
671
|
+
* official DSH block order (text → reasoning → tool-call), then the body.
|
|
672
|
+
* Each round contributes at most one 行动 / 思考 / 工具 item; missing actions
|
|
673
|
+
* are derived from the thinking segment so pre-actionDescriptions logs still
|
|
674
|
+
* render sensibly.
|
|
675
|
+
*/
|
|
676
|
+
function buildStepSequence(actionDescriptions, thinkingSegments, toolViews) {
|
|
677
|
+
const items = [];
|
|
678
|
+
const count = Math.max(actionDescriptions.length, thinkingSegments.length, toolViews.length);
|
|
679
|
+
for (let index = 0; index < count; index += 1) {
|
|
680
|
+
const step = index + 1;
|
|
681
|
+
const described = actionDescriptions[index]?.trim();
|
|
682
|
+
const segment = thinkingSegments[index];
|
|
683
|
+
const action = described || (segment !== void 0 && segment.trim() ? extractActionDescription(segment) : "");
|
|
684
|
+
if (action) items.push({
|
|
685
|
+
kind: "action",
|
|
686
|
+
text: action,
|
|
687
|
+
step
|
|
688
|
+
});
|
|
689
|
+
if (segment !== void 0 && segment.trim()) items.push({
|
|
690
|
+
kind: "thinking",
|
|
691
|
+
segment,
|
|
692
|
+
step
|
|
693
|
+
});
|
|
694
|
+
const view = toolViews[index];
|
|
695
|
+
if (view !== void 0) items.push({
|
|
696
|
+
kind: "tool",
|
|
697
|
+
view
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
items.push({ kind: "body" });
|
|
701
|
+
return items;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Render one consultation message as a stack of STEP rows (one row per step,
|
|
705
|
+
* appearing in stream order, each independently expandable):
|
|
706
|
+
* 💭 思考·1 → ⛭ 工具×1(输入/输出)→ 💭 思考·2 → ⛭ 工具×2 → … → 📄 正文
|
|
707
|
+
* Main/system messages stay a single collapsible row.
|
|
708
|
+
*/
|
|
709
|
+
function AdvisorSteps({ message }) {
|
|
710
|
+
const isMain = message.role === "main";
|
|
711
|
+
const isSystem = message.role === "system";
|
|
712
|
+
const label = isMain ? "YOU" : isSystem ? "SYSTEM" : message.advisorName ?? message.advisorId ?? "ADVISOR";
|
|
713
|
+
const group = isMain ? "YOU" : isSystem ? "SYSTEM" : `${label} · R${message.round ?? 1}`;
|
|
714
|
+
const truncated = message.truncated;
|
|
715
|
+
const toolViews = buildToolStepViews(message.toolSteps);
|
|
716
|
+
const thinkingSegments = message.thinkingSegments && message.thinkingSegments.length > 0 ? message.thinkingSegments : message.thinking ? [message.thinking] : [];
|
|
717
|
+
const baseColor = isSystem ? "#fcd34d" : "#4ade80";
|
|
718
|
+
if (isMain || isSystem) return (0, react.createElement)(StepRow, {
|
|
719
|
+
title: group,
|
|
720
|
+
defaultOpen: isMain,
|
|
721
|
+
color: baseColor,
|
|
722
|
+
body: (0, react.createElement)("div", { style: {
|
|
723
|
+
whiteSpace: "pre-wrap",
|
|
724
|
+
wordBreak: "break-word"
|
|
725
|
+
} }, message.content)
|
|
726
|
+
});
|
|
727
|
+
const sequence = buildStepSequence(message.actionDescriptions ?? [], thinkingSegments, toolViews);
|
|
728
|
+
const live = message;
|
|
729
|
+
const liveToolPhase = live.livePhase === "tool";
|
|
730
|
+
const liveAction = liveToolPhase && live.liveBody?.trim() ? live.liveBody.trim() : void 0;
|
|
731
|
+
const children = [];
|
|
732
|
+
for (const [index, item] of sequence.entries()) {
|
|
733
|
+
if (item.kind === "action") {
|
|
734
|
+
children.push((0, react.createElement)("div", {
|
|
735
|
+
key: `action-${index}`,
|
|
736
|
+
style: thinkingInlineStyle
|
|
737
|
+
}, (0, react.createElement)("div", { style: thinkingTagStyle }, `${group} 📋 行动·${item.step}`), (0, react.createElement)("div", { style: descriptionBodyStyle }, item.text)));
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
if (item.kind === "thinking") {
|
|
741
|
+
children.push((0, react.createElement)(StepRow, {
|
|
742
|
+
key: `think-${index}`,
|
|
743
|
+
title: `${group} 💭 思考·${item.step}`,
|
|
744
|
+
badges: [(0, react.createElement)("span", { style: badgeStyle("#fbbf24") }, `${item.segment.length} 字符`)],
|
|
745
|
+
defaultOpen: false,
|
|
746
|
+
color: "#fbbf24",
|
|
747
|
+
body: (0, react.createElement)("div", { style: thinkingBodyStyle }, item.segment)
|
|
748
|
+
}));
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
if (item.kind === "tool") {
|
|
752
|
+
const view = item.view;
|
|
753
|
+
children.push((0, react.createElement)(StepRow, {
|
|
754
|
+
key: `tool-${index}`,
|
|
755
|
+
title: `${group} ⛭ ${view.name}`,
|
|
756
|
+
badges: [(0, react.createElement)("span", { style: badgeStyle(view.failed ? "#f87171" : "#38bdf8") }, view.output === void 0 ? "调用中…" : view.failed ? "⚠ 失败" : "调用完成")],
|
|
757
|
+
defaultOpen: false,
|
|
758
|
+
color: view.failed ? "#f87171" : "#38bdf8",
|
|
759
|
+
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)
|
|
760
|
+
}));
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
if (liveToolPhase) continue;
|
|
764
|
+
children.push((0, react.createElement)(StepRow, {
|
|
765
|
+
key: `body-${index}`,
|
|
766
|
+
title: `${group} 📄 正文`,
|
|
767
|
+
badges: truncated ? [(0, react.createElement)("span", { style: badgeStyle("#f87171") }, truncated.reason === "timeout" ? "⏱ 响应超时截断" : "⚠ 流中断")] : [],
|
|
768
|
+
defaultOpen: true,
|
|
769
|
+
color: "#4ade80",
|
|
770
|
+
body: (0, react.createElement)("div", { style: { wordBreak: "break-word" } }, truncated ? (0, react.createElement)("div", { style: {
|
|
771
|
+
fontSize: 11,
|
|
772
|
+
color: "#f87171",
|
|
773
|
+
border: "1px solid #dc2626",
|
|
774
|
+
borderRadius: 3,
|
|
775
|
+
padding: "2px 6px",
|
|
776
|
+
display: "inline-block",
|
|
777
|
+
marginBottom: 3
|
|
778
|
+
} }, truncated.reason === "timeout" ? "⏱ 响应超时截断 — 思考已收到,正文未完成" : "⚠ 流中断 — 正文可能不完整") : null, renderMarkdown(message.content))
|
|
779
|
+
}));
|
|
780
|
+
}
|
|
781
|
+
if (liveAction !== void 0) children.push((0, react.createElement)("div", {
|
|
782
|
+
key: "live-action",
|
|
783
|
+
style: thinkingInlineStyle
|
|
784
|
+
}, (0, react.createElement)("div", { style: thinkingTagStyle }, `${group} 📋 行动·${toolViews.length + 1}`), (0, react.createElement)("div", { style: descriptionBodyStyle }, liveAction)));
|
|
785
|
+
return (0, react.createElement)("div", null, children);
|
|
786
|
+
}
|
|
787
|
+
function AdvisorGroupNodeView(props) {
|
|
788
|
+
const data = props.node.data;
|
|
789
|
+
const sessionId = data.sessionId;
|
|
790
|
+
const [live, setLive] = (0, react.useState)({});
|
|
791
|
+
const [contextCollapsed, setContextCollapsed] = (0, react.useState)(true);
|
|
792
|
+
const lastEventIdRef = (0, react.useRef)(0);
|
|
793
|
+
const lastBootIdRef = (0, react.useRef)("");
|
|
794
|
+
(0, react.useEffect)(() => {
|
|
795
|
+
if (!sessionId) return;
|
|
796
|
+
const es = new EventSource(`/advisor-group/stream?sessionId=${encodeURIComponent(sessionId)}&lastEventId=${lastEventIdRef.current}&bootId=${encodeURIComponent(lastBootIdRef.current)}&token=${encodeURIComponent(advisorGroupToken())}`);
|
|
797
|
+
es.onmessage = (event) => {
|
|
798
|
+
try {
|
|
799
|
+
const delta = JSON.parse(event.data);
|
|
800
|
+
const bootId = delta.bootId ?? "";
|
|
801
|
+
if (lastBootIdRef.current !== "" && bootId !== lastBootIdRef.current) {
|
|
802
|
+
lastBootIdRef.current = bootId;
|
|
803
|
+
lastEventIdRef.current = 0;
|
|
804
|
+
setLive({});
|
|
805
|
+
} else if (bootId) lastBootIdRef.current = bootId;
|
|
806
|
+
const eventId = Number(delta.eventId ?? 0);
|
|
807
|
+
if (eventId > 0 && eventId <= lastEventIdRef.current) return;
|
|
808
|
+
if (eventId > 0) lastEventIdRef.current = eventId;
|
|
809
|
+
const bucket = `${delta.advisorId}::${delta.round ?? 1}`;
|
|
810
|
+
setLive((prev) => {
|
|
811
|
+
const current = prev[bucket] ?? {
|
|
812
|
+
content: "",
|
|
813
|
+
thinking: "",
|
|
814
|
+
thinkingSegments: [],
|
|
815
|
+
committedLen: 0,
|
|
816
|
+
contentSegments: [],
|
|
817
|
+
committedContentLen: 0,
|
|
818
|
+
phase: "answer",
|
|
819
|
+
finalized: false,
|
|
820
|
+
toolSteps: []
|
|
821
|
+
};
|
|
822
|
+
let thinkingSegments = current.thinkingSegments;
|
|
823
|
+
let committedLen = current.committedLen;
|
|
824
|
+
let contentSegments = current.contentSegments;
|
|
825
|
+
let committedContentLen = current.committedContentLen;
|
|
826
|
+
if (delta.toolStep) {
|
|
827
|
+
const segment = current.thinking.slice(committedLen);
|
|
828
|
+
if (segment.trim()) thinkingSegments = [...current.thinkingSegments, segment];
|
|
829
|
+
committedLen = current.thinking.length;
|
|
830
|
+
const contentSegment = current.content.slice(committedContentLen);
|
|
831
|
+
if (contentSegment.trim()) contentSegments = [...current.contentSegments, contentSegment];
|
|
832
|
+
committedContentLen = current.content.length;
|
|
833
|
+
}
|
|
834
|
+
return {
|
|
835
|
+
...prev,
|
|
836
|
+
[bucket]: {
|
|
837
|
+
content: current.content + (delta.contentDelta ?? ""),
|
|
838
|
+
thinking: current.thinking + (delta.thinkingDelta ?? ""),
|
|
839
|
+
thinkingSegments,
|
|
840
|
+
committedLen,
|
|
841
|
+
contentSegments,
|
|
842
|
+
committedContentLen,
|
|
843
|
+
phase: delta.phase ?? current.phase,
|
|
844
|
+
finalized: current.finalized || delta.done === true,
|
|
845
|
+
toolSteps: delta.toolStep ? [...current.toolSteps, delta.toolStep] : current.toolSteps
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
});
|
|
849
|
+
} catch {}
|
|
850
|
+
};
|
|
851
|
+
es.addEventListener("resync", () => {
|
|
852
|
+
lastEventIdRef.current = 0;
|
|
853
|
+
setLive({});
|
|
854
|
+
});
|
|
855
|
+
return () => es.close();
|
|
856
|
+
}, [sessionId]);
|
|
857
|
+
const mergedMessages = data.messages.map((message) => {
|
|
858
|
+
if (message.role !== "advisor") return message;
|
|
859
|
+
const advisorId = message.advisorId ?? "";
|
|
860
|
+
const streamed = live[`${advisorId}::${message.round ?? 1}`];
|
|
861
|
+
if (!streamed) return message;
|
|
862
|
+
if (streamed.finalized) return message;
|
|
863
|
+
const liveBody = sanitizeClientContent(streamed.content.slice(streamed.committedContentLen));
|
|
864
|
+
const content = liveBody.trim() ? liveBody : sanitizeClientContent(message.content);
|
|
865
|
+
const thinking = (streamed.thinking?.length ?? 0) > (message.thinking?.length ?? 0) ? streamed.thinking : message.thinking;
|
|
866
|
+
const toolSteps = (streamed.toolSteps?.length ?? 0) > (message.toolSteps?.length ?? 0) ? streamed.toolSteps : message.toolSteps ?? [];
|
|
867
|
+
const liveSegments = streamed.thinkingSegments?.length ? streamed.thinkingSegments : [];
|
|
868
|
+
const baseSegments = message.thinkingSegments?.length ? message.thinkingSegments : message.thinking ? [message.thinking] : [];
|
|
869
|
+
const thinkingSegments = liveSegments.length ? liveSegments : baseSegments;
|
|
870
|
+
const liveActions = streamed.contentSegments?.length ? streamed.contentSegments.map((segment) => sanitizeClientContent(segment)) : [];
|
|
871
|
+
const baseActions = message.actionDescriptions?.length ? message.actionDescriptions : [];
|
|
872
|
+
const actionDescriptions = liveActions.length ? liveActions : baseActions;
|
|
873
|
+
return {
|
|
874
|
+
...message,
|
|
875
|
+
content,
|
|
876
|
+
thinking,
|
|
877
|
+
thinkingSegments,
|
|
878
|
+
toolSteps,
|
|
879
|
+
actionDescriptions,
|
|
880
|
+
...streamed.phase === "tool" ? {
|
|
881
|
+
liveBody,
|
|
882
|
+
livePhase: streamed.phase
|
|
883
|
+
} : {}
|
|
884
|
+
};
|
|
885
|
+
});
|
|
886
|
+
const shortId = data.sessionId.length > 8 ? data.sessionId.slice(0, 8) : data.sessionId;
|
|
887
|
+
const title = data.status === "completed" ? "ADVISOR GROUP · DONE" : data.status === "cancelled" ? "ADVISOR GROUP · STOPPED" : "ADVISOR GROUP · LIVE";
|
|
888
|
+
return (0, react.createElement)("div", {
|
|
889
|
+
style: rootStyle,
|
|
890
|
+
role: "log",
|
|
891
|
+
"aria-live": "polite"
|
|
892
|
+
}, (0, react.createElement)("div", { style: scanlineStyle }), (0, react.createElement)("div", { style: headerStyle }, (0, react.createElement)("span", { key: "title" }, title), (0, react.createElement)("span", {
|
|
893
|
+
key: "stop",
|
|
894
|
+
style: {
|
|
895
|
+
display: "flex",
|
|
896
|
+
alignItems: "center",
|
|
897
|
+
gap: 8
|
|
898
|
+
}
|
|
899
|
+
}, data.status === "running" ? (0, react.createElement)("button", {
|
|
900
|
+
type: "button",
|
|
901
|
+
onClick: () => {
|
|
902
|
+
fetch("/advisor-group/stop", {
|
|
903
|
+
method: "POST",
|
|
904
|
+
headers: {
|
|
905
|
+
"content-type": "application/json",
|
|
906
|
+
...authHeaders()
|
|
907
|
+
},
|
|
908
|
+
body: JSON.stringify({ sessionId })
|
|
909
|
+
}).catch(() => {});
|
|
910
|
+
},
|
|
911
|
+
style: {
|
|
912
|
+
cursor: "pointer",
|
|
913
|
+
border: "1px solid #e11d48",
|
|
914
|
+
background: "transparent",
|
|
915
|
+
color: "#fda4af",
|
|
916
|
+
borderRadius: 4,
|
|
917
|
+
padding: "2px 8px",
|
|
918
|
+
fontSize: 11
|
|
919
|
+
}
|
|
920
|
+
}, "⏹ 停止") : data.status === "cancelled" ? (0, react.createElement)("button", {
|
|
921
|
+
type: "button",
|
|
922
|
+
onClick: () => {
|
|
923
|
+
fetch("/advisor-group/resume", {
|
|
924
|
+
method: "POST",
|
|
925
|
+
headers: {
|
|
926
|
+
"content-type": "application/json",
|
|
927
|
+
...authHeaders()
|
|
928
|
+
},
|
|
929
|
+
body: JSON.stringify({ sessionId })
|
|
930
|
+
}).catch(() => {});
|
|
931
|
+
},
|
|
932
|
+
style: {
|
|
933
|
+
cursor: "pointer",
|
|
934
|
+
border: "1px solid #22c55e",
|
|
935
|
+
background: "transparent",
|
|
936
|
+
color: "#86efac",
|
|
937
|
+
borderRadius: 4,
|
|
938
|
+
padding: "2px 8px",
|
|
939
|
+
fontSize: 11
|
|
940
|
+
}
|
|
941
|
+
}, "▶ 继续聊天") : null, (0, react.createElement)("span", { key: "id" }, `#${shortId}`))), data.context ? (0, react.createElement)("div", { style: contextBoxStyle }, (0, react.createElement)("button", {
|
|
942
|
+
type: "button",
|
|
943
|
+
onClick: () => setContextCollapsed((value) => !value),
|
|
944
|
+
style: contextToggleStyle
|
|
945
|
+
}, `📋 项目背景 ${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, {
|
|
946
|
+
key: index,
|
|
947
|
+
message
|
|
948
|
+
})), data.status === "completed" && data.summary?.conclusion ? (0, react.createElement)("div", { style: {
|
|
949
|
+
borderTop: "1px dashed #2f9e44",
|
|
950
|
+
marginTop: 8,
|
|
951
|
+
paddingTop: 8
|
|
952
|
+
} }, (0, react.createElement)("div", { style: {
|
|
953
|
+
color: "#86efac",
|
|
954
|
+
fontWeight: 700,
|
|
955
|
+
marginBottom: 4
|
|
956
|
+
} }, "📌 综合结论"), (0, react.createElement)("div", null, renderMarkdown(data.summary.conclusion))) : null, data.status === "running" ? (0, react.createElement)("div", { style: waitingStyle }, "▊ AWAITING RESPONSES…") : null);
|
|
957
|
+
}
|
|
958
|
+
const settingsRootStyle = {
|
|
959
|
+
fontFamily: "'Inter', 'Segoe UI', system-ui, sans-serif",
|
|
960
|
+
fontSize: 13,
|
|
961
|
+
maxWidth: 720,
|
|
962
|
+
color: "inherit"
|
|
963
|
+
};
|
|
964
|
+
const settingsCardStyle = (open) => ({
|
|
965
|
+
boxSizing: "border-box",
|
|
966
|
+
border: "0.5px solid var(--dsw-alias-border-l4, var(--dsh-color-border, #3a3f4b))",
|
|
967
|
+
background: open ? "var(--dsw-alias-bg-layer-2, #191d26)" : "var(--dsw-alias-bg-layer-3, #151821)",
|
|
968
|
+
borderRadius: 16,
|
|
969
|
+
listStyle: "none",
|
|
970
|
+
transition: "border-color .16s, background .16s"
|
|
971
|
+
});
|
|
972
|
+
const settingsCardHeaderStyle = {
|
|
973
|
+
appearance: "none",
|
|
974
|
+
width: "100%",
|
|
975
|
+
font: "inherit",
|
|
976
|
+
color: "inherit",
|
|
977
|
+
textAlign: "left",
|
|
978
|
+
cursor: "pointer",
|
|
979
|
+
background: "transparent",
|
|
980
|
+
border: 0,
|
|
981
|
+
borderRadius: 12,
|
|
982
|
+
alignItems: "center",
|
|
983
|
+
gap: 12,
|
|
984
|
+
padding: "14px 16px",
|
|
985
|
+
display: "flex"
|
|
986
|
+
};
|
|
987
|
+
const settingsHeadTextStyle = {
|
|
988
|
+
flexDirection: "column",
|
|
989
|
+
flex: 1,
|
|
990
|
+
gap: 4,
|
|
991
|
+
minWidth: 0,
|
|
992
|
+
display: "flex"
|
|
993
|
+
};
|
|
994
|
+
const settingsCardNameStyle = {
|
|
995
|
+
color: "var(--dsw-alias-label-primary, #e6e9f0)",
|
|
996
|
+
fontSize: 15,
|
|
997
|
+
fontWeight: 600,
|
|
998
|
+
lineHeight: 1.4
|
|
999
|
+
};
|
|
1000
|
+
const settingsCardDescriptionStyle = {
|
|
1001
|
+
color: "var(--dsw-alias-label-tertiary, var(--dsh-color-muted, #8b90a0))",
|
|
1002
|
+
fontSize: 13,
|
|
1003
|
+
lineHeight: 1.5
|
|
1004
|
+
};
|
|
1005
|
+
const settingsCardPendingStyle = {
|
|
1006
|
+
background: "var(--dsw-alias-bg-module-platform, #2a2f3d)",
|
|
1007
|
+
color: "var(--dsw-alias-label-secondary, #aab2c5)",
|
|
1008
|
+
borderRadius: 999,
|
|
1009
|
+
padding: "1px 8px",
|
|
1010
|
+
fontSize: 11,
|
|
1011
|
+
fontWeight: 500,
|
|
1012
|
+
lineHeight: "17px",
|
|
1013
|
+
whiteSpace: "nowrap",
|
|
1014
|
+
flex: "none"
|
|
1015
|
+
};
|
|
1016
|
+
const settingsCardBodyStyle = {
|
|
1017
|
+
borderTop: "0.5px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
|
|
1018
|
+
margin: "0 16px",
|
|
1019
|
+
paddingBottom: 8
|
|
1020
|
+
};
|
|
1021
|
+
const settingsCardLoadingStyle = {
|
|
1022
|
+
color: "var(--dsw-alias-label-tertiary, var(--dsh-color-muted, #8b90a0))",
|
|
1023
|
+
margin: "12px 0",
|
|
1024
|
+
fontSize: 12,
|
|
1025
|
+
lineHeight: 1.5
|
|
1026
|
+
};
|
|
1027
|
+
const settingsCardFooterStyle = {
|
|
1028
|
+
borderTop: "0.5px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
|
|
1029
|
+
justifyContent: "flex-end",
|
|
1030
|
+
alignItems: "center",
|
|
1031
|
+
gap: 8,
|
|
1032
|
+
padding: "12px 0 4px",
|
|
1033
|
+
display: "flex",
|
|
1034
|
+
flexWrap: "wrap"
|
|
1035
|
+
};
|
|
1036
|
+
const settingsFooterButtonStyle = {
|
|
1037
|
+
appearance: "none",
|
|
1038
|
+
font: "inherit",
|
|
1039
|
+
cursor: "pointer",
|
|
1040
|
+
border: "1px solid transparent",
|
|
1041
|
+
borderRadius: 8,
|
|
1042
|
+
padding: "5px 14px",
|
|
1043
|
+
fontSize: 13,
|
|
1044
|
+
lineHeight: 1.5
|
|
1045
|
+
};
|
|
1046
|
+
/** Collapsible header shared by the loading and settled card states. */
|
|
1047
|
+
function settingsCardHeader(open, dirty, onToggle) {
|
|
1048
|
+
return (0, react.createElement)("button", {
|
|
1049
|
+
type: "button",
|
|
1050
|
+
style: settingsCardHeaderStyle,
|
|
1051
|
+
"aria-expanded": open,
|
|
1052
|
+
"aria-label": `${open ? "收起" : "展开"}设置:顾问群`,
|
|
1053
|
+
onClick: onToggle
|
|
1054
|
+
}, (0, react.createElement)("span", { style: settingsHeadTextStyle }, (0, react.createElement)("span", { style: settingsCardNameStyle }, "顾问群"), (0, react.createElement)("span", { style: settingsCardDescriptionStyle }, "多顾问模型咨询:触发条件、讨论轮次与顾问列表。")), dirty ? (0, react.createElement)("span", { style: settingsCardPendingStyle }, "未保存") : null, (0, react.createElement)("svg", {
|
|
1055
|
+
width: 14,
|
|
1056
|
+
height: 14,
|
|
1057
|
+
viewBox: "0 0 16 16",
|
|
1058
|
+
fill: "none",
|
|
1059
|
+
"aria-hidden": true,
|
|
1060
|
+
style: {
|
|
1061
|
+
flex: "none",
|
|
1062
|
+
transition: "transform .16s",
|
|
1063
|
+
transform: open ? "rotate(180deg)" : "none"
|
|
1064
|
+
}
|
|
1065
|
+
}, (0, react.createElement)("path", {
|
|
1066
|
+
d: "M3 6 L8 10.5 L13 6",
|
|
1067
|
+
stroke: "currentColor",
|
|
1068
|
+
strokeWidth: 1.5,
|
|
1069
|
+
strokeLinecap: "round",
|
|
1070
|
+
strokeLinejoin: "round"
|
|
1071
|
+
})));
|
|
1072
|
+
}
|
|
1073
|
+
const settingsSectionStyle = {
|
|
1074
|
+
border: "1px solid var(--dsh-color-border, #3a3f4b)",
|
|
1075
|
+
borderRadius: 8,
|
|
1076
|
+
padding: "10px 12px",
|
|
1077
|
+
marginBottom: 10,
|
|
1078
|
+
background: "transparent"
|
|
1079
|
+
};
|
|
1080
|
+
const settingsLabelStyle = {
|
|
1081
|
+
display: "block",
|
|
1082
|
+
fontSize: 12,
|
|
1083
|
+
marginBottom: 4,
|
|
1084
|
+
color: "var(--dsh-color-muted, #8b90a0)"
|
|
1085
|
+
};
|
|
1086
|
+
const settingsInputStyle = {
|
|
1087
|
+
width: "100%",
|
|
1088
|
+
boxSizing: "border-box",
|
|
1089
|
+
padding: "4px 6px",
|
|
1090
|
+
borderRadius: 4,
|
|
1091
|
+
border: "1px solid var(--dsh-color-border, #3a3f4b)",
|
|
1092
|
+
background: "transparent",
|
|
1093
|
+
color: "inherit",
|
|
1094
|
+
fontSize: 13,
|
|
1095
|
+
caretColor: "inherit"
|
|
1096
|
+
};
|
|
1097
|
+
const advisorCardStyle = {
|
|
1098
|
+
border: "1px solid var(--dsh-color-border, #3a3f4b)",
|
|
1099
|
+
borderRadius: 6,
|
|
1100
|
+
padding: 8,
|
|
1101
|
+
marginBottom: 8,
|
|
1102
|
+
display: "grid",
|
|
1103
|
+
gap: 6
|
|
1104
|
+
};
|
|
1105
|
+
const AVATAR_OPTIONS = [
|
|
1106
|
+
"🧠",
|
|
1107
|
+
"⚖️",
|
|
1108
|
+
"👨💻",
|
|
1109
|
+
"👩💻",
|
|
1110
|
+
"💼",
|
|
1111
|
+
"🏥",
|
|
1112
|
+
"📊",
|
|
1113
|
+
"🔬",
|
|
1114
|
+
"💰",
|
|
1115
|
+
"🌐",
|
|
1116
|
+
"🛠️",
|
|
1117
|
+
"📚",
|
|
1118
|
+
"🎯",
|
|
1119
|
+
"⚙️",
|
|
1120
|
+
"🚀",
|
|
1121
|
+
"🛡️"
|
|
1122
|
+
];
|
|
1123
|
+
function AdvisorGroupSettingsTab() {
|
|
1124
|
+
const [config, setConfig] = (0, react.useState)(null);
|
|
1125
|
+
const [providers, setProviders] = (0, react.useState)([]);
|
|
1126
|
+
const [revision, setRevision] = (0, react.useState)(0);
|
|
1127
|
+
const [customModes, setCustomModes] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
1128
|
+
const [fetchedModels, setFetchedModels] = (0, react.useState)({});
|
|
1129
|
+
const [fetchingModels, setFetchingModels] = (0, react.useState)({});
|
|
1130
|
+
const [testingConnection, setTestingConnection] = (0, react.useState)({});
|
|
1131
|
+
const [msg, setMsg] = (0, react.useState)("");
|
|
1132
|
+
const [error, setError] = (0, react.useState)("");
|
|
1133
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1134
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
1135
|
+
const [dailyGuard, setDailyGuard] = (0, react.useState)(null);
|
|
1136
|
+
const savedJsonRef = (0, react.useRef)("");
|
|
1137
|
+
const reload = async () => {
|
|
1138
|
+
setMsg("");
|
|
1139
|
+
setError("");
|
|
1140
|
+
try {
|
|
1141
|
+
const [configData, providerData] = await Promise.all([fetch("/advisor-group/config", { headers: authHeaders() }).then((r) => r.json()), fetch("/advisor-group/providers", { headers: authHeaders() }).then((r) => r.json())]);
|
|
1142
|
+
if (configData.ok && configData.config) {
|
|
1143
|
+
setConfig(configData.config);
|
|
1144
|
+
savedJsonRef.current = JSON.stringify(configData.config);
|
|
1145
|
+
if (typeof configData.revision === "number") setRevision(configData.revision);
|
|
1146
|
+
if (configData.dailyGuard) setDailyGuard(configData.dailyGuard);
|
|
1147
|
+
} else setError(configData.error ?? "加载配置失败");
|
|
1148
|
+
if (providerData.ok && providerData.providers) setProviders(providerData.providers);
|
|
1149
|
+
} catch (e) {
|
|
1150
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
(0, react.useEffect)(() => {
|
|
1154
|
+
reload();
|
|
1155
|
+
}, []);
|
|
1156
|
+
const dirty = config !== null && savedJsonRef.current !== "" && JSON.stringify(config) !== savedJsonRef.current;
|
|
1157
|
+
if (!config) return (0, react.createElement)("li", { style: settingsCardStyle(true) }, settingsCardHeader(true, false, () => setOpen((value) => !value)), (0, react.createElement)("div", { style: settingsCardBodyStyle }, (0, react.createElement)("p", { style: settingsCardLoadingStyle }, error || "加载中…")));
|
|
1158
|
+
const updateAdvisor = (index, patch) => {
|
|
1159
|
+
const advisors = config.advisors.map((advisor, i) => i === index ? {
|
|
1160
|
+
...advisor,
|
|
1161
|
+
...patch
|
|
1162
|
+
} : advisor);
|
|
1163
|
+
setConfig({
|
|
1164
|
+
...config,
|
|
1165
|
+
advisors
|
|
1166
|
+
});
|
|
1167
|
+
};
|
|
1168
|
+
const removeAdvisor = (index) => {
|
|
1169
|
+
const advisors = config.advisors.filter((_, i) => i !== index);
|
|
1170
|
+
setConfig({
|
|
1171
|
+
...config,
|
|
1172
|
+
advisors
|
|
1173
|
+
});
|
|
1174
|
+
};
|
|
1175
|
+
const addAdvisor = () => {
|
|
1176
|
+
setConfig({
|
|
1177
|
+
...config,
|
|
1178
|
+
advisors: [...config.advisors, {
|
|
1179
|
+
id: `advisor-${Date.now()}`,
|
|
1180
|
+
name: "新顾问",
|
|
1181
|
+
provider: "deepseek-official",
|
|
1182
|
+
model: "deepseek-v4-pro",
|
|
1183
|
+
systemPrompt: DEFAULT_ADVISOR_PROMPT
|
|
1184
|
+
}]
|
|
1185
|
+
});
|
|
1186
|
+
};
|
|
1187
|
+
const loadModels = async (index) => {
|
|
1188
|
+
const advisor = config.advisors[index];
|
|
1189
|
+
if (!advisor) return;
|
|
1190
|
+
setFetchingModels((prev) => ({
|
|
1191
|
+
...prev,
|
|
1192
|
+
[advisor.id]: true
|
|
1193
|
+
}));
|
|
1194
|
+
setError("");
|
|
1195
|
+
setMsg("");
|
|
1196
|
+
try {
|
|
1197
|
+
const data = await (await fetch("/advisor-group/models", {
|
|
1198
|
+
method: "POST",
|
|
1199
|
+
headers: {
|
|
1200
|
+
"content-type": "application/json",
|
|
1201
|
+
...authHeaders()
|
|
1202
|
+
},
|
|
1203
|
+
body: JSON.stringify({
|
|
1204
|
+
advisorId: advisor.id,
|
|
1205
|
+
provider: advisor.provider,
|
|
1206
|
+
baseURL: advisor.baseURL ?? "",
|
|
1207
|
+
apiKey: advisor.apiKey ?? "",
|
|
1208
|
+
apiKeyEnv: advisor.apiKeyEnv ?? "",
|
|
1209
|
+
protocol: advisor.protocol ?? "openai"
|
|
1210
|
+
})
|
|
1211
|
+
})).json();
|
|
1212
|
+
if (data.ok && data.models) {
|
|
1213
|
+
setFetchedModels((prev) => ({
|
|
1214
|
+
...prev,
|
|
1215
|
+
[advisor.id]: data.models ?? []
|
|
1216
|
+
}));
|
|
1217
|
+
setMsg(`已获取 ${advisor.name} 的模型列表(${data.models.length} 个)`);
|
|
1218
|
+
} else setError(data.error ?? "获取模型列表失败");
|
|
1219
|
+
} catch (e) {
|
|
1220
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
1221
|
+
} finally {
|
|
1222
|
+
setFetchingModels((prev) => ({
|
|
1223
|
+
...prev,
|
|
1224
|
+
[advisor.id]: false
|
|
1225
|
+
}));
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
const testConnection = async (index) => {
|
|
1229
|
+
const advisor = config.advisors[index];
|
|
1230
|
+
if (!advisor) return;
|
|
1231
|
+
setTestingConnection((prev) => ({
|
|
1232
|
+
...prev,
|
|
1233
|
+
[advisor.id]: true
|
|
1234
|
+
}));
|
|
1235
|
+
setError("");
|
|
1236
|
+
setMsg("");
|
|
1237
|
+
try {
|
|
1238
|
+
const data = await (await fetch("/advisor-group/test-connection", {
|
|
1239
|
+
method: "POST",
|
|
1240
|
+
headers: {
|
|
1241
|
+
"content-type": "application/json",
|
|
1242
|
+
...authHeaders()
|
|
1243
|
+
},
|
|
1244
|
+
body: JSON.stringify({
|
|
1245
|
+
advisorId: advisor.id,
|
|
1246
|
+
provider: advisor.provider,
|
|
1247
|
+
baseURL: advisor.baseURL ?? "",
|
|
1248
|
+
apiKey: advisor.apiKey ?? "",
|
|
1249
|
+
apiKeyEnv: advisor.apiKeyEnv ?? "",
|
|
1250
|
+
protocol: advisor.protocol ?? "openai"
|
|
1251
|
+
})
|
|
1252
|
+
})).json();
|
|
1253
|
+
if (data.ok) setMsg(`✅ ${advisor.name} 连接成功${data.message ? `:${data.message}` : ""}`);
|
|
1254
|
+
else setError(`❌ ${advisor.name} 连接失败:${data.error ?? "未知错误"}`);
|
|
1255
|
+
} catch (e) {
|
|
1256
|
+
setError(`❌ ${advisor.name} 连接失败:${e instanceof Error ? e.message : String(e)}`);
|
|
1257
|
+
} finally {
|
|
1258
|
+
setTestingConnection((prev) => ({
|
|
1259
|
+
...prev,
|
|
1260
|
+
[advisor.id]: false
|
|
1261
|
+
}));
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
const save = async () => {
|
|
1265
|
+
setMsg("");
|
|
1266
|
+
setError("");
|
|
1267
|
+
setSaving(true);
|
|
1268
|
+
try {
|
|
1269
|
+
const configPayload = {
|
|
1270
|
+
...config,
|
|
1271
|
+
advisors: config.advisors.map(({ apiKeyMetaByProvider: _meta, ...rest }) => rest)
|
|
1272
|
+
};
|
|
1273
|
+
const data = await (await fetch("/advisor-group/config", {
|
|
1274
|
+
method: "POST",
|
|
1275
|
+
headers: {
|
|
1276
|
+
"content-type": "application/json",
|
|
1277
|
+
...authHeaders()
|
|
1278
|
+
},
|
|
1279
|
+
body: JSON.stringify({
|
|
1280
|
+
config: configPayload,
|
|
1281
|
+
expectedRevision: revision
|
|
1282
|
+
})
|
|
1283
|
+
})).json();
|
|
1284
|
+
if (data.ok) {
|
|
1285
|
+
if (data.config) {
|
|
1286
|
+
setConfig(data.config);
|
|
1287
|
+
savedJsonRef.current = JSON.stringify(data.config);
|
|
1288
|
+
} else savedJsonRef.current = JSON.stringify(configPayload);
|
|
1289
|
+
if (typeof data.revision === "number") setRevision(data.revision);
|
|
1290
|
+
setMsg("配置已保存");
|
|
1291
|
+
setError("");
|
|
1292
|
+
} else setError(data.error ?? "保存失败");
|
|
1293
|
+
} catch (e) {
|
|
1294
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
1295
|
+
} finally {
|
|
1296
|
+
setSaving(false);
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
const label = (text) => (0, react.createElement)("label", { style: settingsLabelStyle }, text);
|
|
1300
|
+
return (0, react.createElement)("li", { style: settingsCardStyle(open) }, settingsCardHeader(open, dirty, () => setOpen((value) => !value)), open ? (0, react.createElement)("div", { style: settingsCardBodyStyle }, (0, react.createElement)("div", { style: settingsRootStyle }, (0, react.createElement)("div", { style: settingsSectionStyle }, (0, react.createElement)("div", { style: {
|
|
1301
|
+
display: "flex",
|
|
1302
|
+
alignItems: "center",
|
|
1303
|
+
gap: 8
|
|
1304
|
+
} }, (0, react.createElement)("input", {
|
|
1305
|
+
type: "checkbox",
|
|
1306
|
+
checked: config.enabled,
|
|
1307
|
+
onChange: (e) => setConfig({
|
|
1308
|
+
...config,
|
|
1309
|
+
enabled: e.target.checked
|
|
1310
|
+
})
|
|
1311
|
+
}), label("启用顾问群")), (0, react.createElement)("div", { style: {
|
|
1312
|
+
display: "flex",
|
|
1313
|
+
alignItems: "center",
|
|
1314
|
+
gap: 8,
|
|
1315
|
+
marginTop: 4
|
|
1316
|
+
} }, (0, react.createElement)("input", {
|
|
1317
|
+
type: "checkbox",
|
|
1318
|
+
checked: config.quota.enabled,
|
|
1319
|
+
onChange: (e) => setConfig({
|
|
1320
|
+
...config,
|
|
1321
|
+
quota: {
|
|
1322
|
+
...config.quota,
|
|
1323
|
+
enabled: e.target.checked
|
|
1324
|
+
}
|
|
1325
|
+
})
|
|
1326
|
+
}), label("启用每日咨询上限(成本安全阀)")), (0, react.createElement)("div", { style: { marginTop: 6 } }, label(`每日上限次数(${config.quota.enabled ? "生效中" : "已关闭,不拦截"})`), (0, react.createElement)("input", {
|
|
1327
|
+
type: "number",
|
|
1328
|
+
min: 1,
|
|
1329
|
+
max: 1e5,
|
|
1330
|
+
value: String(config.quota.maxPerDay),
|
|
1331
|
+
disabled: !config.quota.enabled,
|
|
1332
|
+
onChange: (e) => setConfig({
|
|
1333
|
+
...config,
|
|
1334
|
+
quota: {
|
|
1335
|
+
...config.quota,
|
|
1336
|
+
maxPerDay: Math.max(1, Number(e.target.value))
|
|
1337
|
+
}
|
|
1338
|
+
}),
|
|
1339
|
+
style: {
|
|
1340
|
+
...settingsInputStyle,
|
|
1341
|
+
width: 120,
|
|
1342
|
+
marginTop: 4
|
|
1343
|
+
}
|
|
1344
|
+
}))), (0, react.createElement)("div", { style: settingsSectionStyle }, dailyGuard ? (0, react.createElement)("div", { style: {
|
|
1345
|
+
fontSize: 12,
|
|
1346
|
+
color: !dailyGuard.enabled || dailyGuard.remaining > 0 ? "var(--dsh-color-muted, #8b90a0)" : "#e11d48"
|
|
1347
|
+
} }, 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: {
|
|
1348
|
+
display: "grid",
|
|
1349
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
|
|
1350
|
+
gap: 8
|
|
1351
|
+
} }, (0, react.createElement)("div", null, label("讨论轮数上限"), (0, react.createElement)("input", {
|
|
1352
|
+
type: "number",
|
|
1353
|
+
min: 0,
|
|
1354
|
+
value: String(config.discussion.maxRounds),
|
|
1355
|
+
onChange: (e) => setConfig({
|
|
1356
|
+
...config,
|
|
1357
|
+
discussion: {
|
|
1358
|
+
...config.discussion,
|
|
1359
|
+
maxRounds: Number(e.target.value)
|
|
1360
|
+
}
|
|
1361
|
+
}),
|
|
1362
|
+
style: settingsInputStyle
|
|
1363
|
+
})), (0, react.createElement)("div", null, label("单次最多顾问数"), (0, react.createElement)("input", {
|
|
1364
|
+
type: "number",
|
|
1365
|
+
min: 1,
|
|
1366
|
+
value: String(config.discussion.maxAdvisorsPerCall ?? 3),
|
|
1367
|
+
onChange: (e) => setConfig({
|
|
1368
|
+
...config,
|
|
1369
|
+
discussion: {
|
|
1370
|
+
...config.discussion,
|
|
1371
|
+
maxAdvisorsPerCall: Math.max(1, Number(e.target.value))
|
|
1372
|
+
}
|
|
1373
|
+
}),
|
|
1374
|
+
style: settingsInputStyle
|
|
1375
|
+
})), (0, react.createElement)("div", null, label("单顾问超时(毫秒)"), (0, react.createElement)("input", {
|
|
1376
|
+
type: "number",
|
|
1377
|
+
min: 1e3,
|
|
1378
|
+
max: 6e5,
|
|
1379
|
+
step: 1e3,
|
|
1380
|
+
value: String(config.discussion.advisorTimeoutMs ?? 6e5),
|
|
1381
|
+
onChange: (e) => setConfig({
|
|
1382
|
+
...config,
|
|
1383
|
+
discussion: {
|
|
1384
|
+
...config.discussion,
|
|
1385
|
+
advisorTimeoutMs: Math.max(1e3, Number(e.target.value))
|
|
1386
|
+
}
|
|
1387
|
+
}),
|
|
1388
|
+
style: settingsInputStyle
|
|
1389
|
+
})), (0, react.createElement)("div", null, label("驱动模型生成超时(毫秒)"), (0, react.createElement)("input", {
|
|
1390
|
+
type: "number",
|
|
1391
|
+
min: 1e3,
|
|
1392
|
+
max: 12e5,
|
|
1393
|
+
step: 1e3,
|
|
1394
|
+
value: String(config.discussion.driverTimeoutMs ?? 6e5),
|
|
1395
|
+
onChange: (e) => setConfig({
|
|
1396
|
+
...config,
|
|
1397
|
+
discussion: {
|
|
1398
|
+
...config.discussion,
|
|
1399
|
+
driverTimeoutMs: Math.max(1e3, Number(e.target.value))
|
|
1400
|
+
}
|
|
1401
|
+
}),
|
|
1402
|
+
style: settingsInputStyle
|
|
1403
|
+
}))), (0, react.createElement)("div", { style: { marginTop: 8 } }, label("置信度阈值(0-1,低于此值升级顾问群)"), (0, react.createElement)("input", {
|
|
1404
|
+
type: "number",
|
|
1405
|
+
min: 0,
|
|
1406
|
+
max: 1,
|
|
1407
|
+
step: .05,
|
|
1408
|
+
value: String(config.trigger.confidenceThreshold ?? .6),
|
|
1409
|
+
onChange: (e) => setConfig({
|
|
1410
|
+
...config,
|
|
1411
|
+
trigger: {
|
|
1412
|
+
...config.trigger,
|
|
1413
|
+
confidenceThreshold: Number(e.target.value)
|
|
1414
|
+
}
|
|
1415
|
+
}),
|
|
1416
|
+
style: settingsInputStyle
|
|
1417
|
+
}))), (0, react.createElement)("div", { style: settingsSectionStyle }, (0, react.createElement)("div", { style: {
|
|
1418
|
+
display: "flex",
|
|
1419
|
+
alignItems: "center",
|
|
1420
|
+
gap: 8
|
|
1421
|
+
} }, (0, react.createElement)("input", {
|
|
1422
|
+
type: "checkbox",
|
|
1423
|
+
checked: config.discussion.autoDeepen,
|
|
1424
|
+
onChange: (e) => setConfig({
|
|
1425
|
+
...config,
|
|
1426
|
+
discussion: {
|
|
1427
|
+
...config.discussion,
|
|
1428
|
+
autoDeepen: e.target.checked
|
|
1429
|
+
}
|
|
1430
|
+
})
|
|
1431
|
+
}), label("自动多轮深挖(每轮后驱动模型生成深入追问,顾问按 A→B→C 顺序接力,直到轮数上限)")), (0, react.createElement)("div", { style: {
|
|
1432
|
+
display: "flex",
|
|
1433
|
+
alignItems: "center",
|
|
1434
|
+
gap: 8,
|
|
1435
|
+
marginTop: 4
|
|
1436
|
+
} }, (0, react.createElement)("input", {
|
|
1437
|
+
type: "checkbox",
|
|
1438
|
+
checked: config.trigger.requireClassifier,
|
|
1439
|
+
onChange: (e) => setConfig({
|
|
1440
|
+
...config,
|
|
1441
|
+
trigger: {
|
|
1442
|
+
...config.trigger,
|
|
1443
|
+
requireClassifier: e.target.checked
|
|
1444
|
+
}
|
|
1445
|
+
})
|
|
1446
|
+
}), label("启用前置分类器")), (0, react.createElement)("div", { style: {
|
|
1447
|
+
display: "flex",
|
|
1448
|
+
alignItems: "center",
|
|
1449
|
+
gap: 8,
|
|
1450
|
+
marginTop: 4
|
|
1451
|
+
} }, (0, react.createElement)("input", {
|
|
1452
|
+
type: "checkbox",
|
|
1453
|
+
checked: config.trigger.allowWebFallback,
|
|
1454
|
+
onChange: (e) => setConfig({
|
|
1455
|
+
...config,
|
|
1456
|
+
trigger: {
|
|
1457
|
+
...config.trigger,
|
|
1458
|
+
allowWebFallback: e.target.checked
|
|
1459
|
+
}
|
|
1460
|
+
})
|
|
1461
|
+
}), label("分类器建议时允许返回联网搜索提示"))), (0, react.createElement)("div", { style: settingsSectionStyle }, (0, react.createElement)("div", { style: {
|
|
1462
|
+
display: "flex",
|
|
1463
|
+
justifyContent: "space-between",
|
|
1464
|
+
alignItems: "center",
|
|
1465
|
+
marginBottom: 8
|
|
1466
|
+
} }, (0, react.createElement)("strong", null, `顾问列表(${config.advisors.length})`), (0, react.createElement)("button", {
|
|
1467
|
+
type: "button",
|
|
1468
|
+
onClick: addAdvisor,
|
|
1469
|
+
style: { cursor: "pointer" }
|
|
1470
|
+
}, "+ 添加顾问")), config.advisors.map((advisor, index) => (0, react.createElement)("div", {
|
|
1471
|
+
key: advisor.id,
|
|
1472
|
+
style: advisorCardStyle
|
|
1473
|
+
}, (0, react.createElement)("div", { style: {
|
|
1474
|
+
display: "flex",
|
|
1475
|
+
justifyContent: "space-between",
|
|
1476
|
+
gap: 8
|
|
1477
|
+
} }, (0, react.createElement)("strong", null, advisor.name || advisor.id), (0, react.createElement)("button", {
|
|
1478
|
+
type: "button",
|
|
1479
|
+
onClick: () => removeAdvisor(index),
|
|
1480
|
+
style: {
|
|
1481
|
+
cursor: "pointer",
|
|
1482
|
+
color: "#e11d48"
|
|
1483
|
+
}
|
|
1484
|
+
}, "删除")), label(`ID · ${advisor.id}`), (0, react.createElement)("input", {
|
|
1485
|
+
value: advisor.name,
|
|
1486
|
+
onChange: (e) => updateAdvisor(index, { name: e.target.value }),
|
|
1487
|
+
style: settingsInputStyle
|
|
1488
|
+
}), label("头像"), (0, react.createElement)("div", { style: {
|
|
1489
|
+
display: "flex",
|
|
1490
|
+
flexWrap: "wrap",
|
|
1491
|
+
gap: 4,
|
|
1492
|
+
marginTop: 2
|
|
1493
|
+
} }, (0, react.createElement)("button", {
|
|
1494
|
+
type: "button",
|
|
1495
|
+
onClick: () => updateAdvisor(index, { avatar: void 0 }),
|
|
1496
|
+
style: {
|
|
1497
|
+
cursor: "pointer",
|
|
1498
|
+
fontSize: 12,
|
|
1499
|
+
padding: "2px 6px",
|
|
1500
|
+
border: advisor.avatar === void 0 || advisor.avatar === "" ? "2px solid #4ade80" : "1px solid var(--dsh-color-border, #333)",
|
|
1501
|
+
background: "transparent"
|
|
1502
|
+
}
|
|
1503
|
+
}, "无"), AVATAR_OPTIONS.map((emoji) => (0, react.createElement)("button", {
|
|
1504
|
+
key: emoji,
|
|
1505
|
+
type: "button",
|
|
1506
|
+
onClick: () => updateAdvisor(index, { avatar: emoji }),
|
|
1507
|
+
style: {
|
|
1508
|
+
cursor: "pointer",
|
|
1509
|
+
fontSize: 18,
|
|
1510
|
+
padding: "2px 6px",
|
|
1511
|
+
border: advisor.avatar === emoji ? "2px solid #4ade80" : "1px solid var(--dsh-color-border, #333)",
|
|
1512
|
+
background: advisor.avatar === emoji ? "rgba(74,222,128,.15)" : "transparent"
|
|
1513
|
+
}
|
|
1514
|
+
}, emoji))), (0, react.createElement)("input", {
|
|
1515
|
+
placeholder: "自定义 emoji(可选)",
|
|
1516
|
+
value: advisor.avatar ?? "",
|
|
1517
|
+
onChange: (e) => updateAdvisor(index, { avatar: e.target.value }),
|
|
1518
|
+
style: {
|
|
1519
|
+
...settingsInputStyle,
|
|
1520
|
+
marginTop: 4
|
|
1521
|
+
}
|
|
1522
|
+
}), label("供应商"), (0, react.createElement)("select", {
|
|
1523
|
+
"aria-label": "供应商",
|
|
1524
|
+
value: customModes.has(advisor.id) || !providers.some((p) => p.id === advisor.provider) ? "__custom__" : advisor.provider,
|
|
1525
|
+
onChange: (e) => {
|
|
1526
|
+
const value = e.target.value;
|
|
1527
|
+
if (value === "__custom__") {
|
|
1528
|
+
const next = new Set(customModes);
|
|
1529
|
+
next.add(advisor.id);
|
|
1530
|
+
setCustomModes(next);
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
const next = new Set(customModes);
|
|
1534
|
+
next.delete(advisor.id);
|
|
1535
|
+
setCustomModes(next);
|
|
1536
|
+
const provider = providers.find((p) => p.id === value);
|
|
1537
|
+
if (provider?.kind === "llm") updateAdvisor(index, {
|
|
1538
|
+
provider: value,
|
|
1539
|
+
baseURL: void 0,
|
|
1540
|
+
apiKey: "",
|
|
1541
|
+
clearApiKey: false,
|
|
1542
|
+
apiKeyEnv: void 0,
|
|
1543
|
+
protocol: void 0
|
|
1544
|
+
});
|
|
1545
|
+
else updateAdvisor(index, {
|
|
1546
|
+
provider: value,
|
|
1547
|
+
apiKey: "",
|
|
1548
|
+
clearApiKey: false,
|
|
1549
|
+
...provider?.baseURL ? { baseURL: provider.baseURL } : {},
|
|
1550
|
+
...provider?.apiKeyEnv ? { apiKeyEnv: provider.apiKeyEnv } : {},
|
|
1551
|
+
...provider?.protocol ? { protocol: provider.protocol } : {}
|
|
1552
|
+
});
|
|
1553
|
+
},
|
|
1554
|
+
style: settingsInputStyle
|
|
1555
|
+
}, (0, react.createElement)("option", {
|
|
1556
|
+
value: "",
|
|
1557
|
+
disabled: true
|
|
1558
|
+
}, "请选择供应商"), providers.map((provider) => (0, react.createElement)("option", {
|
|
1559
|
+
key: provider.id,
|
|
1560
|
+
value: provider.id
|
|
1561
|
+
}, `${provider.kind === "llm" ? "DSH 内置" : "预设"} · ${provider.name}(${provider.id})`)), (0, react.createElement)("option", { value: "__custom__" }, "自定义供应商…")), customModes.has(advisor.id) || !providers.some((p) => p.id === advisor.provider) ? (0, react.createElement)("input", {
|
|
1562
|
+
placeholder: "自定义 provider 标识",
|
|
1563
|
+
value: advisor.provider,
|
|
1564
|
+
onChange: (e) => updateAdvisor(index, { provider: e.target.value }),
|
|
1565
|
+
style: {
|
|
1566
|
+
...settingsInputStyle,
|
|
1567
|
+
marginTop: 4
|
|
1568
|
+
}
|
|
1569
|
+
}) : null, label("模型"), (0, react.createElement)("div", { style: {
|
|
1570
|
+
display: "flex",
|
|
1571
|
+
gap: 4
|
|
1572
|
+
} }, (0, react.createElement)("input", {
|
|
1573
|
+
"aria-label": "模型",
|
|
1574
|
+
list: `advisor-models-${index}`,
|
|
1575
|
+
placeholder: "model",
|
|
1576
|
+
value: advisor.model,
|
|
1577
|
+
onChange: (e) => updateAdvisor(index, { model: e.target.value }),
|
|
1578
|
+
style: {
|
|
1579
|
+
...settingsInputStyle,
|
|
1580
|
+
marginTop: 4,
|
|
1581
|
+
border: advisor.model && advisor.model.trim() ? void 0 : "1px solid #e11d48"
|
|
1582
|
+
}
|
|
1583
|
+
}), (0, react.createElement)("button", {
|
|
1584
|
+
type: "button",
|
|
1585
|
+
onClick: () => void loadModels(index),
|
|
1586
|
+
disabled: !!fetchingModels[advisor.id],
|
|
1587
|
+
style: {
|
|
1588
|
+
cursor: "pointer",
|
|
1589
|
+
marginTop: 4,
|
|
1590
|
+
whiteSpace: "nowrap"
|
|
1591
|
+
}
|
|
1592
|
+
}, fetchingModels[advisor.id] ? "获取中…" : "获取模型列表"), (0, react.createElement)("button", {
|
|
1593
|
+
type: "button",
|
|
1594
|
+
onClick: () => void testConnection(index),
|
|
1595
|
+
disabled: !!testingConnection[advisor.id],
|
|
1596
|
+
style: {
|
|
1597
|
+
cursor: "pointer",
|
|
1598
|
+
marginTop: 4,
|
|
1599
|
+
whiteSpace: "nowrap"
|
|
1600
|
+
}
|
|
1601
|
+
}, testingConnection[advisor.id] ? "测试中…" : "测试连接")), !advisor.model?.trim() ? (0, react.createElement)("div", { style: {
|
|
1602
|
+
color: "#e11d48",
|
|
1603
|
+
fontSize: 12,
|
|
1604
|
+
marginTop: 2
|
|
1605
|
+
} }, "⚠️ 模型未配置,ask_advisors 将跳过整轮咨询。") : null, (0, react.createElement)("datalist", { id: `advisor-models-${index}` }, [...fetchedModels[advisor.id] ?? providers.find((p) => p.id === advisor.provider)?.models ?? [], ...advisor.model && !(fetchedModels[advisor.id] ?? providers.find((p) => p.id === advisor.provider)?.models ?? []).includes(advisor.model) ? [advisor.model] : []].map((model) => (0, react.createElement)("option", {
|
|
1606
|
+
key: model,
|
|
1607
|
+
value: model
|
|
1608
|
+
}, model))), (fetchedModels[advisor.id] ?? []).length > 0 ? (0, react.createElement)("div", { style: {
|
|
1609
|
+
maxHeight: 150,
|
|
1610
|
+
overflowY: "auto",
|
|
1611
|
+
border: "1px solid var(--dsh-color-border, #333)",
|
|
1612
|
+
borderRadius: 4,
|
|
1613
|
+
marginTop: 4
|
|
1614
|
+
} }, (fetchedModels[advisor.id] ?? []).map((model) => (0, react.createElement)("button", {
|
|
1615
|
+
key: model,
|
|
1616
|
+
type: "button",
|
|
1617
|
+
onClick: () => updateAdvisor(index, { model }),
|
|
1618
|
+
style: {
|
|
1619
|
+
display: "block",
|
|
1620
|
+
width: "100%",
|
|
1621
|
+
textAlign: "left",
|
|
1622
|
+
cursor: "pointer",
|
|
1623
|
+
padding: "3px 6px",
|
|
1624
|
+
background: "transparent",
|
|
1625
|
+
border: "none",
|
|
1626
|
+
color: "inherit",
|
|
1627
|
+
fontSize: 12
|
|
1628
|
+
}
|
|
1629
|
+
}, model))) : null, ...providers.find((p) => p.id === advisor.provider)?.kind === "llm" ? [(0, react.createElement)("div", { style: {
|
|
1630
|
+
fontSize: 12,
|
|
1631
|
+
color: "var(--dsh-color-muted, #8b90a0)",
|
|
1632
|
+
marginTop: 4
|
|
1633
|
+
} }, "✅ DSH 内置 Provider:复用 DSH 已配置凭据,无需填写 API 地址/Key。")] : (() => {
|
|
1634
|
+
const keyMeta = advisor.apiKeyMetaByProvider?.[advisor.provider];
|
|
1635
|
+
const keyConfigured = keyMeta?.configured === true;
|
|
1636
|
+
return [
|
|
1637
|
+
label("API 地址"),
|
|
1638
|
+
(0, react.createElement)("input", {
|
|
1639
|
+
"aria-label": "API 地址",
|
|
1640
|
+
placeholder: "https://api.example.com/v1",
|
|
1641
|
+
value: advisor.baseURL ?? "",
|
|
1642
|
+
onChange: (e) => updateAdvisor(index, { baseURL: e.target.value }),
|
|
1643
|
+
style: settingsInputStyle
|
|
1644
|
+
}),
|
|
1645
|
+
(0, react.createElement)("div", { style: {
|
|
1646
|
+
display: "flex",
|
|
1647
|
+
alignItems: "flex-end",
|
|
1648
|
+
gap: 6,
|
|
1649
|
+
flexWrap: "wrap"
|
|
1650
|
+
} }, (0, react.createElement)("div", { style: {
|
|
1651
|
+
flex: 1,
|
|
1652
|
+
minWidth: 200
|
|
1653
|
+
} }, (0, react.createElement)("div", { style: {
|
|
1654
|
+
display: "flex",
|
|
1655
|
+
alignItems: "center",
|
|
1656
|
+
gap: 8
|
|
1657
|
+
} }, label("API Key"), (0, react.createElement)("span", { style: {
|
|
1658
|
+
fontSize: 11,
|
|
1659
|
+
padding: "1px 8px",
|
|
1660
|
+
borderRadius: 999,
|
|
1661
|
+
background: keyConfigured ? "rgba(22,163,74,.15)" : "var(--dsh-color-muted, #8b90a0)",
|
|
1662
|
+
color: keyConfigured ? "#16a34a" : "inherit"
|
|
1663
|
+
} }, keyConfigured ? `已配置 ••••${keyMeta?.last4 ?? ""}` : "未配置")), (0, react.createElement)("input", {
|
|
1664
|
+
type: "password",
|
|
1665
|
+
"aria-label": "API Key",
|
|
1666
|
+
placeholder: keyConfigured ? "输入新 Key 可覆盖;留空保持不变" : "sk-...",
|
|
1667
|
+
value: advisor.apiKey ?? "",
|
|
1668
|
+
onChange: (e) => updateAdvisor(index, {
|
|
1669
|
+
apiKey: e.target.value,
|
|
1670
|
+
clearApiKey: false
|
|
1671
|
+
}),
|
|
1672
|
+
style: {
|
|
1673
|
+
...settingsInputStyle,
|
|
1674
|
+
marginTop: 4
|
|
1675
|
+
}
|
|
1676
|
+
})), keyConfigured ? (0, react.createElement)("button", {
|
|
1677
|
+
type: "button",
|
|
1678
|
+
disabled: saving,
|
|
1679
|
+
onClick: () => updateAdvisor(index, {
|
|
1680
|
+
apiKey: "",
|
|
1681
|
+
clearApiKey: true
|
|
1682
|
+
}),
|
|
1683
|
+
style: {
|
|
1684
|
+
cursor: "pointer",
|
|
1685
|
+
border: "1px solid var(--dsh-color-border, #3a3f4b)",
|
|
1686
|
+
background: "transparent",
|
|
1687
|
+
color: "inherit",
|
|
1688
|
+
borderRadius: 4,
|
|
1689
|
+
padding: "4px 10px",
|
|
1690
|
+
fontSize: 12,
|
|
1691
|
+
whiteSpace: "nowrap"
|
|
1692
|
+
}
|
|
1693
|
+
}, "清除") : null),
|
|
1694
|
+
(0, react.createElement)("div", { style: {
|
|
1695
|
+
fontSize: 12,
|
|
1696
|
+
color: "var(--dsh-color-muted, #8b90a0)",
|
|
1697
|
+
marginTop: 2
|
|
1698
|
+
} }, keyConfigured ? "已保存该供应商的 API Key(仅显示末 4 位);留空保存保持原 Key,输入新 Key 覆盖。" : "直接填入 API Key,保存到本地配置。"),
|
|
1699
|
+
label("协议"),
|
|
1700
|
+
(0, react.createElement)("select", {
|
|
1701
|
+
"aria-label": "协议",
|
|
1702
|
+
value: advisor.protocol ?? "openai",
|
|
1703
|
+
onChange: (e) => updateAdvisor(index, { protocol: e.target.value }),
|
|
1704
|
+
style: settingsInputStyle
|
|
1705
|
+
}, (0, react.createElement)("option", { value: "openai" }, "OpenAI 兼容"), (0, react.createElement)("option", { value: "anthropic" }, "Anthropic"), (0, react.createElement)("option", { value: "gemini" }, "Gemini"))
|
|
1706
|
+
];
|
|
1707
|
+
})(), (0, react.createElement)("textarea", {
|
|
1708
|
+
"aria-label": "systemPrompt",
|
|
1709
|
+
placeholder: "systemPrompt",
|
|
1710
|
+
rows: 2,
|
|
1711
|
+
value: advisor.systemPrompt,
|
|
1712
|
+
onChange: (e) => updateAdvisor(index, { systemPrompt: e.target.value }),
|
|
1713
|
+
style: {
|
|
1714
|
+
...settingsInputStyle,
|
|
1715
|
+
marginTop: 4,
|
|
1716
|
+
resize: "vertical"
|
|
1717
|
+
}
|
|
1718
|
+
}), (() => {
|
|
1719
|
+
const supportsTools = !providers.some((p) => p.id === advisor.provider && p.kind === "llm") || Boolean(advisor.baseURL || advisor.apiKeyEnv);
|
|
1720
|
+
const globalDefault = config.discussion.advisorTools ?? "readonly";
|
|
1721
|
+
const mode = advisor.tools ?? globalDefault;
|
|
1722
|
+
const modeLabel = {
|
|
1723
|
+
readonly: "只读工具",
|
|
1724
|
+
all: "全部会话工具(含可写,慎用)",
|
|
1725
|
+
off: "关闭"
|
|
1726
|
+
};
|
|
1727
|
+
const toolPromptNote = "工具启用时,系统提示词会自动追加:『对于你不了解、不熟悉的内容,尤其是项目背景、代码细节、仓库状态等不在知识范围内的情况,请优先使用联网工具(如 web_search、web_fetch)搜索核实后再作答,不要编造未核实的事实。』";
|
|
1728
|
+
if (supportsTools) return [
|
|
1729
|
+
label(`顾问工具调用范围(未单独设置时跟随全局默认:${modeLabel[globalDefault] ?? globalDefault})`),
|
|
1730
|
+
(0, react.createElement)("select", {
|
|
1731
|
+
"aria-label": "顾问工具调用范围",
|
|
1732
|
+
value: mode,
|
|
1733
|
+
onChange: (e) => updateAdvisor(index, { tools: e.target.value }),
|
|
1734
|
+
style: settingsInputStyle
|
|
1735
|
+
}, (0, react.createElement)("option", { value: "readonly" }, "只读工具"), (0, react.createElement)("option", { value: "all" }, "全部会话工具(含可写,慎用)"), (0, react.createElement)("option", { value: "off" }, "关闭")),
|
|
1736
|
+
mode === "off" ? null : (0, react.createElement)("div", { style: {
|
|
1737
|
+
fontSize: 12,
|
|
1738
|
+
color: "var(--dsh-color-muted, #8b90a0)",
|
|
1739
|
+
border: "1px dashed var(--dsh-color-border, #3a3f4b)",
|
|
1740
|
+
borderRadius: 4,
|
|
1741
|
+
padding: "4px 6px",
|
|
1742
|
+
marginTop: 4
|
|
1743
|
+
} }, toolPromptNote)
|
|
1744
|
+
];
|
|
1745
|
+
return [label("顾问工具调用范围"), (0, react.createElement)("div", { style: {
|
|
1746
|
+
fontSize: 12,
|
|
1747
|
+
color: "var(--dsh-color-muted, #8b90a0)",
|
|
1748
|
+
border: "1px solid var(--dsh-color-border, #3a3f4b)",
|
|
1749
|
+
borderRadius: 4,
|
|
1750
|
+
padding: "4px 6px"
|
|
1751
|
+
} }, "❌ 当前为 DSH 内置通道(复用 DSH 凭据),不支持工具调用;为其配置 API 地址 / Key / 环境变量以走直连通道后即可启用(工具指引提示词也会随之生效)。")];
|
|
1752
|
+
})(), label("思考模式 / 思考强度(未设置 = 跟随模型与供应商默认)"), (0, react.createElement)("div", { style: {
|
|
1753
|
+
display: "flex",
|
|
1754
|
+
gap: 8,
|
|
1755
|
+
marginTop: 4
|
|
1756
|
+
} }, (0, react.createElement)("select", {
|
|
1757
|
+
"aria-label": "思考模式",
|
|
1758
|
+
value: advisor.thinking ?? "",
|
|
1759
|
+
onChange: (e) => updateAdvisor(index, { thinking: e.target.value === "" ? void 0 : e.target.value }),
|
|
1760
|
+
style: settingsInputStyle
|
|
1761
|
+
}, (0, react.createElement)("option", { value: "" }, "思考:默认"), (0, react.createElement)("option", { value: "enabled" }, "思考:开启"), (0, react.createElement)("option", { value: "disabled" }, "思考:关闭")), (0, react.createElement)("select", {
|
|
1762
|
+
"aria-label": "思考强度",
|
|
1763
|
+
value: advisor.reasoningEffort ?? "",
|
|
1764
|
+
onChange: (e) => updateAdvisor(index, { reasoningEffort: e.target.value === "" ? void 0 : e.target.value }),
|
|
1765
|
+
style: settingsInputStyle
|
|
1766
|
+
}, (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: {
|
|
1767
|
+
display: "flex",
|
|
1768
|
+
gap: 8,
|
|
1769
|
+
marginTop: 4
|
|
1770
|
+
} }, (0, react.createElement)("input", {
|
|
1771
|
+
type: "number",
|
|
1772
|
+
placeholder: "temperature",
|
|
1773
|
+
value: advisor.temperature ?? "",
|
|
1774
|
+
onChange: (e) => updateAdvisor(index, { temperature: e.target.value === "" ? void 0 : Number(e.target.value) }),
|
|
1775
|
+
style: {
|
|
1776
|
+
...settingsInputStyle,
|
|
1777
|
+
width: 90
|
|
1778
|
+
}
|
|
1779
|
+
}), (0, react.createElement)("input", {
|
|
1780
|
+
type: "number",
|
|
1781
|
+
placeholder: "maxTokens",
|
|
1782
|
+
value: advisor.maxTokens ?? "",
|
|
1783
|
+
onChange: (e) => updateAdvisor(index, { maxTokens: e.target.value === "" ? void 0 : Number(e.target.value) }),
|
|
1784
|
+
style: {
|
|
1785
|
+
...settingsInputStyle,
|
|
1786
|
+
width: 120
|
|
1787
|
+
}
|
|
1788
|
+
}))))), (0, react.createElement)("div", { style: settingsCardFooterStyle }, msg ? (0, react.createElement)("span", { style: {
|
|
1789
|
+
flex: 1,
|
|
1790
|
+
minWidth: 0,
|
|
1791
|
+
color: "#16a34a",
|
|
1792
|
+
fontSize: 12
|
|
1793
|
+
} }, msg) : null, error ? (0, react.createElement)("span", { style: {
|
|
1794
|
+
flex: 1,
|
|
1795
|
+
minWidth: 0,
|
|
1796
|
+
color: "#e11d48",
|
|
1797
|
+
fontSize: 12
|
|
1798
|
+
} }, error) : null, (0, react.createElement)("button", {
|
|
1799
|
+
type: "button",
|
|
1800
|
+
disabled: !dirty || saving,
|
|
1801
|
+
onClick: () => void reload(),
|
|
1802
|
+
style: {
|
|
1803
|
+
...settingsFooterButtonStyle,
|
|
1804
|
+
border: "1px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
|
|
1805
|
+
background: "transparent",
|
|
1806
|
+
color: "inherit"
|
|
1807
|
+
}
|
|
1808
|
+
}, "放弃更改"), (0, react.createElement)("button", {
|
|
1809
|
+
type: "button",
|
|
1810
|
+
disabled: !dirty || saving,
|
|
1811
|
+
onClick: () => void save(),
|
|
1812
|
+
style: {
|
|
1813
|
+
...settingsFooterButtonStyle,
|
|
1814
|
+
background: "var(--dsw-alias-label-primary, #e6e9f0)",
|
|
1815
|
+
color: "var(--dsw-alias-bg-layer-3, #151821)"
|
|
1816
|
+
}
|
|
1817
|
+
}, saving ? "保存中…" : "保存设置")))) : null);
|
|
1818
|
+
}
|
|
1819
|
+
const name = "dsh-advisor-group";
|
|
1820
|
+
const inject = [
|
|
1821
|
+
"uiConversation",
|
|
1822
|
+
"slots",
|
|
1823
|
+
"locale"
|
|
1824
|
+
];
|
|
1825
|
+
function apply(ctx) {
|
|
1826
|
+
ctx.uiConversation.events.register(advisorGroupDefinition);
|
|
1827
|
+
ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
|
|
1828
|
+
name: "conversation.chat.node",
|
|
1829
|
+
key: "advisor-group",
|
|
1830
|
+
locale: "chat"
|
|
1831
|
+
}, AdvisorGroupNodeView));
|
|
1832
|
+
ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
1833
|
+
name: "settings.plugin.item",
|
|
1834
|
+
key: "advisor-group"
|
|
1835
|
+
}, AdvisorGroupSettingsTab));
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
//#endregion
|
|
1839
|
+
exports.advisorGroupDefinition = advisorGroupDefinition;
|
|
1840
|
+
exports.apply = apply;
|
|
1841
|
+
exports.buildStepSequence = buildStepSequence;
|
|
1842
|
+
exports.buildToolStepViews = buildToolStepViews;
|
|
1843
|
+
exports.extractActionDescription = extractActionDescription;
|
|
1844
|
+
exports.inject = inject;
|
|
1845
|
+
exports.name = name;
|
|
1846
|
+
exports.sanitizeClientContent = sanitizeClientContent;
|
|
1847
|
+
return module.exports; } });
|