dsh-advisor-group 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/README.zh.md +5 -1
- package/lib/client.js +97 -45
- package/lib/index.mjs +1243 -230
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
- **Three ways to activate** — `@顾问群` mention (force-start), same question repeated 3 times without resolution, or main-model self-assessed confidence below the threshold.
|
|
18
18
|
- **Optional Jev semantic pre-classification (off by default)** — when `trigger.jev.enabled` is on, a configured Jev model judges non-force-started consultations first (escalation / high-risk / web-search fit, thresholds under `trigger.jev.*`); if Jev is unavailable the local rules-based classifier still decides. With `trigger.jev.useEnglishState`, the judge reads the caller-supplied English gist (`questionEn`) instead of the Chinese question — display and session records stay Chinese.
|
|
19
19
|
- **Retro CRT chat cards** — green/amber/blue CRT themes, scanlines, LIVE/DONE headers, auto-expanded thinking panel with auto-scroll; advisor Markdown rendered with a link-protocol whitelist (headings, lists, code, quotes, links, tables).
|
|
20
|
+
- **Ask another session (`ask_session`)** — hand one question to another live session — by exact session id, exact title, or a natural partial name such as the workspace name (`极简遥控器`) — and read what that session said between the delivery receipt and its next whole-session idle. The result is explicitly **not** a one-to-one reply, and the asking side renders the exchange in the same retro chat-group card. Guards: hop ≤ 2, no self/subagent target, one wait per session, a 32 KB payload budget; the target's turn is never cancelled.
|
|
20
21
|
- **Provider presets (11 platforms · 26 presets)** — DeepSeek, Moonshot Kimi, Kimi Code, Aliyun Bailian, Zhipu AI, OpenAI, Claude, Gemini, SiliconFlow, AIHubMix, OpenRouter (OpenAI/Anthropic-compatible variants included).
|
|
21
22
|
- **Security-minded by design** — API keys use official `SecretField` semantics (never returned to the browser; `apiKeysByProvider` key history is server-side only), SSRF-guarded diagnostics (https-only / loopback, no IP literals, no redirects), per-boot token auth on `/advisor-group/*` routes, and an atomic **configurable daily consultation cap** (default 50, can be disabled) persisted across restarts.
|
|
22
23
|
- **Runtime toggle** — `toggle_advisor_group` enables/disables the plugin and persists the flag to settings.
|
|
@@ -58,7 +59,7 @@ npx @deepseek-ai/dsh web
|
|
|
58
59
|
- type `@顾问群` in your question to force a consultation, or
|
|
59
60
|
- ask a professional/uncertain question — the plugin escalates automatically when appropriate.
|
|
60
61
|
|
|
61
|
-
`ask_advisors` tools available to the model: `ask_advisors`, `toggle_advisor_group`.
|
|
62
|
+
`ask_advisors` tools available to the model: `ask_advisors`, `toggle_advisor_group`, `ask_session`.
|
|
62
63
|
|
|
63
64
|
## ⚙️ Configuration
|
|
64
65
|
|
|
@@ -100,6 +101,8 @@ One consultation is not one call but a chain of them. With the defaults (`maxRou
|
|
|
100
101
|
|
|
101
102
|
Billing follows each channel: advisors bill on the route and model you gave them, while the driver bills on the current session's model. To keep costs down, lower `maxRounds`, configure fewer advisors, set `tools: 'off'` for advisors, and pick a daily cap you are comfortable with. Resuming after an interruption ("▶ continue") only asks the advisors that had not finished, so it costs less than starting over.
|
|
102
103
|
|
|
104
|
+
`ask_session` is outside the consultation quota: it wakes the target session, which answers with one ordinary turn billed on that session's own model, and it never counts as a new consultation.
|
|
105
|
+
|
|
103
106
|
## 🔒 Security & privacy
|
|
104
107
|
|
|
105
108
|
- Direct API keys can be stored in the DSH `settings.yaml` (marked secret, never returned to the browser by `describe`); prefer `apiKeyEnv` (env-var mode) if you don't want keys on disk.
|
|
@@ -113,6 +116,7 @@ Billing follows each channel: advisors bill on the route and model you gave them
|
|
|
113
116
|
|
|
114
117
|
- A hard crash of the DSH host can leave an already-open card showing **LIVE** until the page is refreshed (a disconnect notice now appears); refresh restores the true state from the session log.
|
|
115
118
|
- Risk notes are **fact-based, never text-based**: a consultation reports truncation (an advisor stream cut off by timeout or network) and cancellation/stop. Users see them on **stopped or completed** cards and in the session log, and the main model receives them in the `ask_advisors` result. Advisor prose is not scanned for keywords — the earlier text heuristic was removed in 0.1.1 because it also fired on plain negations such as 「没有任何风险」.
|
|
119
|
+
- Cross-session asking (`ask_session`) has no settings switch yet, so the tool is visible in every session and relies on its own description to keep use to explicit user requests; two sessions asking each other at the same time is not detected.
|
|
116
120
|
|
|
117
121
|
## 🛠️ Development
|
|
118
122
|
|
package/README.zh.md
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
- **三种触发方式**:`@顾问群` 提及(强制启动)、同一问题重复 3 次未解决、主模型自评置信度低于阈值。
|
|
20
20
|
- **Jev 语义前置分类(可选,默认关闭)**:开启 `trigger.jev.enabled` 后,未强制启动的咨询先由配置的 Jev 模型语义判断(是否该找顾问 / 是否高风险 / 是否更适合联网,阈值在 `trigger.jev.*`);Jev 不可用时仍由本地规则分类器判定。`trigger.jev.useEnglishState` 开启后,判定改读主模型附带的英文概要(`questionEn`),展示与会话记录仍用中文。
|
|
21
21
|
- **复古 CRT 聊天组卡片**:绿/琥珀/蓝三主题、扫描线、LIVE/DONE 标题、💭 思考面板默认展开自动滚底;顾问正文走轻量 Markdown 渲染(链接协议白名单,支持标题/列表/代码/引用/链接/表格)。
|
|
22
|
+
- **跨会话提问(`ask_session`)**:把一个问题交给另一个**活着的**会话——用完整会话号、一字不差的标题,或者自然的名字(例如工作目录名「极简遥控器」)都可以——并唤醒它;拿回的是**从投递回执到对方下一次整体空闲**之间它说的话,明确**不是**逐条对应的回答。提问方一侧用同一套复古聊天组卡片呈现这次对话。护栏:跳数 ≤ 2、不能问自己或子会话、同一会话同时只等一个、载荷上限 32 KB;**永不取消对方的回合**。
|
|
22
23
|
- **供应商预设(11 平台 · 26 预设)**:DeepSeek、月之暗面 Kimi、Kimi Code、阿里云百炼、智谱 AI、OpenAI、Claude、Gemini、硅基流动、AIHubMix、OpenRouter(含 OpenAI / Anthropic 兼容变体)。
|
|
23
24
|
- **注重安全**:API Key 采用官方 `SecretField` 语义(浏览器永不回显;`apiKeysByProvider` 供应商密钥档案仅存服务端);诊断端点 SSRF 加固(仅 https/loopback、拒绝 IP 字面量与重定向);`/advisor-group/*` 路由启动期 token 鉴权;每日新咨询**可配置原子配额**(默认 50,可关闭),持久化跨重启。
|
|
24
25
|
- **运行时开关**:`toggle_advisor_group` 启停插件并持久化到设置。
|
|
@@ -61,7 +62,7 @@ npx @deepseek-ai/dsh web
|
|
|
61
62
|
- 提问时带 `@顾问群` 强制发起咨询,或
|
|
62
63
|
- 直接提专业/不确定的问题——插件会按需自动升级。
|
|
63
64
|
|
|
64
|
-
模型可用工具:`ask_advisors`、`toggle_advisor_group`。
|
|
65
|
+
模型可用工具:`ask_advisors`、`toggle_advisor_group`、`ask_session`。
|
|
65
66
|
|
|
66
67
|
## ⚙️ 配置项
|
|
67
68
|
|
|
@@ -103,6 +104,8 @@ npx @deepseek-ai/dsh web
|
|
|
103
104
|
|
|
104
105
|
费用分别结算:顾问按你给它们配的通道与模型计费,驱动模型按当前会话的模型计费。想控成本,可以降 `maxRounds`、减少顾问数、给顾问设 `tools: 'off'`,再把每日上限设成你能接受的数字。中断后用「▶ 继续聊天」续跑只补问没答完的顾问,不会整场重来,代价低于重开一次。
|
|
105
106
|
|
|
107
|
+
`ask_session` 不走咨询配额:它唤醒目标会话,对方用一次普通回合作答、按其自身模型计费,也不会被算作一次新咨询。
|
|
108
|
+
|
|
106
109
|
## 🔒 安全与隐私
|
|
107
110
|
|
|
108
111
|
- 直连 API Key 可存于 DSH `settings.yaml`(标 secret,官方 `describe` 绝不回传浏览器);不想落盘请用 `apiKeyEnv` 环境变量模式。
|
|
@@ -116,6 +119,7 @@ npx @deepseek-ai/dsh web
|
|
|
116
119
|
|
|
117
120
|
- DSH 宿主进程硬崩溃时,已打开的卡片在刷新前可能仍显示 **LIVE**(现在会先出现断线提示);刷新后按会话日志恢复真实状态。
|
|
118
121
|
- 风险提示**只看事实、不看措辞**:截断(顾问输出被超时或网络中断)与取消(本轮咨询被停止)两种。用户可在**已停止或已完成**的卡片与会话日志中看到,主模型随 `ask_advisors` 返回收到。顾问正文不做关键词扫描——早期那套「凭字眼猜有没有保留意见」的启发式已在 0.1.1 移除,原因是它连「没有任何风险」这样的否定句也会当成风险提示(盲测里 4 条纯否定句全部误报)。
|
|
122
|
+
- 跨会话提问(`ask_session`)目前**没有设置开关**,工具在所有会话里可见,靠工具说明把使用限制在「用户明确要求时」;两个会话互相等待的情况尚未检测。
|
|
119
123
|
|
|
120
124
|
## 🛠️ 开发
|
|
121
125
|
|
package/lib/client.js
CHANGED
|
@@ -283,12 +283,60 @@ const DEFAULT_ADVISOR_PROMPT = `
|
|
|
283
283
|
* advisor-group/message -> update (live bubbles)
|
|
284
284
|
* advisor-group/end -> update (final synopsis)
|
|
285
285
|
*/
|
|
286
|
-
|
|
286
|
+
/** The per-boot token as injected into the page, when that injection arrived. */
|
|
287
|
+
function injectedAdvisorGroupToken() {
|
|
287
288
|
return globalThis.__ADVISOR_GROUP_TOKEN__ ?? "";
|
|
288
289
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
290
|
+
let advisorGroupTokenCache = "";
|
|
291
|
+
let advisorGroupTokenFetch = null;
|
|
292
|
+
/** Token known so far: the injected one wins, otherwise the last handshake. */
|
|
293
|
+
function currentAdvisorGroupToken() {
|
|
294
|
+
return injectedAdvisorGroupToken() || advisorGroupTokenCache;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* The host normally injects the per-boot token into the served index. The
|
|
298
|
+
* desktop shell can compose its window before this plugin registers that
|
|
299
|
+
* injection, so the page may never receive it — fall back to fetching it from
|
|
300
|
+
* the plugin's own handshake route.
|
|
301
|
+
*/
|
|
302
|
+
async function resolveAdvisorGroupToken(force = false) {
|
|
303
|
+
if (!force) {
|
|
304
|
+
const known = currentAdvisorGroupToken();
|
|
305
|
+
if (known) return known;
|
|
306
|
+
}
|
|
307
|
+
if (!advisorGroupTokenFetch) advisorGroupTokenFetch = (async () => {
|
|
308
|
+
try {
|
|
309
|
+
const res = await fetch(`/advisor-group/boot-token`, { headers: { accept: "application/json" } });
|
|
310
|
+
if (!res.ok) return "";
|
|
311
|
+
const body = await res.json();
|
|
312
|
+
return typeof body.token === "string" ? body.token : "";
|
|
313
|
+
} catch {
|
|
314
|
+
return "";
|
|
315
|
+
}
|
|
316
|
+
})().then((token) => {
|
|
317
|
+
advisorGroupTokenCache = token;
|
|
318
|
+
advisorGroupTokenFetch = null;
|
|
319
|
+
return token;
|
|
320
|
+
});
|
|
321
|
+
return await advisorGroupTokenFetch || injectedAdvisorGroupToken();
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* `fetch` with the per-boot token attached. A 401 means the token rotated (a
|
|
325
|
+
* live plugin reload mints a new one) or that the injected copy was stale, so
|
|
326
|
+
* refresh it once and retry before surfacing the failure.
|
|
327
|
+
*/
|
|
328
|
+
async function authedFetch(path, init = {}) {
|
|
329
|
+
const send = (token) => fetch(path, {
|
|
330
|
+
...init,
|
|
331
|
+
headers: {
|
|
332
|
+
...init.headers,
|
|
333
|
+
...token ? { "x-advisor-group-token": token } : {}
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
const res = await send(await resolveAdvisorGroupToken());
|
|
337
|
+
if (res.status !== 401) return res;
|
|
338
|
+
const refreshed = await resolveAdvisorGroupToken(true);
|
|
339
|
+
return refreshed ? send(refreshed) : res;
|
|
292
340
|
}
|
|
293
341
|
/**
|
|
294
342
|
* Browser-side twin of `sanitizeAdvisorContent` (host module cannot be
|
|
@@ -315,7 +363,8 @@ function viewData(state) {
|
|
|
315
363
|
...state.context === void 0 ? {} : { context: state.context },
|
|
316
364
|
advisors: state.advisors,
|
|
317
365
|
messages: state.messages,
|
|
318
|
-
...state.summary === void 0 ? {} : { summary: state.summary }
|
|
366
|
+
...state.summary === void 0 ? {} : { summary: state.summary },
|
|
367
|
+
...state.mode === void 0 ? {} : { mode: state.mode }
|
|
319
368
|
};
|
|
320
369
|
}
|
|
321
370
|
const advisorGroupDefinition = {
|
|
@@ -335,7 +384,7 @@ const advisorGroupDefinition = {
|
|
|
335
384
|
start: (_context, match) => {
|
|
336
385
|
if (match.event.type !== "advisor-group/start") throw new Error("advisor-group requires advisor-group/start");
|
|
337
386
|
const data = match.event.data;
|
|
338
|
-
|
|
387
|
+
const base = {
|
|
339
388
|
sessionId: data.sessionId,
|
|
340
389
|
turn: data.turn,
|
|
341
390
|
step: data.step,
|
|
@@ -351,6 +400,11 @@ const advisorGroupDefinition = {
|
|
|
351
400
|
}],
|
|
352
401
|
status: "running"
|
|
353
402
|
};
|
|
403
|
+
if (data.kind === "ask") return {
|
|
404
|
+
...base,
|
|
405
|
+
mode: "ask"
|
|
406
|
+
};
|
|
407
|
+
return base;
|
|
354
408
|
},
|
|
355
409
|
update: (context, match) => {
|
|
356
410
|
if (match.event.type === "advisor-group/message") {
|
|
@@ -710,11 +764,11 @@ function buildStepSequence(actionDescriptions, thinkingSegments, toolViews) {
|
|
|
710
764
|
* 💭 思考·1 → ⛭ 工具×1(输入/输出)→ 💭 思考·2 → ⛭ 工具×2 → … → 📄 正文
|
|
711
765
|
* Main/system messages stay a single collapsible row.
|
|
712
766
|
*/
|
|
713
|
-
function AdvisorSteps({ message }) {
|
|
767
|
+
function AdvisorSteps({ message, plain = false }) {
|
|
714
768
|
const isMain = message.role === "main";
|
|
715
769
|
const isSystem = message.role === "system";
|
|
716
770
|
const label = isMain ? "YOU" : isSystem ? "SYSTEM" : message.advisorName ?? message.advisorId ?? "ADVISOR";
|
|
717
|
-
const group = isMain
|
|
771
|
+
const group = isMain || isSystem || plain ? label : `${label} · R${message.round ?? 1}`;
|
|
718
772
|
const truncated = message.truncated;
|
|
719
773
|
const toolViews = buildToolStepViews(message.toolSteps);
|
|
720
774
|
const thinkingSegments = message.thinkingSegments && message.thinkingSegments.length > 0 ? message.thinkingSegments : message.thinking ? [message.thinking] : [];
|
|
@@ -767,7 +821,7 @@ function AdvisorSteps({ message }) {
|
|
|
767
821
|
if (liveToolPhase) continue;
|
|
768
822
|
children.push((0, react.createElement)(StepRow, {
|
|
769
823
|
key: `body-${index}`,
|
|
770
|
-
title: `${group} 📄 正文`,
|
|
824
|
+
title: plain ? group : `${group} 📄 正文`,
|
|
771
825
|
badges: truncated ? [(0, react.createElement)("span", { style: badgeStyle("#f87171") }, truncated.reason === "timeout" ? "⏱ 响应超时截断" : "⚠ 流中断")] : [],
|
|
772
826
|
defaultOpen: true,
|
|
773
827
|
color: "#4ade80",
|
|
@@ -793,6 +847,7 @@ const NOTICE_TTL_MS = 6e3;
|
|
|
793
847
|
function AdvisorGroupNodeView(props) {
|
|
794
848
|
const data = props.node.data;
|
|
795
849
|
const sessionId = data.sessionId;
|
|
850
|
+
const isAsk = data.mode === "ask";
|
|
796
851
|
const [live, setLive] = (0, react.useState)({});
|
|
797
852
|
const [contextCollapsed, setContextCollapsed] = (0, react.useState)(true);
|
|
798
853
|
const lastEventIdRef = (0, react.useRef)(0);
|
|
@@ -816,9 +871,20 @@ function AdvisorGroupNodeView(props) {
|
|
|
816
871
|
const clearStreamNotice = () => {
|
|
817
872
|
setTransientNotice((prev) => prev && prev.kind === "stream" ? null : prev);
|
|
818
873
|
};
|
|
874
|
+
const [streamTokenReady, setStreamTokenReady] = (0, react.useState)(() => currentAdvisorGroupToken() !== "");
|
|
875
|
+
(0, react.useEffect)(() => {
|
|
876
|
+
if (streamTokenReady) return;
|
|
877
|
+
let alive = true;
|
|
878
|
+
resolveAdvisorGroupToken().then((token) => {
|
|
879
|
+
if (alive && token) setStreamTokenReady(true);
|
|
880
|
+
});
|
|
881
|
+
return () => {
|
|
882
|
+
alive = false;
|
|
883
|
+
};
|
|
884
|
+
}, [streamTokenReady]);
|
|
819
885
|
(0, react.useEffect)(() => {
|
|
820
|
-
if (!sessionId) return;
|
|
821
|
-
const es = new EventSource(`/advisor-group/stream?sessionId=${encodeURIComponent(sessionId)}&lastEventId=${lastEventIdRef.current}&bootId=${encodeURIComponent(lastBootIdRef.current)}&token=${encodeURIComponent(
|
|
886
|
+
if (!sessionId || isAsk || !streamTokenReady) return;
|
|
887
|
+
const es = new EventSource(`/advisor-group/stream?sessionId=${encodeURIComponent(sessionId)}&lastEventId=${lastEventIdRef.current}&bootId=${encodeURIComponent(lastBootIdRef.current)}&token=${encodeURIComponent(currentAdvisorGroupToken())}`);
|
|
822
888
|
es.onopen = () => {
|
|
823
889
|
clearStreamNotice();
|
|
824
890
|
};
|
|
@@ -896,7 +962,7 @@ function AdvisorGroupNodeView(props) {
|
|
|
896
962
|
noticeTimerRef.current = null;
|
|
897
963
|
}
|
|
898
964
|
};
|
|
899
|
-
}, [sessionId]);
|
|
965
|
+
}, [sessionId, streamTokenReady]);
|
|
900
966
|
const mergedMessages = data.messages.map((message) => {
|
|
901
967
|
if (message.role !== "advisor") return message;
|
|
902
968
|
const advisorId = message.advisorId ?? "";
|
|
@@ -927,7 +993,7 @@ function AdvisorGroupNodeView(props) {
|
|
|
927
993
|
};
|
|
928
994
|
});
|
|
929
995
|
const shortId = data.sessionId.length > 8 ? data.sessionId.slice(0, 8) : data.sessionId;
|
|
930
|
-
const title = data.status === "completed" ? "ADVISOR GROUP · DONE" : data.status === "cancelled" ? "ADVISOR GROUP · STOPPED" : "ADVISOR GROUP · LIVE";
|
|
996
|
+
const title = isAsk ? data.status === "completed" ? "跨会话提问 · 已完成" : "跨会话提问 · 等待应答" : data.status === "completed" ? "ADVISOR GROUP · DONE" : data.status === "cancelled" ? "ADVISOR GROUP · STOPPED" : "ADVISOR GROUP · LIVE";
|
|
931
997
|
return (0, react.createElement)("div", {
|
|
932
998
|
style: rootStyle,
|
|
933
999
|
role: "log",
|
|
@@ -939,17 +1005,14 @@ function AdvisorGroupNodeView(props) {
|
|
|
939
1005
|
alignItems: "center",
|
|
940
1006
|
gap: 8
|
|
941
1007
|
}
|
|
942
|
-
}, data.status === "running" ? (0, react.createElement)("button", {
|
|
1008
|
+
}, data.status === "running" && !isAsk ? (0, react.createElement)("button", {
|
|
943
1009
|
type: "button",
|
|
944
1010
|
onClick: () => {
|
|
945
1011
|
(async () => {
|
|
946
1012
|
try {
|
|
947
|
-
const body = await (await
|
|
1013
|
+
const body = await (await authedFetch("/advisor-group/stop", {
|
|
948
1014
|
method: "POST",
|
|
949
|
-
headers: {
|
|
950
|
-
"content-type": "application/json",
|
|
951
|
-
...authHeaders()
|
|
952
|
-
},
|
|
1015
|
+
headers: { "content-type": "application/json" },
|
|
953
1016
|
body: JSON.stringify({ sessionId })
|
|
954
1017
|
})).json();
|
|
955
1018
|
if (body.ok === false) showTransientNotice(body.error ?? "请求未被服务端接受,请稍后重试。", "action", NOTICE_TTL_MS);
|
|
@@ -967,17 +1030,14 @@ function AdvisorGroupNodeView(props) {
|
|
|
967
1030
|
padding: "2px 8px",
|
|
968
1031
|
fontSize: 11
|
|
969
1032
|
}
|
|
970
|
-
}, "⏹ 停止") : data.status === "cancelled" ? (0, react.createElement)("button", {
|
|
1033
|
+
}, "⏹ 停止") : data.status === "cancelled" && !isAsk ? (0, react.createElement)("button", {
|
|
971
1034
|
type: "button",
|
|
972
1035
|
onClick: () => {
|
|
973
1036
|
(async () => {
|
|
974
1037
|
try {
|
|
975
|
-
const body = await (await
|
|
1038
|
+
const body = await (await authedFetch("/advisor-group/resume", {
|
|
976
1039
|
method: "POST",
|
|
977
|
-
headers: {
|
|
978
|
-
"content-type": "application/json",
|
|
979
|
-
...authHeaders()
|
|
980
|
-
},
|
|
1040
|
+
headers: { "content-type": "application/json" },
|
|
981
1041
|
body: JSON.stringify({ sessionId })
|
|
982
1042
|
})).json();
|
|
983
1043
|
if (body.ok === false) showTransientNotice(body.error ?? "请求未被服务端接受,请稍后重试。", "action", NOTICE_TTL_MS);
|
|
@@ -999,9 +1059,10 @@ function AdvisorGroupNodeView(props) {
|
|
|
999
1059
|
type: "button",
|
|
1000
1060
|
onClick: () => setContextCollapsed((value) => !value),
|
|
1001
1061
|
style: contextToggleStyle
|
|
1002
|
-
}, `📋 项目背景 ${contextCollapsed ? "▸" : "▾"}`), !contextCollapsed ? (0, react.createElement)("div", { style: contextBodyStyle }, data.context) : null) : null, (0, react.createElement)("div", { style: advisorsLineStyle }, `ADVISORS: ${data.advisors.map((a) => a.avatar ? `${a.avatar} ${a.name}` : a.name).join(" · ")}`), mergedMessages.map((message, index) => (0, react.createElement)(AdvisorSteps, {
|
|
1062
|
+
}, `📋 项目背景 ${contextCollapsed ? "▸" : "▾"}`), !contextCollapsed ? (0, react.createElement)("div", { style: contextBodyStyle }, data.context) : null) : null, (0, react.createElement)("div", { style: advisorsLineStyle }, isAsk ? `提问方: ${data.advisors[0]?.name ?? "?"} → 目标会话: ${data.advisors[1]?.name ?? "?"}` : `ADVISORS: ${data.advisors.map((a) => a.avatar ? `${a.avatar} ${a.name}` : a.name).join(" · ")}`), mergedMessages.map((message, index) => (0, react.createElement)(AdvisorSteps, {
|
|
1003
1063
|
key: index,
|
|
1004
|
-
message
|
|
1064
|
+
message,
|
|
1065
|
+
plain: isAsk
|
|
1005
1066
|
})), data.status === "completed" && data.summary?.conclusion ? (0, react.createElement)("div", { style: {
|
|
1006
1067
|
borderTop: "1px dashed #2f9e44",
|
|
1007
1068
|
marginTop: 8,
|
|
@@ -1010,7 +1071,7 @@ function AdvisorGroupNodeView(props) {
|
|
|
1010
1071
|
color: "#86efac",
|
|
1011
1072
|
fontWeight: 700,
|
|
1012
1073
|
marginBottom: 4
|
|
1013
|
-
} }, "📌 综合结论"), (0, react.createElement)("div", null, renderMarkdown(data.summary.conclusion))) : null, data.status === "completed" && (data.summary?.riskNotes ?? []).length > 0 ? (0, react.createElement)("div", { style: {
|
|
1074
|
+
} }, isAsk ? "📌 投递说明" : "📌 综合结论"), (0, react.createElement)("div", null, renderMarkdown(data.summary.conclusion))) : null, data.status === "completed" && (data.summary?.riskNotes ?? []).length > 0 ? (0, react.createElement)("div", { style: {
|
|
1014
1075
|
borderTop: "1px dashed #2f9e44",
|
|
1015
1076
|
marginTop: 8,
|
|
1016
1077
|
paddingTop: 8
|
|
@@ -1035,7 +1096,7 @@ function AdvisorGroupNodeView(props) {
|
|
|
1035
1096
|
fontSize: 12,
|
|
1036
1097
|
marginTop: 2
|
|
1037
1098
|
}
|
|
1038
|
-
}, note))) : null, data.status === "running" ? (0, react.createElement)("div", { style: waitingStyle }, "▊ AWAITING RESPONSES…") : null, transientNotice ? (0, react.createElement)("div", { style: noticeStyle }, transientNotice.text) : null);
|
|
1099
|
+
}, note))) : null, data.status === "running" ? (0, react.createElement)("div", { style: waitingStyle }, isAsk ? "▊ 已投递,等待对方回应…" : "▊ AWAITING RESPONSES…") : null, transientNotice ? (0, react.createElement)("div", { style: noticeStyle }, transientNotice.text) : null);
|
|
1039
1100
|
}
|
|
1040
1101
|
const settingsRootStyle = {
|
|
1041
1102
|
fontFamily: "'Inter', 'Segoe UI', system-ui, sans-serif",
|
|
@@ -1235,7 +1296,7 @@ function AdvisorGroupSettingsTab() {
|
|
|
1235
1296
|
setMsg("");
|
|
1236
1297
|
setError("");
|
|
1237
1298
|
try {
|
|
1238
|
-
const [configData, providerData] = await Promise.all([
|
|
1299
|
+
const [configData, providerData] = await Promise.all([authedFetch("/advisor-group/config").then((r) => r.json()), authedFetch("/advisor-group/providers").then((r) => r.json())]);
|
|
1239
1300
|
if (configData.ok && configData.config) {
|
|
1240
1301
|
setConfig(configData.config);
|
|
1241
1302
|
savedJsonRef.current = JSON.stringify(configData.config);
|
|
@@ -1291,12 +1352,9 @@ function AdvisorGroupSettingsTab() {
|
|
|
1291
1352
|
setError("");
|
|
1292
1353
|
setMsg("");
|
|
1293
1354
|
try {
|
|
1294
|
-
const data = await (await
|
|
1355
|
+
const data = await (await authedFetch("/advisor-group/models", {
|
|
1295
1356
|
method: "POST",
|
|
1296
|
-
headers: {
|
|
1297
|
-
"content-type": "application/json",
|
|
1298
|
-
...authHeaders()
|
|
1299
|
-
},
|
|
1357
|
+
headers: { "content-type": "application/json" },
|
|
1300
1358
|
body: JSON.stringify({
|
|
1301
1359
|
advisorId: advisor.id,
|
|
1302
1360
|
provider: advisor.provider,
|
|
@@ -1332,12 +1390,9 @@ function AdvisorGroupSettingsTab() {
|
|
|
1332
1390
|
setError("");
|
|
1333
1391
|
setMsg("");
|
|
1334
1392
|
try {
|
|
1335
|
-
const data = await (await
|
|
1393
|
+
const data = await (await authedFetch("/advisor-group/test-connection", {
|
|
1336
1394
|
method: "POST",
|
|
1337
|
-
headers: {
|
|
1338
|
-
"content-type": "application/json",
|
|
1339
|
-
...authHeaders()
|
|
1340
|
-
},
|
|
1395
|
+
headers: { "content-type": "application/json" },
|
|
1341
1396
|
body: JSON.stringify({
|
|
1342
1397
|
advisorId: advisor.id,
|
|
1343
1398
|
provider: advisor.provider,
|
|
@@ -1371,12 +1426,9 @@ function AdvisorGroupSettingsTab() {
|
|
|
1371
1426
|
},
|
|
1372
1427
|
advisors: config.advisors.map(({ apiKeyMetaByProvider: _meta, ...rest }) => rest)
|
|
1373
1428
|
};
|
|
1374
|
-
const data = await (await
|
|
1429
|
+
const data = await (await authedFetch("/advisor-group/config", {
|
|
1375
1430
|
method: "POST",
|
|
1376
|
-
headers: {
|
|
1377
|
-
"content-type": "application/json",
|
|
1378
|
-
...authHeaders()
|
|
1379
|
-
},
|
|
1431
|
+
headers: { "content-type": "application/json" },
|
|
1380
1432
|
body: JSON.stringify({
|
|
1381
1433
|
config: configPayload,
|
|
1382
1434
|
expectedRevision: revision
|