dsh-advisor-group 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js ADDED
@@ -0,0 +1,1444 @@
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
+ function locationOf(context) {
294
+ return context.start?.location ?? context.matches[0]?.location ?? { kind: "unresolved" };
295
+ }
296
+ function viewData(state) {
297
+ return {
298
+ sessionId: state.sessionId,
299
+ status: state.status,
300
+ question: state.question,
301
+ ...state.context === void 0 ? {} : { context: state.context },
302
+ advisors: state.advisors,
303
+ messages: state.messages,
304
+ ...state.summary === void 0 ? {} : { summary: state.summary }
305
+ };
306
+ }
307
+ const advisorGroupDefinition = {
308
+ kind: "advisor-group",
309
+ target: "chat",
310
+ match: (event) => {
311
+ if (event.type === "advisor-group/start") return {
312
+ id: event.data.sessionId,
313
+ role: "start"
314
+ };
315
+ if (event.type === "advisor-group/message" || event.type === "advisor-group/delta" || event.type === "advisor-group/end") return {
316
+ id: event.data.sessionId,
317
+ role: "update"
318
+ };
319
+ return null;
320
+ },
321
+ start: (_context, match) => {
322
+ if (match.event.type !== "advisor-group/start") throw new Error("advisor-group requires advisor-group/start");
323
+ const data = match.event.data;
324
+ return {
325
+ sessionId: data.sessionId,
326
+ turn: data.turn,
327
+ step: data.step,
328
+ question: data.question,
329
+ ...data.context === void 0 ? {} : { context: data.context },
330
+ advisors: data.advisors,
331
+ messages: [{
332
+ sessionId: data.sessionId,
333
+ turn: data.turn,
334
+ step: data.step,
335
+ role: "main",
336
+ content: data.question
337
+ }],
338
+ status: "running"
339
+ };
340
+ },
341
+ update: (context, match) => {
342
+ if (match.event.type === "advisor-group/message") {
343
+ const data = match.event.data;
344
+ 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));
345
+ if (existingIndex >= 0) {
346
+ const messages = [...context.state.messages];
347
+ messages[existingIndex] = {
348
+ ...messages[existingIndex],
349
+ ...data
350
+ };
351
+ return {
352
+ ...context.state,
353
+ messages
354
+ };
355
+ }
356
+ return {
357
+ ...context.state,
358
+ messages: [...context.state.messages, data]
359
+ };
360
+ }
361
+ if (match.event.type === "advisor-group/delta") {
362
+ const delta = match.event.data;
363
+ 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 {
364
+ ...context.state,
365
+ messages: [...context.state.messages, {
366
+ sessionId: delta.sessionId,
367
+ turn: delta.turn,
368
+ step: delta.step,
369
+ role: "advisor",
370
+ advisorId: delta.advisorId,
371
+ advisorName: delta.advisorName,
372
+ ...delta.round === void 0 ? {} : { round: delta.round },
373
+ content: delta.contentDelta ?? "",
374
+ thinking: delta.thinkingDelta ?? ""
375
+ }]
376
+ };
377
+ return {
378
+ ...context.state,
379
+ 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) ? {
380
+ ...message,
381
+ content: message.content + (delta.contentDelta ?? ""),
382
+ thinking: (message.thinking ?? "") + (delta.thinkingDelta ?? "")
383
+ } : message)
384
+ };
385
+ }
386
+ if (match.event.type === "advisor-group/end") return {
387
+ ...context.state,
388
+ status: "completed",
389
+ summary: match.event.data.summary
390
+ };
391
+ return context.state;
392
+ },
393
+ publication: (match) => match.event.type === "advisor-group/message" || match.event.type === "advisor-group/delta" ? "animation-frame" : "immediate",
394
+ buildLocationData: (context, scope) => {
395
+ if (scope !== "step" || context.state === void 0) return null;
396
+ return {
397
+ kind: "step",
398
+ turn: context.state.turn,
399
+ step: context.state.step,
400
+ key: "advisor-group",
401
+ value: viewData(context.state)
402
+ };
403
+ },
404
+ buildViewNode: (context) => {
405
+ if (context.state === void 0) return null;
406
+ return buildChatNode(context, "advisor-group", context.start?.event.seq ?? context.matches[0]?.event.seq ?? 0, viewData(context.state));
407
+ }
408
+ };
409
+ /**
410
+ * Build the chat-target view Node for one assembled advisor-group Context.
411
+ *
412
+ * The generic `ConversationNodeDefinition.buildViewNode` only declares the
413
+ * `{ key, kind, id, target, data }` face; DSH's chat snapshot builder reads
414
+ * the chat-specific extras (`anchorSeq` / `location` / `visibility`) at
415
+ * runtime. The factory returns them through a function call so the object is
416
+ * not subject to fresh-literal excess-property checks against the generic
417
+ * type — the same technique the official chat definitions use.
418
+ */
419
+ function buildChatNode(context, kind, anchorSeq, data) {
420
+ return {
421
+ key: context.key,
422
+ kind,
423
+ id: context.id,
424
+ target: "chat",
425
+ anchorSeq,
426
+ location: locationOf(context),
427
+ visibility: "visible",
428
+ data
429
+ };
430
+ }
431
+ const rootStyle = {
432
+ boxSizing: "border-box",
433
+ width: "100%",
434
+ margin: "8px 0",
435
+ padding: "10px 12px",
436
+ border: "1px solid #22c55e",
437
+ borderRadius: 4,
438
+ background: "#0a0f0a",
439
+ color: "#b8f5c4",
440
+ fontFamily: "'Courier New', ui-monospace, SFMono-Regular, monospace",
441
+ fontSize: 13,
442
+ lineHeight: 1.5,
443
+ position: "relative",
444
+ overflow: "hidden",
445
+ boxShadow: "0 0 12px rgba(34,197,94,0.22), inset 0 0 24px rgba(34,197,94,0.06)"
446
+ };
447
+ const scanlineStyle = {
448
+ position: "absolute",
449
+ top: 0,
450
+ right: 0,
451
+ bottom: 0,
452
+ left: 0,
453
+ pointerEvents: "none",
454
+ background: "repeating-linear-gradient(0deg, rgba(0,0,0,0.18) 0px, rgba(0,0,0,0.18) 1px, transparent 1px, transparent 3px)"
455
+ };
456
+ const headerStyle = {
457
+ display: "flex",
458
+ justifyContent: "space-between",
459
+ gap: 8,
460
+ borderBottom: "1px dashed #2f9e44",
461
+ paddingBottom: 4,
462
+ marginBottom: 8,
463
+ color: "#86efac",
464
+ textTransform: "uppercase",
465
+ letterSpacing: 1,
466
+ fontSize: 11
467
+ };
468
+ const questionStyle = {
469
+ color: "#d9f99d",
470
+ fontWeight: 700,
471
+ whiteSpace: "pre-wrap",
472
+ wordBreak: "break-word",
473
+ marginBottom: 6
474
+ };
475
+ const advisorsLineStyle = {
476
+ fontSize: 11,
477
+ color: "#4ade80",
478
+ marginBottom: 6
479
+ };
480
+ const contextBoxStyle = {
481
+ border: "1px dashed #a16207",
482
+ borderRadius: 3,
483
+ marginBottom: 6,
484
+ background: "rgba(217,119,6,0.06)"
485
+ };
486
+ const contextToggleStyle = {
487
+ width: "100%",
488
+ cursor: "pointer",
489
+ background: "transparent",
490
+ border: "none",
491
+ color: "#fbbf24",
492
+ fontSize: 12,
493
+ textAlign: "left",
494
+ padding: "3px 6px"
495
+ };
496
+ const contextBodyStyle = {
497
+ maxHeight: 180,
498
+ overflowY: "auto",
499
+ whiteSpace: "pre-wrap",
500
+ wordBreak: "break-word",
501
+ padding: "4px 6px",
502
+ color: "#fde68a",
503
+ fontSize: 12,
504
+ borderTop: "1px dashed #a16207"
505
+ };
506
+ const waitingStyle = {
507
+ color: "#fbbf24",
508
+ marginTop: 6
509
+ };
510
+ function MessageBubble({ message }) {
511
+ const [thinkingCollapsed, setThinkingCollapsed] = (0, react.useState)(false);
512
+ const thinkingPanelRef = (0, react.useRef)(null);
513
+ (0, react.useEffect)(() => {
514
+ if (!thinkingCollapsed && thinkingPanelRef.current) thinkingPanelRef.current.scrollTop = thinkingPanelRef.current.scrollHeight;
515
+ }, [message.thinking, thinkingCollapsed]);
516
+ const isMain = message.role === "main";
517
+ 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
+ const label = isMain ? "YOU" : isSystem ? "SYSTEM" : message.advisorName ?? message.advisorId ?? "ADVISOR";
522
+ const hasThinking = Boolean(message.thinking && message.thinking.length > 0);
523
+ return (0, react.createElement)("div", { style: {
524
+ display: "flex",
525
+ justifyContent: justify,
526
+ margin: "4px 0"
527
+ } }, (0, react.createElement)("div", { style: {
528
+ maxWidth: "92%",
529
+ padding: "4px 8px",
530
+ border: `1px solid ${borderColor}`,
531
+ borderRadius: 3,
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",
562
+ whiteSpace: "pre-wrap",
563
+ wordBreak: "break-word",
564
+ padding: "4px 6px",
565
+ color: "#fde68a",
566
+ fontSize: 12,
567
+ borderTop: "1px dashed #a16207"
568
+ }
569
+ }, message.thinking) : null) : null, isMain || isSystem ? (0, react.createElement)("div", { style: {
570
+ whiteSpace: "pre-wrap",
571
+ wordBreak: "break-word"
572
+ } }, message.content) : (0, react.createElement)("div", { style: { wordBreak: "break-word" } }, renderMarkdown(message.content))));
573
+ }
574
+ function AdvisorGroupNodeView(props) {
575
+ const data = props.node.data;
576
+ const sessionId = data.sessionId;
577
+ const [live, setLive] = (0, react.useState)({});
578
+ const [contextCollapsed, setContextCollapsed] = (0, react.useState)(true);
579
+ const lastEventIdRef = (0, react.useRef)(0);
580
+ const lastBootIdRef = (0, react.useRef)("");
581
+ (0, react.useEffect)(() => {
582
+ if (!sessionId) return;
583
+ const es = new EventSource(`/advisor-group/stream?sessionId=${encodeURIComponent(sessionId)}&lastEventId=${lastEventIdRef.current}&bootId=${encodeURIComponent(lastBootIdRef.current)}&token=${encodeURIComponent(advisorGroupToken())}`);
584
+ es.onmessage = (event) => {
585
+ try {
586
+ const delta = JSON.parse(event.data);
587
+ const bootId = delta.bootId ?? "";
588
+ if (lastBootIdRef.current !== "" && bootId !== lastBootIdRef.current) {
589
+ lastBootIdRef.current = bootId;
590
+ lastEventIdRef.current = 0;
591
+ setLive({});
592
+ } else if (bootId) lastBootIdRef.current = bootId;
593
+ const eventId = Number(delta.eventId ?? 0);
594
+ if (eventId > 0 && eventId <= lastEventIdRef.current) return;
595
+ if (eventId > 0) lastEventIdRef.current = eventId;
596
+ setLive((prev) => {
597
+ const current = prev[delta.advisorId] ?? {
598
+ content: "",
599
+ thinking: ""
600
+ };
601
+ return {
602
+ ...prev,
603
+ [delta.advisorId]: {
604
+ content: current.content + (delta.contentDelta ?? ""),
605
+ thinking: current.thinking + (delta.thinkingDelta ?? "")
606
+ }
607
+ };
608
+ });
609
+ } catch {}
610
+ };
611
+ es.addEventListener("resync", () => {
612
+ lastEventIdRef.current = 0;
613
+ setLive({});
614
+ });
615
+ return () => es.close();
616
+ }, [sessionId]);
617
+ const mergedMessages = data.messages.map((message) => {
618
+ if (message.role !== "advisor") return message;
619
+ const advisorId = message.advisorId ?? "";
620
+ const streamed = live[advisorId];
621
+ if (!streamed) return message;
622
+ const content = (streamed.content?.length ?? 0) > (message.content?.length ?? 0) ? streamed.content : message.content;
623
+ const thinking = (streamed.thinking?.length ?? 0) > (message.thinking?.length ?? 0) ? streamed.thinking : message.thinking;
624
+ return {
625
+ ...message,
626
+ content,
627
+ thinking
628
+ };
629
+ });
630
+ 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";
632
+ return (0, react.createElement)("div", {
633
+ style: rootStyle,
634
+ role: "log",
635
+ "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", { key: "id" }, `#${shortId}`)), (0, react.createElement)("div", { style: questionStyle }, `> ${data.question}`), data.context ? (0, react.createElement)("div", { style: contextBoxStyle }, (0, react.createElement)("button", {
637
+ type: "button",
638
+ onClick: () => setContextCollapsed((value) => !value),
639
+ 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)(MessageBubble, {
641
+ key: index,
642
+ message
643
+ })), data.status === "completed" && data.summary?.conclusion ? (0, react.createElement)("div", { style: {
644
+ borderTop: "1px dashed #2f9e44",
645
+ marginTop: 8,
646
+ paddingTop: 8
647
+ } }, (0, react.createElement)("div", { style: {
648
+ color: "#86efac",
649
+ fontWeight: 700,
650
+ marginBottom: 4
651
+ } }, "📌 综合结论"), (0, react.createElement)("div", null, renderMarkdown(data.summary.conclusion))) : null, data.status === "running" ? (0, react.createElement)("div", { style: waitingStyle }, "▊ AWAITING RESPONSES…") : null);
652
+ }
653
+ const settingsRootStyle = {
654
+ fontFamily: "'Inter', 'Segoe UI', system-ui, sans-serif",
655
+ fontSize: 13,
656
+ maxWidth: 720,
657
+ color: "inherit"
658
+ };
659
+ const settingsCardStyle = (open) => ({
660
+ boxSizing: "border-box",
661
+ border: "0.5px solid var(--dsw-alias-border-l4, var(--dsh-color-border, #3a3f4b))",
662
+ background: open ? "var(--dsw-alias-bg-layer-2, #191d26)" : "var(--dsw-alias-bg-layer-3, #151821)",
663
+ borderRadius: 16,
664
+ listStyle: "none",
665
+ transition: "border-color .16s, background .16s"
666
+ });
667
+ const settingsCardHeaderStyle = {
668
+ appearance: "none",
669
+ width: "100%",
670
+ font: "inherit",
671
+ color: "inherit",
672
+ textAlign: "left",
673
+ cursor: "pointer",
674
+ background: "transparent",
675
+ border: 0,
676
+ borderRadius: 12,
677
+ alignItems: "center",
678
+ gap: 12,
679
+ padding: "14px 16px",
680
+ display: "flex"
681
+ };
682
+ const settingsHeadTextStyle = {
683
+ flexDirection: "column",
684
+ flex: 1,
685
+ gap: 4,
686
+ minWidth: 0,
687
+ display: "flex"
688
+ };
689
+ const settingsCardNameStyle = {
690
+ color: "var(--dsw-alias-label-primary, #e6e9f0)",
691
+ fontSize: 15,
692
+ fontWeight: 600,
693
+ lineHeight: 1.4
694
+ };
695
+ const settingsCardDescriptionStyle = {
696
+ color: "var(--dsw-alias-label-tertiary, var(--dsh-color-muted, #8b90a0))",
697
+ fontSize: 13,
698
+ lineHeight: 1.5
699
+ };
700
+ const settingsCardPendingStyle = {
701
+ background: "var(--dsw-alias-bg-module-platform, #2a2f3d)",
702
+ color: "var(--dsw-alias-label-secondary, #aab2c5)",
703
+ borderRadius: 999,
704
+ padding: "1px 8px",
705
+ fontSize: 11,
706
+ fontWeight: 500,
707
+ lineHeight: "17px",
708
+ whiteSpace: "nowrap",
709
+ flex: "none"
710
+ };
711
+ const settingsCardBodyStyle = {
712
+ borderTop: "0.5px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
713
+ margin: "0 16px",
714
+ paddingBottom: 8
715
+ };
716
+ const settingsCardLoadingStyle = {
717
+ color: "var(--dsw-alias-label-tertiary, var(--dsh-color-muted, #8b90a0))",
718
+ margin: "12px 0",
719
+ fontSize: 12,
720
+ lineHeight: 1.5
721
+ };
722
+ const settingsCardFooterStyle = {
723
+ borderTop: "0.5px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
724
+ justifyContent: "flex-end",
725
+ alignItems: "center",
726
+ gap: 8,
727
+ padding: "12px 0 4px",
728
+ display: "flex",
729
+ flexWrap: "wrap"
730
+ };
731
+ const settingsFooterButtonStyle = {
732
+ appearance: "none",
733
+ font: "inherit",
734
+ cursor: "pointer",
735
+ border: "1px solid transparent",
736
+ borderRadius: 8,
737
+ padding: "5px 14px",
738
+ fontSize: 13,
739
+ lineHeight: 1.5
740
+ };
741
+ /** Collapsible header shared by the loading and settled card states. */
742
+ function settingsCardHeader(open, dirty, onToggle) {
743
+ return (0, react.createElement)("button", {
744
+ type: "button",
745
+ style: settingsCardHeaderStyle,
746
+ "aria-expanded": open,
747
+ "aria-label": `${open ? "收起" : "展开"}设置:顾问群`,
748
+ onClick: onToggle
749
+ }, (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", {
750
+ width: 14,
751
+ height: 14,
752
+ viewBox: "0 0 16 16",
753
+ fill: "none",
754
+ "aria-hidden": true,
755
+ style: {
756
+ flex: "none",
757
+ transition: "transform .16s",
758
+ transform: open ? "rotate(180deg)" : "none"
759
+ }
760
+ }, (0, react.createElement)("path", {
761
+ d: "M3 6 L8 10.5 L13 6",
762
+ stroke: "currentColor",
763
+ strokeWidth: 1.5,
764
+ strokeLinecap: "round",
765
+ strokeLinejoin: "round"
766
+ })));
767
+ }
768
+ const settingsSectionStyle = {
769
+ border: "1px solid var(--dsh-color-border, #3a3f4b)",
770
+ borderRadius: 8,
771
+ padding: "10px 12px",
772
+ marginBottom: 10,
773
+ background: "transparent"
774
+ };
775
+ const settingsLabelStyle = {
776
+ display: "block",
777
+ fontSize: 12,
778
+ marginBottom: 4,
779
+ color: "var(--dsh-color-muted, #8b90a0)"
780
+ };
781
+ const settingsInputStyle = {
782
+ width: "100%",
783
+ boxSizing: "border-box",
784
+ padding: "4px 6px",
785
+ borderRadius: 4,
786
+ border: "1px solid var(--dsh-color-border, #3a3f4b)",
787
+ background: "transparent",
788
+ color: "inherit",
789
+ fontSize: 13,
790
+ caretColor: "inherit"
791
+ };
792
+ const advisorCardStyle = {
793
+ border: "1px solid var(--dsh-color-border, #3a3f4b)",
794
+ borderRadius: 6,
795
+ padding: 8,
796
+ marginBottom: 8,
797
+ display: "grid",
798
+ gap: 6
799
+ };
800
+ const AVATAR_OPTIONS = [
801
+ "🧠",
802
+ "⚖️",
803
+ "👨‍💻",
804
+ "👩‍💻",
805
+ "💼",
806
+ "🏥",
807
+ "📊",
808
+ "🔬",
809
+ "💰",
810
+ "🌐",
811
+ "🛠️",
812
+ "📚",
813
+ "🎯",
814
+ "⚙️",
815
+ "🚀",
816
+ "🛡️"
817
+ ];
818
+ function AdvisorGroupSettingsTab() {
819
+ const [config, setConfig] = (0, react.useState)(null);
820
+ const [providers, setProviders] = (0, react.useState)([]);
821
+ const [revision, setRevision] = (0, react.useState)(0);
822
+ const [customModes, setCustomModes] = (0, react.useState)(/* @__PURE__ */ new Set());
823
+ const [fetchedModels, setFetchedModels] = (0, react.useState)({});
824
+ const [fetchingModels, setFetchingModels] = (0, react.useState)({});
825
+ const [testingConnection, setTestingConnection] = (0, react.useState)({});
826
+ const [msg, setMsg] = (0, react.useState)("");
827
+ const [error, setError] = (0, react.useState)("");
828
+ const [open, setOpen] = (0, react.useState)(false);
829
+ const [saving, setSaving] = (0, react.useState)(false);
830
+ const [dailyGuard, setDailyGuard] = (0, react.useState)(null);
831
+ const savedJsonRef = (0, react.useRef)("");
832
+ const reload = async () => {
833
+ setMsg("");
834
+ setError("");
835
+ try {
836
+ 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())]);
837
+ if (configData.ok && configData.config) {
838
+ setConfig(configData.config);
839
+ savedJsonRef.current = JSON.stringify(configData.config);
840
+ if (typeof configData.revision === "number") setRevision(configData.revision);
841
+ if (configData.dailyGuard) setDailyGuard(configData.dailyGuard);
842
+ } else setError(configData.error ?? "加载配置失败");
843
+ if (providerData.ok && providerData.providers) setProviders(providerData.providers);
844
+ } catch (e) {
845
+ setError(e instanceof Error ? e.message : String(e));
846
+ }
847
+ };
848
+ (0, react.useEffect)(() => {
849
+ reload();
850
+ }, []);
851
+ const dirty = config !== null && savedJsonRef.current !== "" && JSON.stringify(config) !== savedJsonRef.current;
852
+ 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 || "加载中…")));
853
+ const updateAdvisor = (index, patch) => {
854
+ const advisors = config.advisors.map((advisor, i) => i === index ? {
855
+ ...advisor,
856
+ ...patch
857
+ } : advisor);
858
+ setConfig({
859
+ ...config,
860
+ advisors
861
+ });
862
+ };
863
+ const removeAdvisor = (index) => {
864
+ const advisors = config.advisors.filter((_, i) => i !== index);
865
+ setConfig({
866
+ ...config,
867
+ advisors
868
+ });
869
+ };
870
+ const addAdvisor = () => {
871
+ setConfig({
872
+ ...config,
873
+ advisors: [...config.advisors, {
874
+ id: `advisor-${Date.now()}`,
875
+ name: "新顾问",
876
+ provider: "deepseek-official",
877
+ model: "deepseek-v4-pro",
878
+ systemPrompt: DEFAULT_ADVISOR_PROMPT
879
+ }]
880
+ });
881
+ };
882
+ const loadModels = async (index) => {
883
+ const advisor = config.advisors[index];
884
+ if (!advisor) return;
885
+ setFetchingModels((prev) => ({
886
+ ...prev,
887
+ [advisor.id]: true
888
+ }));
889
+ setError("");
890
+ setMsg("");
891
+ try {
892
+ const data = await (await fetch("/advisor-group/models", {
893
+ method: "POST",
894
+ headers: {
895
+ "content-type": "application/json",
896
+ ...authHeaders()
897
+ },
898
+ body: JSON.stringify({
899
+ advisorId: advisor.id,
900
+ provider: advisor.provider,
901
+ baseURL: advisor.baseURL ?? "",
902
+ apiKey: advisor.apiKey ?? "",
903
+ apiKeyEnv: advisor.apiKeyEnv ?? "",
904
+ protocol: advisor.protocol ?? "openai"
905
+ })
906
+ })).json();
907
+ if (data.ok && data.models) {
908
+ setFetchedModels((prev) => ({
909
+ ...prev,
910
+ [advisor.id]: data.models ?? []
911
+ }));
912
+ setMsg(`已获取 ${advisor.name} 的模型列表(${data.models.length} 个)`);
913
+ } else setError(data.error ?? "获取模型列表失败");
914
+ } catch (e) {
915
+ setError(e instanceof Error ? e.message : String(e));
916
+ } finally {
917
+ setFetchingModels((prev) => ({
918
+ ...prev,
919
+ [advisor.id]: false
920
+ }));
921
+ }
922
+ };
923
+ const testConnection = async (index) => {
924
+ const advisor = config.advisors[index];
925
+ if (!advisor) return;
926
+ setTestingConnection((prev) => ({
927
+ ...prev,
928
+ [advisor.id]: true
929
+ }));
930
+ setError("");
931
+ setMsg("");
932
+ try {
933
+ const data = await (await fetch("/advisor-group/test-connection", {
934
+ method: "POST",
935
+ headers: {
936
+ "content-type": "application/json",
937
+ ...authHeaders()
938
+ },
939
+ body: JSON.stringify({
940
+ advisorId: advisor.id,
941
+ provider: advisor.provider,
942
+ baseURL: advisor.baseURL ?? "",
943
+ apiKey: advisor.apiKey ?? "",
944
+ apiKeyEnv: advisor.apiKeyEnv ?? "",
945
+ protocol: advisor.protocol ?? "openai"
946
+ })
947
+ })).json();
948
+ if (data.ok) setMsg(`✅ ${advisor.name} 连接成功${data.message ? `:${data.message}` : ""}`);
949
+ else setError(`❌ ${advisor.name} 连接失败:${data.error ?? "未知错误"}`);
950
+ } catch (e) {
951
+ setError(`❌ ${advisor.name} 连接失败:${e instanceof Error ? e.message : String(e)}`);
952
+ } finally {
953
+ setTestingConnection((prev) => ({
954
+ ...prev,
955
+ [advisor.id]: false
956
+ }));
957
+ }
958
+ };
959
+ const save = async () => {
960
+ setMsg("");
961
+ setError("");
962
+ setSaving(true);
963
+ try {
964
+ const configPayload = {
965
+ ...config,
966
+ advisors: config.advisors.map(({ apiKeyMetaByProvider: _meta, ...rest }) => rest)
967
+ };
968
+ const data = await (await fetch("/advisor-group/config", {
969
+ method: "POST",
970
+ headers: {
971
+ "content-type": "application/json",
972
+ ...authHeaders()
973
+ },
974
+ body: JSON.stringify({
975
+ config: configPayload,
976
+ expectedRevision: revision
977
+ })
978
+ })).json();
979
+ if (data.ok) {
980
+ if (data.config) {
981
+ setConfig(data.config);
982
+ savedJsonRef.current = JSON.stringify(data.config);
983
+ } else savedJsonRef.current = JSON.stringify(configPayload);
984
+ if (typeof data.revision === "number") setRevision(data.revision);
985
+ setMsg("配置已保存");
986
+ setError("");
987
+ } else setError(data.error ?? "保存失败");
988
+ } catch (e) {
989
+ setError(e instanceof Error ? e.message : String(e));
990
+ } finally {
991
+ setSaving(false);
992
+ }
993
+ };
994
+ const label = (text) => (0, react.createElement)("label", { style: settingsLabelStyle }, text);
995
+ 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: {
996
+ display: "flex",
997
+ alignItems: "center",
998
+ gap: 8
999
+ } }, (0, react.createElement)("input", {
1000
+ type: "checkbox",
1001
+ checked: config.enabled,
1002
+ onChange: (e) => setConfig({
1003
+ ...config,
1004
+ enabled: e.target.checked
1005
+ })
1006
+ }), label("启用顾问群")), dailyGuard ? (0, react.createElement)("div", { style: {
1007
+ marginTop: 6,
1008
+ 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: {
1011
+ display: "grid",
1012
+ gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
1013
+ gap: 8
1014
+ } }, (0, react.createElement)("div", null, label("讨论轮数上限"), (0, react.createElement)("input", {
1015
+ type: "number",
1016
+ min: 0,
1017
+ value: String(config.discussion.maxRounds),
1018
+ onChange: (e) => setConfig({
1019
+ ...config,
1020
+ discussion: {
1021
+ ...config.discussion,
1022
+ maxRounds: Number(e.target.value)
1023
+ }
1024
+ }),
1025
+ style: settingsInputStyle
1026
+ })), (0, react.createElement)("div", null, label("单次最多顾问数"), (0, react.createElement)("input", {
1027
+ type: "number",
1028
+ min: 1,
1029
+ value: String(config.discussion.maxAdvisorsPerCall ?? 3),
1030
+ onChange: (e) => setConfig({
1031
+ ...config,
1032
+ discussion: {
1033
+ ...config.discussion,
1034
+ maxAdvisorsPerCall: Math.max(1, Number(e.target.value))
1035
+ }
1036
+ }),
1037
+ style: settingsInputStyle
1038
+ })), (0, react.createElement)("div", null, label("单顾问超时(毫秒)"), (0, react.createElement)("input", {
1039
+ type: "number",
1040
+ min: 1e3,
1041
+ max: 6e5,
1042
+ step: 1e3,
1043
+ value: String(config.discussion.advisorTimeoutMs ?? 12e4),
1044
+ onChange: (e) => setConfig({
1045
+ ...config,
1046
+ discussion: {
1047
+ ...config.discussion,
1048
+ advisorTimeoutMs: Math.max(1e3, Number(e.target.value))
1049
+ }
1050
+ }),
1051
+ style: settingsInputStyle
1052
+ }))), (0, react.createElement)("div", { style: { marginTop: 8 } }, label("置信度阈值(0-1,低于此值升级顾问群)"), (0, react.createElement)("input", {
1053
+ type: "number",
1054
+ min: 0,
1055
+ max: 1,
1056
+ step: .05,
1057
+ value: String(config.trigger.confidenceThreshold ?? .6),
1058
+ onChange: (e) => setConfig({
1059
+ ...config,
1060
+ trigger: {
1061
+ ...config.trigger,
1062
+ confidenceThreshold: Number(e.target.value)
1063
+ }
1064
+ }),
1065
+ style: settingsInputStyle
1066
+ }))), (0, react.createElement)("div", { style: settingsSectionStyle }, (0, react.createElement)("div", { style: {
1067
+ display: "flex",
1068
+ alignItems: "center",
1069
+ gap: 8
1070
+ } }, (0, react.createElement)("input", {
1071
+ type: "checkbox",
1072
+ checked: config.discussion.autoDeepen,
1073
+ onChange: (e) => setConfig({
1074
+ ...config,
1075
+ discussion: {
1076
+ ...config.discussion,
1077
+ autoDeepen: e.target.checked
1078
+ }
1079
+ })
1080
+ }), label("自动多轮深挖(每轮后驱动模型生成深入追问,顾问按 A→B→C 顺序接力,直到轮数上限)")), (0, react.createElement)("div", { style: {
1081
+ display: "flex",
1082
+ alignItems: "center",
1083
+ gap: 8,
1084
+ marginTop: 4
1085
+ } }, (0, react.createElement)("input", {
1086
+ type: "checkbox",
1087
+ checked: config.trigger.requireClassifier,
1088
+ onChange: (e) => setConfig({
1089
+ ...config,
1090
+ trigger: {
1091
+ ...config.trigger,
1092
+ requireClassifier: e.target.checked
1093
+ }
1094
+ })
1095
+ }), label("启用前置分类器")), (0, react.createElement)("div", { style: {
1096
+ display: "flex",
1097
+ alignItems: "center",
1098
+ gap: 8,
1099
+ marginTop: 4
1100
+ } }, (0, react.createElement)("input", {
1101
+ type: "checkbox",
1102
+ checked: config.trigger.allowWebFallback,
1103
+ onChange: (e) => setConfig({
1104
+ ...config,
1105
+ trigger: {
1106
+ ...config.trigger,
1107
+ allowWebFallback: e.target.checked
1108
+ }
1109
+ })
1110
+ }), label("分类器建议时允许返回联网搜索提示"))), (0, react.createElement)("div", { style: settingsSectionStyle }, (0, react.createElement)("div", { style: {
1111
+ display: "flex",
1112
+ justifyContent: "space-between",
1113
+ alignItems: "center",
1114
+ marginBottom: 8
1115
+ } }, (0, react.createElement)("strong", null, `顾问列表(${config.advisors.length})`), (0, react.createElement)("button", {
1116
+ type: "button",
1117
+ onClick: addAdvisor,
1118
+ style: { cursor: "pointer" }
1119
+ }, "+ 添加顾问")), config.advisors.map((advisor, index) => (0, react.createElement)("div", {
1120
+ key: advisor.id,
1121
+ style: advisorCardStyle
1122
+ }, (0, react.createElement)("div", { style: {
1123
+ display: "flex",
1124
+ justifyContent: "space-between",
1125
+ gap: 8
1126
+ } }, (0, react.createElement)("strong", null, advisor.name || advisor.id), (0, react.createElement)("button", {
1127
+ type: "button",
1128
+ onClick: () => removeAdvisor(index),
1129
+ style: {
1130
+ cursor: "pointer",
1131
+ color: "#e11d48"
1132
+ }
1133
+ }, "删除")), label(`ID · ${advisor.id}`), (0, react.createElement)("input", {
1134
+ value: advisor.name,
1135
+ onChange: (e) => updateAdvisor(index, { name: e.target.value }),
1136
+ style: settingsInputStyle
1137
+ }), label("头像"), (0, react.createElement)("div", { style: {
1138
+ display: "flex",
1139
+ flexWrap: "wrap",
1140
+ gap: 4,
1141
+ marginTop: 2
1142
+ } }, (0, react.createElement)("button", {
1143
+ type: "button",
1144
+ onClick: () => updateAdvisor(index, { avatar: void 0 }),
1145
+ style: {
1146
+ cursor: "pointer",
1147
+ fontSize: 12,
1148
+ padding: "2px 6px",
1149
+ border: advisor.avatar === void 0 || advisor.avatar === "" ? "2px solid #4ade80" : "1px solid var(--dsh-color-border, #333)",
1150
+ background: "transparent"
1151
+ }
1152
+ }, "无"), AVATAR_OPTIONS.map((emoji) => (0, react.createElement)("button", {
1153
+ key: emoji,
1154
+ type: "button",
1155
+ onClick: () => updateAdvisor(index, { avatar: emoji }),
1156
+ style: {
1157
+ cursor: "pointer",
1158
+ fontSize: 18,
1159
+ padding: "2px 6px",
1160
+ border: advisor.avatar === emoji ? "2px solid #4ade80" : "1px solid var(--dsh-color-border, #333)",
1161
+ background: advisor.avatar === emoji ? "rgba(74,222,128,.15)" : "transparent"
1162
+ }
1163
+ }, emoji))), (0, react.createElement)("input", {
1164
+ placeholder: "自定义 emoji(可选)",
1165
+ value: advisor.avatar ?? "",
1166
+ onChange: (e) => updateAdvisor(index, { avatar: e.target.value }),
1167
+ style: {
1168
+ ...settingsInputStyle,
1169
+ marginTop: 4
1170
+ }
1171
+ }), label("供应商"), (0, react.createElement)("select", {
1172
+ "aria-label": "供应商",
1173
+ value: customModes.has(advisor.id) || !providers.some((p) => p.id === advisor.provider) ? "__custom__" : advisor.provider,
1174
+ onChange: (e) => {
1175
+ const value = e.target.value;
1176
+ if (value === "__custom__") {
1177
+ const next = new Set(customModes);
1178
+ next.add(advisor.id);
1179
+ setCustomModes(next);
1180
+ return;
1181
+ }
1182
+ const next = new Set(customModes);
1183
+ next.delete(advisor.id);
1184
+ setCustomModes(next);
1185
+ const provider = providers.find((p) => p.id === value);
1186
+ if (provider?.kind === "llm") updateAdvisor(index, {
1187
+ provider: value,
1188
+ baseURL: void 0,
1189
+ apiKey: "",
1190
+ clearApiKey: false,
1191
+ apiKeyEnv: void 0,
1192
+ protocol: void 0
1193
+ });
1194
+ else updateAdvisor(index, {
1195
+ provider: value,
1196
+ apiKey: "",
1197
+ clearApiKey: false,
1198
+ ...provider?.baseURL ? { baseURL: provider.baseURL } : {},
1199
+ ...provider?.apiKeyEnv ? { apiKeyEnv: provider.apiKeyEnv } : {},
1200
+ ...provider?.protocol ? { protocol: provider.protocol } : {}
1201
+ });
1202
+ },
1203
+ style: settingsInputStyle
1204
+ }, (0, react.createElement)("option", {
1205
+ value: "",
1206
+ disabled: true
1207
+ }, "请选择供应商"), providers.map((provider) => (0, react.createElement)("option", {
1208
+ key: provider.id,
1209
+ value: provider.id
1210
+ }, `${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", {
1211
+ placeholder: "自定义 provider 标识",
1212
+ value: advisor.provider,
1213
+ onChange: (e) => updateAdvisor(index, { provider: e.target.value }),
1214
+ style: {
1215
+ ...settingsInputStyle,
1216
+ marginTop: 4
1217
+ }
1218
+ }) : null, label("模型"), (0, react.createElement)("div", { style: {
1219
+ display: "flex",
1220
+ gap: 4
1221
+ } }, (0, react.createElement)("input", {
1222
+ "aria-label": "模型",
1223
+ list: `advisor-models-${index}`,
1224
+ placeholder: "model",
1225
+ value: advisor.model,
1226
+ onChange: (e) => updateAdvisor(index, { model: e.target.value }),
1227
+ style: {
1228
+ ...settingsInputStyle,
1229
+ marginTop: 4,
1230
+ border: advisor.model && advisor.model.trim() ? void 0 : "1px solid #e11d48"
1231
+ }
1232
+ }), (0, react.createElement)("button", {
1233
+ type: "button",
1234
+ onClick: () => void loadModels(index),
1235
+ disabled: !!fetchingModels[advisor.id],
1236
+ style: {
1237
+ cursor: "pointer",
1238
+ marginTop: 4,
1239
+ whiteSpace: "nowrap"
1240
+ }
1241
+ }, fetchingModels[advisor.id] ? "获取中…" : "获取模型列表"), (0, react.createElement)("button", {
1242
+ type: "button",
1243
+ onClick: () => void testConnection(index),
1244
+ disabled: !!testingConnection[advisor.id],
1245
+ style: {
1246
+ cursor: "pointer",
1247
+ marginTop: 4,
1248
+ whiteSpace: "nowrap"
1249
+ }
1250
+ }, testingConnection[advisor.id] ? "测试中…" : "测试连接")), !advisor.model?.trim() ? (0, react.createElement)("div", { style: {
1251
+ color: "#e11d48",
1252
+ fontSize: 12,
1253
+ marginTop: 2
1254
+ } }, "⚠️ 模型未配置,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", {
1255
+ key: model,
1256
+ value: model
1257
+ }, model))), (fetchedModels[advisor.id] ?? []).length > 0 ? (0, react.createElement)("div", { style: {
1258
+ maxHeight: 150,
1259
+ overflowY: "auto",
1260
+ border: "1px solid var(--dsh-color-border, #333)",
1261
+ borderRadius: 4,
1262
+ marginTop: 4
1263
+ } }, (fetchedModels[advisor.id] ?? []).map((model) => (0, react.createElement)("button", {
1264
+ key: model,
1265
+ type: "button",
1266
+ onClick: () => updateAdvisor(index, { model }),
1267
+ style: {
1268
+ display: "block",
1269
+ width: "100%",
1270
+ textAlign: "left",
1271
+ cursor: "pointer",
1272
+ padding: "3px 6px",
1273
+ background: "transparent",
1274
+ border: "none",
1275
+ color: "inherit",
1276
+ fontSize: 12
1277
+ }
1278
+ }, model))) : null, ...providers.find((p) => p.id === advisor.provider)?.kind === "llm" ? [(0, react.createElement)("div", { style: {
1279
+ fontSize: 12,
1280
+ color: "var(--dsh-color-muted, #8b90a0)",
1281
+ marginTop: 4
1282
+ } }, "✅ DSH 内置 Provider:复用 DSH 已配置凭据,无需填写 API 地址/Key。")] : (() => {
1283
+ const keyMeta = advisor.apiKeyMetaByProvider?.[advisor.provider];
1284
+ const keyConfigured = keyMeta?.configured === true;
1285
+ return [
1286
+ label("API 地址"),
1287
+ (0, react.createElement)("input", {
1288
+ "aria-label": "API 地址",
1289
+ placeholder: "https://api.example.com/v1",
1290
+ value: advisor.baseURL ?? "",
1291
+ onChange: (e) => updateAdvisor(index, { baseURL: e.target.value }),
1292
+ style: settingsInputStyle
1293
+ }),
1294
+ (0, react.createElement)("div", { style: {
1295
+ display: "flex",
1296
+ alignItems: "flex-end",
1297
+ gap: 6,
1298
+ flexWrap: "wrap"
1299
+ } }, (0, react.createElement)("div", { style: {
1300
+ flex: 1,
1301
+ minWidth: 200
1302
+ } }, (0, react.createElement)("div", { style: {
1303
+ display: "flex",
1304
+ alignItems: "center",
1305
+ gap: 8
1306
+ } }, label("API Key"), (0, react.createElement)("span", { style: {
1307
+ fontSize: 11,
1308
+ padding: "1px 8px",
1309
+ borderRadius: 999,
1310
+ background: keyConfigured ? "rgba(22,163,74,.15)" : "var(--dsh-color-muted, #8b90a0)",
1311
+ color: keyConfigured ? "#16a34a" : "inherit"
1312
+ } }, keyConfigured ? `已配置 ••••${keyMeta?.last4 ?? ""}` : "未配置")), (0, react.createElement)("input", {
1313
+ type: "password",
1314
+ "aria-label": "API Key",
1315
+ placeholder: keyConfigured ? "输入新 Key 可覆盖;留空保持不变" : "sk-...",
1316
+ value: advisor.apiKey ?? "",
1317
+ onChange: (e) => updateAdvisor(index, {
1318
+ apiKey: e.target.value,
1319
+ clearApiKey: false
1320
+ }),
1321
+ style: {
1322
+ ...settingsInputStyle,
1323
+ marginTop: 4
1324
+ }
1325
+ })), keyConfigured ? (0, react.createElement)("button", {
1326
+ type: "button",
1327
+ disabled: saving,
1328
+ onClick: () => updateAdvisor(index, {
1329
+ apiKey: "",
1330
+ clearApiKey: true
1331
+ }),
1332
+ style: {
1333
+ cursor: "pointer",
1334
+ border: "1px solid var(--dsh-color-border, #3a3f4b)",
1335
+ background: "transparent",
1336
+ color: "inherit",
1337
+ borderRadius: 4,
1338
+ padding: "4px 10px",
1339
+ fontSize: 12,
1340
+ whiteSpace: "nowrap"
1341
+ }
1342
+ }, "清除") : null),
1343
+ (0, react.createElement)("div", { style: {
1344
+ fontSize: 12,
1345
+ color: "var(--dsh-color-muted, #8b90a0)",
1346
+ marginTop: 2
1347
+ } }, keyConfigured ? "已保存该供应商的 API Key(仅显示末 4 位);留空保存保持原 Key,输入新 Key 覆盖。" : "直接填入 API Key,保存到本地配置。"),
1348
+ label("协议"),
1349
+ (0, react.createElement)("select", {
1350
+ "aria-label": "协议",
1351
+ value: advisor.protocol ?? "openai",
1352
+ onChange: (e) => updateAdvisor(index, { protocol: e.target.value }),
1353
+ style: settingsInputStyle
1354
+ }, (0, react.createElement)("option", { value: "openai" }, "OpenAI 兼容"), (0, react.createElement)("option", { value: "anthropic" }, "Anthropic"), (0, react.createElement)("option", { value: "gemini" }, "Gemini"))
1355
+ ];
1356
+ })(), (0, react.createElement)("textarea", {
1357
+ "aria-label": "systemPrompt",
1358
+ placeholder: "systemPrompt",
1359
+ rows: 2,
1360
+ value: advisor.systemPrompt,
1361
+ onChange: (e) => updateAdvisor(index, { systemPrompt: e.target.value }),
1362
+ style: {
1363
+ ...settingsInputStyle,
1364
+ marginTop: 4,
1365
+ resize: "vertical"
1366
+ }
1367
+ }), (0, react.createElement)("div", { style: {
1368
+ display: "flex",
1369
+ gap: 8,
1370
+ marginTop: 4
1371
+ } }, (0, react.createElement)("input", {
1372
+ type: "number",
1373
+ placeholder: "temperature",
1374
+ value: advisor.temperature ?? "",
1375
+ onChange: (e) => updateAdvisor(index, { temperature: e.target.value === "" ? void 0 : Number(e.target.value) }),
1376
+ style: {
1377
+ ...settingsInputStyle,
1378
+ width: 90
1379
+ }
1380
+ }), (0, react.createElement)("input", {
1381
+ type: "number",
1382
+ placeholder: "maxTokens",
1383
+ value: advisor.maxTokens ?? "",
1384
+ onChange: (e) => updateAdvisor(index, { maxTokens: e.target.value === "" ? void 0 : Number(e.target.value) }),
1385
+ style: {
1386
+ ...settingsInputStyle,
1387
+ width: 120
1388
+ }
1389
+ }))))), (0, react.createElement)("div", { style: settingsCardFooterStyle }, msg ? (0, react.createElement)("span", { style: {
1390
+ flex: 1,
1391
+ minWidth: 0,
1392
+ color: "#16a34a",
1393
+ fontSize: 12
1394
+ } }, msg) : null, error ? (0, react.createElement)("span", { style: {
1395
+ flex: 1,
1396
+ minWidth: 0,
1397
+ color: "#e11d48",
1398
+ fontSize: 12
1399
+ } }, error) : null, (0, react.createElement)("button", {
1400
+ type: "button",
1401
+ disabled: !dirty || saving,
1402
+ onClick: () => void reload(),
1403
+ style: {
1404
+ ...settingsFooterButtonStyle,
1405
+ border: "1px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
1406
+ background: "transparent",
1407
+ color: "inherit"
1408
+ }
1409
+ }, "放弃更改"), (0, react.createElement)("button", {
1410
+ type: "button",
1411
+ disabled: !dirty || saving,
1412
+ onClick: () => void save(),
1413
+ style: {
1414
+ ...settingsFooterButtonStyle,
1415
+ background: "var(--dsw-alias-label-primary, #e6e9f0)",
1416
+ color: "var(--dsw-alias-bg-layer-3, #151821)"
1417
+ }
1418
+ }, saving ? "保存中…" : "保存设置")))) : null);
1419
+ }
1420
+ const name = "dsh-advisor-group";
1421
+ const inject = [
1422
+ "uiConversation",
1423
+ "slots",
1424
+ "locale"
1425
+ ];
1426
+ function apply(ctx) {
1427
+ ctx.uiConversation.events.register(advisorGroupDefinition);
1428
+ ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
1429
+ name: "conversation.chat.node",
1430
+ key: "advisor-group",
1431
+ locale: "chat"
1432
+ }, AdvisorGroupNodeView));
1433
+ ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
1434
+ name: "settings.plugin.item",
1435
+ key: "advisor-group"
1436
+ }, AdvisorGroupSettingsTab));
1437
+ }
1438
+
1439
+ //#endregion
1440
+ exports.advisorGroupDefinition = advisorGroupDefinition;
1441
+ exports.apply = apply;
1442
+ exports.inject = inject;
1443
+ exports.name = name;
1444
+ return module.exports; } });