dsh-advisor-group 0.1.4 → 0.1.5

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 CHANGED
@@ -844,6 +844,42 @@ function AdvisorSteps({ message, plain = false }) {
844
844
  }
845
845
  /** Auto-hide delay for transient card notices (request failures, resync). */
846
846
  const NOTICE_TTL_MS = 6e3;
847
+ /**
848
+ * Merge the SSE live overlay into the durable messages, per `advisorId::round`.
849
+ * Pure and exported so the rules that matter — `done` hands authority back to
850
+ * the durable message, only uncommitted text streams as the body, a tool-phase
851
+ * body is a pending 📋 action — are testable without a browser.
852
+ */
853
+ function mergeLiveOverlay(messages, live) {
854
+ return messages.map((message) => {
855
+ if (message.role !== "advisor") return message;
856
+ const streamed = live[`${message.advisorId ?? ""}::${message.round ?? 1}`];
857
+ if (!streamed) return message;
858
+ if (streamed.finalized) return message;
859
+ const liveBody = sanitizeClientContent(streamed.content.slice(streamed.committedContentLen));
860
+ const content = liveBody.trim() ? liveBody : sanitizeClientContent(message.content);
861
+ const thinking = (streamed.thinking?.length ?? 0) > (message.thinking?.length ?? 0) ? streamed.thinking : message.thinking;
862
+ const toolSteps = (streamed.toolSteps?.length ?? 0) > (message.toolSteps?.length ?? 0) ? streamed.toolSteps : message.toolSteps ?? [];
863
+ const liveSegments = streamed.thinkingSegments?.length ? streamed.thinkingSegments : [];
864
+ const baseSegments = message.thinkingSegments?.length ? message.thinkingSegments : message.thinking ? [message.thinking] : [];
865
+ const thinkingSegments = liveSegments.length ? liveSegments : baseSegments;
866
+ const liveActions = streamed.contentSegments?.length ? streamed.contentSegments.map((segment) => sanitizeClientContent(segment)) : [];
867
+ const baseActions = message.actionDescriptions?.length ? message.actionDescriptions : [];
868
+ const actionDescriptions = liveActions.length ? liveActions : baseActions;
869
+ return {
870
+ ...message,
871
+ content,
872
+ thinking,
873
+ thinkingSegments,
874
+ toolSteps,
875
+ actionDescriptions,
876
+ ...streamed.phase === "tool" ? {
877
+ liveBody,
878
+ livePhase: streamed.phase
879
+ } : {}
880
+ };
881
+ });
882
+ }
847
883
  function AdvisorGroupNodeView(props) {
848
884
  const data = props.node.data;
849
885
  const sessionId = data.sessionId;
@@ -963,35 +999,7 @@ function AdvisorGroupNodeView(props) {
963
999
  }
964
1000
  };
965
1001
  }, [sessionId, streamTokenReady]);
966
- const mergedMessages = data.messages.map((message) => {
967
- if (message.role !== "advisor") return message;
968
- const advisorId = message.advisorId ?? "";
969
- const streamed = live[`${advisorId}::${message.round ?? 1}`];
970
- if (!streamed) return message;
971
- if (streamed.finalized) return message;
972
- const liveBody = sanitizeClientContent(streamed.content.slice(streamed.committedContentLen));
973
- const content = liveBody.trim() ? liveBody : sanitizeClientContent(message.content);
974
- const thinking = (streamed.thinking?.length ?? 0) > (message.thinking?.length ?? 0) ? streamed.thinking : message.thinking;
975
- const toolSteps = (streamed.toolSteps?.length ?? 0) > (message.toolSteps?.length ?? 0) ? streamed.toolSteps : message.toolSteps ?? [];
976
- const liveSegments = streamed.thinkingSegments?.length ? streamed.thinkingSegments : [];
977
- const baseSegments = message.thinkingSegments?.length ? message.thinkingSegments : message.thinking ? [message.thinking] : [];
978
- const thinkingSegments = liveSegments.length ? liveSegments : baseSegments;
979
- const liveActions = streamed.contentSegments?.length ? streamed.contentSegments.map((segment) => sanitizeClientContent(segment)) : [];
980
- const baseActions = message.actionDescriptions?.length ? message.actionDescriptions : [];
981
- const actionDescriptions = liveActions.length ? liveActions : baseActions;
982
- return {
983
- ...message,
984
- content,
985
- thinking,
986
- thinkingSegments,
987
- toolSteps,
988
- actionDescriptions,
989
- ...streamed.phase === "tool" ? {
990
- liveBody,
991
- livePhase: streamed.phase
992
- } : {}
993
- };
994
- });
1002
+ const mergedMessages = mergeLiveOverlay(data.messages, live);
995
1003
  const shortId = data.sessionId.length > 8 ? data.sessionId.slice(0, 8) : data.sessionId;
996
1004
  const title = isAsk ? data.status === "completed" ? "跨会话提问 · 已完成" : "跨会话提问 · 等待应答" : data.status === "completed" ? "ADVISOR GROUP · DONE" : data.status === "cancelled" ? "ADVISOR GROUP · STOPPED" : "ADVISOR GROUP · LIVE";
997
1005
  return (0, react.createElement)("div", {
@@ -1098,11 +1106,31 @@ function AdvisorGroupNodeView(props) {
1098
1106
  }
1099
1107
  }, 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);
1100
1108
  }
1109
+ const SETTINGS_ACCESSIBILITY_CSS = `
1110
+ .dsh-advisor-group-settings button:focus-visible,
1111
+ .dsh-advisor-group-settings input:focus-visible,
1112
+ .dsh-advisor-group-settings select:focus-visible,
1113
+ .dsh-advisor-group-settings textarea:focus-visible {
1114
+ outline: 2px solid var(--dsw-alias-brand-primary, #3f8cff);
1115
+ outline-offset: 2px;
1116
+ }
1117
+ .dsh-advisor-group-settings button:disabled {
1118
+ cursor: not-allowed;
1119
+ opacity: .55;
1120
+ }
1121
+ .dsh-advisor-group-settings__fold-header:hover {
1122
+ background: var(--dsw-alias-bg-layer-2, rgba(127, 127, 127, .08));
1123
+ }
1124
+ `;
1101
1125
  const settingsRootStyle = {
1126
+ width: "100%",
1127
+ maxWidth: 840,
1128
+ margin: "0 auto",
1129
+ paddingTop: 12,
1130
+ color: "inherit",
1102
1131
  fontFamily: "'Inter', 'Segoe UI', system-ui, sans-serif",
1103
1132
  fontSize: 13,
1104
- maxWidth: 720,
1105
- color: "inherit"
1133
+ lineHeight: 1.5
1106
1134
  };
1107
1135
  const settingsCardStyle = (open) => ({
1108
1136
  boxSizing: "border-box",
@@ -1168,13 +1196,19 @@ const settingsCardLoadingStyle = {
1168
1196
  lineHeight: 1.5
1169
1197
  };
1170
1198
  const settingsCardFooterStyle = {
1171
- borderTop: "0.5px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
1199
+ position: "sticky",
1200
+ bottom: 12,
1201
+ zIndex: 20,
1202
+ display: "flex",
1203
+ flexWrap: "wrap",
1172
1204
  justifyContent: "flex-end",
1173
1205
  alignItems: "center",
1174
1206
  gap: 8,
1175
- padding: "12px 0 4px",
1176
- display: "flex",
1177
- flexWrap: "wrap"
1207
+ marginTop: 12,
1208
+ padding: 12,
1209
+ border: "0.5px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
1210
+ borderRadius: 12,
1211
+ background: "var(--dsw-alias-bg-layer-2, #191d26)"
1178
1212
  };
1179
1213
  const settingsFooterButtonStyle = {
1180
1214
  appearance: "none",
@@ -1186,6 +1220,17 @@ const settingsFooterButtonStyle = {
1186
1220
  fontSize: 13,
1187
1221
  lineHeight: 1.5
1188
1222
  };
1223
+ const settingsSecondaryButtonStyle = {
1224
+ ...settingsFooterButtonStyle,
1225
+ border: "1px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
1226
+ background: "transparent",
1227
+ color: "inherit",
1228
+ whiteSpace: "nowrap"
1229
+ };
1230
+ const settingsDangerButtonStyle = {
1231
+ ...settingsSecondaryButtonStyle,
1232
+ color: "var(--dsw-alias-state-error-primary, #e11d48)"
1233
+ };
1189
1234
  /** Collapsible header shared by the loading and settled card states. */
1190
1235
  function settingsCardHeader(open, dirty, onToggle) {
1191
1236
  return (0, react.createElement)("button", {
@@ -1194,7 +1239,7 @@ function settingsCardHeader(open, dirty, onToggle) {
1194
1239
  "aria-expanded": open,
1195
1240
  "aria-label": `${open ? "收起" : "展开"}设置:顾问群`,
1196
1241
  onClick: onToggle
1197
- }, (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", {
1242
+ }, (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", {
1198
1243
  width: 14,
1199
1244
  height: 14,
1200
1245
  viewBox: "0 0 16 16",
@@ -1213,37 +1258,134 @@ function settingsCardHeader(open, dirty, onToggle) {
1213
1258
  strokeLinejoin: "round"
1214
1259
  })));
1215
1260
  }
1216
- const settingsSectionStyle = {
1217
- border: "1px solid var(--dsh-color-border, #3a3f4b)",
1218
- borderRadius: 8,
1219
- padding: "10px 12px",
1220
- marginBottom: 10,
1261
+ const settingsFoldStyle = {
1262
+ marginBottom: 12,
1263
+ overflow: "hidden",
1264
+ border: "0.5px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
1265
+ borderRadius: 12,
1221
1266
  background: "transparent"
1222
1267
  };
1268
+ const settingsFoldHeaderStyle = {
1269
+ display: "flex",
1270
+ alignItems: "center",
1271
+ gap: 12,
1272
+ width: "100%",
1273
+ minHeight: 54,
1274
+ boxSizing: "border-box",
1275
+ padding: "12px 14px",
1276
+ border: "none",
1277
+ background: "transparent",
1278
+ color: "inherit",
1279
+ font: "inherit",
1280
+ textAlign: "left",
1281
+ cursor: "pointer",
1282
+ transition: "background .16s"
1283
+ };
1284
+ const settingsFoldHeaderTextStyle = {
1285
+ flex: 1,
1286
+ minWidth: 0,
1287
+ display: "grid",
1288
+ gap: 3
1289
+ };
1290
+ const settingsFoldCaretStyle = {
1291
+ width: 16,
1292
+ flex: "none",
1293
+ color: "var(--dsw-alias-label-tertiary, var(--dsh-color-muted, #8b90a0))",
1294
+ fontSize: 14,
1295
+ textAlign: "center"
1296
+ };
1297
+ const settingsFoldTitleStyle = {
1298
+ color: "var(--dsw-alias-label-primary, #e6e9f0)",
1299
+ fontSize: 14,
1300
+ fontWeight: 600,
1301
+ lineHeight: 1.4
1302
+ };
1303
+ const settingsFoldDescriptionStyle = {
1304
+ color: "var(--dsw-alias-label-tertiary, var(--dsh-color-muted, #8b90a0))",
1305
+ fontSize: 12,
1306
+ lineHeight: 1.5
1307
+ };
1308
+ const settingsFoldBodyStyle = {
1309
+ padding: "14px 16px 16px",
1310
+ borderTop: "0.5px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))"
1311
+ };
1312
+ const settingsSectionActionsStyle = {
1313
+ display: "flex",
1314
+ justifyContent: "flex-end",
1315
+ alignItems: "center",
1316
+ gap: 10,
1317
+ marginTop: 14,
1318
+ paddingTop: 12,
1319
+ borderTop: "0.5px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))"
1320
+ };
1321
+ /** Every settings section starts folded: the page opens as a compact index. */
1322
+ const SETTINGS_FOLD_KEYS = [
1323
+ "basics",
1324
+ "advisors",
1325
+ "discussion",
1326
+ "trigger",
1327
+ "swarm",
1328
+ "advanced"
1329
+ ];
1330
+ const settingsGridStyle = {
1331
+ display: "grid",
1332
+ gridTemplateColumns: "repeat(auto-fit, minmax(min(280px, 100%), 1fr))",
1333
+ gap: 12,
1334
+ alignItems: "start"
1335
+ };
1223
1336
  const settingsLabelStyle = {
1224
1337
  display: "block",
1338
+ marginBottom: 5,
1339
+ color: "var(--dsw-alias-label-secondary, var(--dsh-color-muted, #8b90a0))",
1340
+ fontSize: 12,
1341
+ fontWeight: 500
1342
+ };
1343
+ const settingsHelpStyle = {
1344
+ color: "var(--dsw-alias-label-tertiary, var(--dsh-color-muted, #8b90a0))",
1345
+ fontSize: 12,
1346
+ lineHeight: 1.55
1347
+ };
1348
+ const settingsSubheadingStyle = {
1349
+ margin: "2px 0 8px",
1350
+ color: "var(--dsw-alias-label-secondary, #aab2c5)",
1225
1351
  fontSize: 12,
1226
- marginBottom: 4,
1227
- color: "var(--dsh-color-muted, #8b90a0)"
1352
+ fontWeight: 600
1228
1353
  };
1229
1354
  const settingsInputStyle = {
1230
1355
  width: "100%",
1356
+ minHeight: 32,
1231
1357
  boxSizing: "border-box",
1232
- padding: "4px 6px",
1233
- borderRadius: 4,
1234
- border: "1px solid var(--dsh-color-border, #3a3f4b)",
1235
- background: "transparent",
1358
+ padding: "5px 8px",
1359
+ borderRadius: 6,
1360
+ border: "1px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
1361
+ background: "var(--dsw-alias-bg-base, transparent)",
1236
1362
  color: "inherit",
1237
1363
  fontSize: 13,
1238
1364
  caretColor: "inherit"
1239
1365
  };
1366
+ const settingsUsageStyle = {
1367
+ display: "grid",
1368
+ gap: 4,
1369
+ alignContent: "start",
1370
+ padding: "12px 14px",
1371
+ border: "0.5px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
1372
+ borderRadius: 10,
1373
+ background: "var(--dsw-alias-bg-layer-1, transparent)"
1374
+ };
1375
+ const settingsUsageValueStyle = (exhausted) => ({
1376
+ color: exhausted ? "var(--dsw-alias-state-error-primary, #e11d48)" : "var(--dsw-alias-label-primary, #e6e9f0)",
1377
+ fontSize: 22,
1378
+ fontWeight: 650,
1379
+ lineHeight: 1.25
1380
+ });
1240
1381
  const advisorCardStyle = {
1241
- border: "1px solid var(--dsh-color-border, #3a3f4b)",
1242
- borderRadius: 6,
1243
- padding: 8,
1244
- marginBottom: 8,
1245
1382
  display: "grid",
1246
- gap: 6
1383
+ gap: 8,
1384
+ marginBottom: 12,
1385
+ padding: 12,
1386
+ border: "0.5px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
1387
+ borderRadius: 12,
1388
+ background: "var(--dsw-alias-bg-layer-1, transparent)"
1247
1389
  };
1248
1390
  const AVATAR_OPTIONS = [
1249
1391
  "🧠",
@@ -1289,6 +1431,7 @@ function AdvisorGroupSettingsTab() {
1289
1431
  const [msg, setMsg] = (0, react.useState)("");
1290
1432
  const [error, setError] = (0, react.useState)("");
1291
1433
  const [open, setOpen] = (0, react.useState)(false);
1434
+ const [folded, setFolded] = (0, react.useState)(() => new Set(SETTINGS_FOLD_KEYS));
1292
1435
  const [saving, setSaving] = (0, react.useState)(false);
1293
1436
  const [dailyGuard, setDailyGuard] = (0, react.useState)(null);
1294
1437
  const savedJsonRef = (0, react.useRef)("");
@@ -1312,7 +1455,10 @@ function AdvisorGroupSettingsTab() {
1312
1455
  reload();
1313
1456
  }, []);
1314
1457
  const dirty = config !== null && savedJsonRef.current !== "" && JSON.stringify(config) !== savedJsonRef.current;
1315
- 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 || "加载中…")));
1458
+ if (!config) return (0, react.createElement)("li", {
1459
+ className: "dsh-advisor-group-settings",
1460
+ style: settingsCardStyle(true)
1461
+ }, (0, react.createElement)("style", null, SETTINGS_ACCESSIBILITY_CSS), settingsCardHeader(true, false, () => setOpen((value) => !value)), (0, react.createElement)("div", { style: settingsCardBodyStyle }, (0, react.createElement)("p", { style: settingsCardLoadingStyle }, error || "加载中…")));
1316
1462
  const updateAdvisor = (index, patch) => {
1317
1463
  const advisors = config.advisors.map((advisor, i) => i === index ? {
1318
1464
  ...advisor,
@@ -1450,887 +1596,1364 @@ function AdvisorGroupSettingsTab() {
1450
1596
  }
1451
1597
  };
1452
1598
  const label = (text) => (0, react.createElement)("label", { style: settingsLabelStyle }, text);
1453
- 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: {
1454
- display: "flex",
1455
- alignItems: "center",
1456
- gap: 8
1457
- } }, (0, react.createElement)("input", {
1458
- type: "checkbox",
1459
- checked: config.enabled,
1460
- onChange: (e) => setConfig({
1461
- ...config,
1462
- enabled: e.target.checked
1463
- })
1464
- }), label("启用顾问群")), (0, react.createElement)("div", { style: {
1465
- display: "flex",
1466
- alignItems: "center",
1467
- gap: 8,
1468
- marginTop: 4
1469
- } }, (0, react.createElement)("input", {
1470
- type: "checkbox",
1471
- checked: config.quota.enabled,
1472
- onChange: (e) => setConfig({
1473
- ...config,
1474
- quota: {
1475
- ...config.quota,
1476
- enabled: e.target.checked
1477
- }
1478
- })
1479
- }), label("启用每日咨询上限(成本安全阀)")), (0, react.createElement)("div", { style: { marginTop: 6 } }, label(`每日上限次数(${config.quota.enabled ? "生效中" : "已关闭,不拦截"})`), (0, react.createElement)("input", {
1480
- type: "number",
1481
- min: 1,
1482
- max: 1e5,
1483
- value: String(config.quota.maxPerDay),
1484
- disabled: !config.quota.enabled,
1485
- onChange: (e) => setConfig({
1486
- ...config,
1487
- quota: {
1488
- ...config.quota,
1489
- maxPerDay: Math.max(1, Number(e.target.value))
1490
- }
1491
- }),
1492
- style: {
1493
- ...settingsInputStyle,
1494
- width: 120,
1495
- marginTop: 4
1496
- }
1497
- }))), (0, react.createElement)("div", { style: settingsSectionStyle }, dailyGuard ? (0, react.createElement)("div", { style: {
1498
- fontSize: 12,
1499
- color: !dailyGuard.enabled || dailyGuard.remaining > 0 ? "var(--dsh-color-muted, #8b90a0)" : "#e11d48"
1500
- } }, 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)("strong", null, "全局蜂群"), (0, react.createElement)("div", { style: {
1501
- fontSize: 12,
1502
- color: "var(--dsh-color-muted, #8b90a0)",
1503
- margin: "4px 0 8px"
1504
- } }, "跨会话提问的开关与默认应答方式。"), (0, react.createElement)("div", { style: {
1505
- display: "flex",
1506
- alignItems: "center",
1507
- gap: 8
1508
- } }, (0, react.createElement)("input", {
1509
- type: "checkbox",
1510
- checked: config.swarm.enabled,
1511
- onChange: (e) => setConfig({
1512
- ...config,
1513
- swarm: {
1514
- ...config.swarm,
1515
- enabled: e.target.checked
1516
- }
1517
- })
1518
- }), label("总开关")), (0, react.createElement)("div", { style: {
1519
- fontSize: 12,
1520
- color: "var(--dsh-color-muted, #8b90a0)",
1521
- margin: "2px 0 8px"
1522
- } }, "关闭后本插件不再跨会话提问,三个工具会明确拒绝"), (0, react.createElement)("div", { style: {
1523
- display: "grid",
1524
- gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
1525
- gap: 8
1526
- } }, (0, react.createElement)("div", null, label("默认应答方式"), (0, react.createElement)("select", {
1527
- value: config.swarm.defaultMode,
1528
- onChange: (e) => setConfig({
1529
- ...config,
1530
- swarm: {
1531
- ...config.swarm,
1532
- defaultMode: e.target.value === "detach" ? "detach" : "wait"
1533
- }
1534
- }),
1535
- style: settingsInputStyle
1536
- }, (0, react.createElement)("option", { value: "wait" }, "同步等待"), (0, react.createElement)("option", { value: "detach" }, "异步投递"))), (0, react.createElement)("div", null, label("默认等待上限(分钟)"), (0, react.createElement)("input", {
1537
- type: "number",
1538
- min: 1,
1539
- max: 10,
1540
- value: String(Math.round(config.swarm.defaultWaitMs / 6e4)),
1541
- onChange: (e) => setConfig({
1542
- ...config,
1543
- swarm: {
1544
- ...config.swarm,
1545
- defaultWaitMs: Math.min(10, Math.max(1, Number(e.target.value) || 1)) * 6e4
1546
- }
1547
- }),
1548
- style: settingsInputStyle
1549
- }), (0, react.createElement)("div", { style: {
1550
- fontSize: 12,
1551
- color: "var(--dsh-color-muted, #8b90a0)",
1552
- marginTop: 4
1553
- } }, "可填 1–10 分钟")), (0, react.createElement)("div", null, label("异步结果保留(小时)"), (0, react.createElement)("input", {
1554
- type: "number",
1555
- min: 1,
1556
- max: 720,
1557
- value: String(config.swarm.resultTtlHours),
1558
- onChange: (e) => setConfig({
1559
- ...config,
1560
- swarm: {
1561
- ...config.swarm,
1562
- resultTtlHours: Math.min(720, Math.max(1, Number(e.target.value) || 1))
1563
- }
1564
- }),
1565
- style: settingsInputStyle
1566
- }))), (0, react.createElement)("div", { style: {
1567
- display: "flex",
1568
- alignItems: "center",
1569
- gap: 8,
1570
- marginTop: 8
1571
- } }, (0, react.createElement)("input", {
1572
- type: "checkbox",
1573
- checked: config.swarm.rosterIncludeSubagents,
1574
- onChange: (e) => setConfig({
1575
- ...config,
1576
- swarm: {
1577
- ...config.swarm,
1578
- rosterIncludeSubagents: e.target.checked
1579
- }
1580
- })
1581
- }), label("名录列出子代理会话")), (0, react.createElement)("div", { style: {
1582
- display: "flex",
1583
- alignItems: "center",
1584
- gap: 8,
1585
- marginTop: 8
1586
- } }, (0, react.createElement)("input", {
1587
- type: "checkbox",
1588
- checked: config.swarm.routingEnabled !== false,
1589
- onChange: (e) => setConfig({
1590
- ...config,
1591
- swarm: {
1592
- ...config.swarm,
1593
- routingEnabled: e.target.checked
1594
- }
1595
- })
1596
- }), label("按问题找会话")), (0, react.createElement)("div", { style: {
1597
- fontSize: 12,
1598
- color: "var(--dsh-color-muted, #8b90a0)",
1599
- margin: "2px 0 8px"
1600
- } }, "没有点名会话时,先按问题关键词列出候选,交给用户挑一个,不自动投递"), (0, react.createElement)("div", { style: {
1601
- display: "flex",
1602
- alignItems: "center",
1603
- gap: 8
1604
- } }, label("候选数"), (0, react.createElement)("input", {
1605
- type: "number",
1606
- min: 1,
1607
- max: 8,
1608
- value: String(config.swarm.routingTopN ?? 3),
1609
- disabled: config.swarm.routingEnabled === false,
1610
- onChange: (e) => setConfig({
1611
- ...config,
1612
- swarm: {
1613
- ...config.swarm,
1614
- routingTopN: Math.min(8, Math.max(1, Number(e.target.value) || 3))
1615
- }
1616
- }),
1617
- style: {
1618
- ...settingsInputStyle,
1619
- width: 120
1620
- }
1621
- })), (0, react.createElement)("div", { style: {
1622
- display: "flex",
1623
- alignItems: "center",
1624
- gap: 8,
1625
- marginTop: 8
1626
- } }, label("路由方式"), (0, react.createElement)("select", {
1627
- value: config.swarm.routingEngine === "jev" ? "jev" : "local",
1628
- disabled: config.swarm.routingEnabled === false,
1629
- onChange: (e) => setConfig({
1630
- ...config,
1631
- swarm: {
1632
- ...config.swarm,
1633
- routingEngine: e.target.value === "jev" ? "jev" : "local"
1634
- }
1635
- }),
1636
- style: settingsInputStyle
1637
- }, (0, react.createElement)("option", { value: "local" }, "本地计算"), (0, react.createElement)("option", { value: "jev" }, "Jev 判断"))), (0, react.createElement)("div", { style: {
1638
- fontSize: 12,
1639
- color: "var(--dsh-color-muted, #8b90a0)",
1640
- margin: "2px 0 8px"
1641
- } }, "Jev 判断需要单独配置在「触发判定」里的 Jev 凭据;任何失败都会静默回退到本地排序"), (0, react.createElement)("div", { style: {
1642
- display: "flex",
1643
- alignItems: "center",
1644
- gap: 8,
1645
- marginTop: 8
1646
- } }, label("是否该问别的会话"), (0, react.createElement)("select", {
1647
- value: config.swarm.routingAsk === "off" ? "off" : config.swarm.routingAsk === "always" ? "always" : "gated",
1648
- onChange: (e) => setConfig({
1649
- ...config,
1650
- swarm: {
1651
- ...config.swarm,
1652
- routingAsk: e.target.value === "off" ? "off" : e.target.value === "always" ? "always" : "gated"
1653
- }
1654
- }),
1655
- style: settingsInputStyle
1656
- }, (0, react.createElement)("option", { value: "off" }, "关"), (0, react.createElement)("option", { value: "gated" }, "本地预过滤 + Jev(默认)"), (0, react.createElement)("option", { value: "always" }, "每轮都问"))), (0, react.createElement)("div", { style: {
1599
+ const toggleFold = (key) => {
1600
+ setFolded((previous) => {
1601
+ const next = new Set(previous);
1602
+ if (next.has(key)) next.delete(key);
1603
+ else next.add(key);
1604
+ return next;
1605
+ });
1606
+ };
1607
+ /** One save row per section. It still saves the whole config. */
1608
+ const sectionActions = () => (0, react.createElement)("div", { style: settingsSectionActionsStyle }, (0, react.createElement)("span", { style: {
1657
1609
  fontSize: 12,
1658
- color: "var(--dsh-color-muted, #8b90a0)",
1659
- margin: "2px 0 8px"
1660
- } }, "中间档只在消息看起来涉及别的会话时才问 Jev,几乎不花钱;最后一档每轮都会多一次 Jev 调用"), (0, react.createElement)("div", { style: { marginTop: 8 } }, label("不参与路由的会话"), (0, react.createElement)("textarea", {
1661
- "aria-label": "routingExclude",
1662
- placeholder: "每行一个:会话标题关键词或完整 sessionId",
1663
- rows: 3,
1664
- value: (config.swarm.routingExclude ?? []).join("\n"),
1665
- onChange: (e) => setConfig({
1666
- ...config,
1667
- swarm: {
1668
- ...config.swarm,
1669
- routingExclude: e.target.value.split("\n").map((line) => line.trim()).filter((line) => line.length > 0)
1670
- }
1671
- }),
1610
+ color: dirty ? "var(--dsw-alias-state-warning-primary, #b45309)" : "var(--dsh-color-muted, #8b90a0)"
1611
+ } }, dirty ? "有未保存的更改" : "已保存"), (0, react.createElement)("button", {
1612
+ type: "button",
1613
+ disabled: !dirty || saving,
1614
+ onClick: () => void save(),
1615
+ title: "保存整页设置,包括其他板块的未保存修改",
1672
1616
  style: {
1673
- ...settingsInputStyle,
1674
- marginTop: 4,
1675
- resize: "vertical",
1676
- width: "100%"
1617
+ ...settingsFooterButtonStyle,
1618
+ background: "var(--dsw-alias-label-primary, #e6e9f0)",
1619
+ color: "var(--dsw-alias-bg-layer-3, #151821)"
1677
1620
  }
1678
- }), (0, react.createElement)("div", { style: {
1679
- fontSize: 12,
1680
- color: "var(--dsh-color-muted, #8b90a0)",
1681
- marginTop: 4
1682
- } }, "填会话标题里的关键词或完整 sessionId;这些会话不会出现在路由候选和会话名录里,被点名时也会拒绝投递"))), (0, react.createElement)("div", { style: settingsSectionStyle }, (0, react.createElement)("div", { style: {
1683
- display: "grid",
1684
- gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
1685
- gap: 8
1686
- } }, (0, react.createElement)("div", null, label("讨论轮数上限"), (0, react.createElement)("input", {
1687
- type: "number",
1688
- min: 0,
1689
- value: String(config.discussion.maxRounds),
1690
- onChange: (e) => setConfig({
1691
- ...config,
1692
- discussion: {
1693
- ...config.discussion,
1694
- maxRounds: Number(e.target.value)
1695
- }
1696
- }),
1697
- style: settingsInputStyle
1698
- })), (0, react.createElement)("div", null, label("单次最多顾问数"), (0, react.createElement)("input", {
1699
- type: "number",
1700
- min: 1,
1701
- value: String(config.discussion.maxAdvisorsPerCall ?? 3),
1702
- onChange: (e) => setConfig({
1703
- ...config,
1704
- discussion: {
1705
- ...config.discussion,
1706
- maxAdvisorsPerCall: Math.max(1, Number(e.target.value))
1707
- }
1708
- }),
1709
- style: settingsInputStyle
1710
- })), (0, react.createElement)("div", null, label("单顾问超时(毫秒)"), (0, react.createElement)("input", {
1711
- type: "number",
1712
- min: 1e3,
1713
- max: 6e5,
1714
- step: 1e3,
1715
- value: String(config.discussion.advisorTimeoutMs ?? 6e5),
1716
- onChange: (e) => setConfig({
1717
- ...config,
1718
- discussion: {
1719
- ...config.discussion,
1720
- advisorTimeoutMs: Math.max(1e3, Number(e.target.value))
1721
- }
1722
- }),
1723
- style: settingsInputStyle
1724
- })), (0, react.createElement)("div", null, label("驱动模型生成超时(毫秒)"), (0, react.createElement)("input", {
1725
- type: "number",
1726
- min: 1e3,
1727
- max: 12e5,
1728
- step: 1e3,
1729
- value: String(config.discussion.driverTimeoutMs ?? 6e5),
1730
- onChange: (e) => setConfig({
1731
- ...config,
1732
- discussion: {
1733
- ...config.discussion,
1734
- driverTimeoutMs: Math.max(1e3, Number(e.target.value))
1735
- }
1736
- }),
1737
- style: settingsInputStyle
1738
- }))), (0, react.createElement)("div", { style: { marginTop: 8 } }, label("置信度阈值(0-1,低于此值升级顾问群)"), (0, react.createElement)("input", {
1739
- type: "number",
1740
- min: 0,
1741
- max: 1,
1742
- step: .05,
1743
- value: String(config.trigger.confidenceThreshold ?? .6),
1744
- onChange: (e) => setConfig({
1745
- ...config,
1746
- trigger: {
1747
- ...config.trigger,
1748
- confidenceThreshold: Number(e.target.value)
1749
- }
1750
- }),
1751
- style: settingsInputStyle
1752
- }))), (0, react.createElement)("div", { style: settingsSectionStyle }, (0, react.createElement)("strong", null, "Jev 前置分类"), (0, react.createElement)("div", { style: {
1753
- fontSize: 12,
1754
- color: "var(--dsh-color-muted, #8b90a0)",
1755
- margin: "4px 0 8px"
1756
- } }, "启用后由 Jev 判断是否需要顾问群;未配置或调用失败时自动使用本地分类器。"), (0, react.createElement)("div", { style: {
1757
- display: "flex",
1758
- alignItems: "center",
1759
- gap: 8,
1760
- marginBottom: 8
1761
- } }, (0, react.createElement)("input", {
1762
- type: "checkbox",
1763
- checked: config.trigger.jev?.enabled ?? false,
1764
- onChange: (e) => setConfig({
1765
- ...config,
1766
- trigger: {
1767
- ...config.trigger,
1768
- jev: {
1769
- ...config.trigger.jev ?? defaultJevConfig(),
1621
+ }, saving ? "保存中…" : "保存整页设置"));
1622
+ /** One collapsible settings block; native buttons provide Enter/Space support. */
1623
+ const fold = (key, title, description, ...children) => {
1624
+ const contentId = `advisor-group-settings-${key}`;
1625
+ const headerId = `${contentId}-header`;
1626
+ return (0, react.createElement)("section", {
1627
+ key,
1628
+ style: settingsFoldStyle
1629
+ }, (0, react.createElement)("button", {
1630
+ id: headerId,
1631
+ className: "dsh-advisor-group-settings__fold-header",
1632
+ type: "button",
1633
+ "aria-expanded": !folded.has(key),
1634
+ "aria-controls": contentId,
1635
+ onClick: () => toggleFold(key),
1636
+ style: settingsFoldHeaderStyle
1637
+ }, (0, react.createElement)("span", { style: settingsFoldHeaderTextStyle }, (0, react.createElement)("span", { style: settingsFoldTitleStyle }, title), (0, react.createElement)("span", { style: settingsFoldDescriptionStyle }, description)), (0, react.createElement)("span", {
1638
+ style: settingsFoldCaretStyle,
1639
+ "aria-hidden": true
1640
+ }, folded.has(key) ? "▸" : "▾")), folded.has(key) ? null : (0, react.createElement)("div", {
1641
+ id: contentId,
1642
+ role: "region",
1643
+ "aria-labelledby": headerId,
1644
+ style: settingsFoldBodyStyle
1645
+ }, children, sectionActions()));
1646
+ };
1647
+ const sections = {
1648
+ basics: fold("basics", "启用与每日配额", "开启外部模型会诊,并控制每天的新咨询次数。", (0, react.createElement)("div", { style: settingsGridStyle }, (0, react.createElement)("div", null, (0, react.createElement)("div", { style: {
1649
+ display: "flex",
1650
+ alignItems: "center",
1651
+ gap: 8
1652
+ } }, (0, react.createElement)("input", {
1653
+ type: "checkbox",
1654
+ checked: config.enabled,
1655
+ onChange: (e) => setConfig({
1656
+ ...config,
1657
+ enabled: e.target.checked
1658
+ })
1659
+ }), label("启用顾问群")), (0, react.createElement)("div", { style: {
1660
+ display: "flex",
1661
+ alignItems: "center",
1662
+ gap: 8,
1663
+ marginTop: 10
1664
+ } }, (0, react.createElement)("input", {
1665
+ type: "checkbox",
1666
+ checked: config.quota.enabled,
1667
+ onChange: (e) => setConfig({
1668
+ ...config,
1669
+ quota: {
1670
+ ...config.quota,
1770
1671
  enabled: e.target.checked
1771
1672
  }
1772
- }
1773
- })
1774
- }), label("启用 Jev")), (0, react.createElement)("div", { style: {
1775
- display: "grid",
1776
- gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
1777
- gap: 8
1778
- } }, (0, react.createElement)("div", null, label("Jev 渠道"), (0, react.createElement)("select", {
1779
- value: config.trigger.jev?.provider ?? "typesafe",
1780
- onChange: (e) => {
1781
- const current = config.trigger.jev ?? defaultJevConfig();
1782
- const previousProvider = current.provider ?? "typesafe";
1783
- const currentURL = current.baseURL ?? "";
1784
- const usesDefault = !currentURL || currentURL === JEV_DEFAULT_ENDPOINTS[previousProvider];
1785
- setConfig({
1673
+ })
1674
+ }), label("启用每日咨询上限")), (0, react.createElement)("div", { style: { marginTop: 12 } }, label("每日新会诊上限(次)"), (0, react.createElement)("input", {
1675
+ type: "number",
1676
+ min: 1,
1677
+ max: 1e5,
1678
+ value: String(config.quota.maxPerDay),
1679
+ disabled: !config.quota.enabled,
1680
+ onChange: (e) => setConfig({
1786
1681
  ...config,
1787
- trigger: {
1788
- ...config.trigger,
1789
- jev: {
1790
- ...current,
1791
- provider: e.target.value,
1792
- ...usesDefault ? { baseURL: JEV_DEFAULT_ENDPOINTS[e.target.value] } : {}
1793
- }
1794
- }
1795
- });
1796
- },
1797
- style: settingsInputStyle
1798
- }, (0, react.createElement)("option", { value: "typesafe" }, "TypeSafe 官方 API"), (0, react.createElement)("option", { value: "openrouter" }, "OpenRouter Decisions API"))), (0, react.createElement)("div", null, label("Jev 模型"), (0, react.createElement)("input", {
1799
- value: config.trigger.jev?.model ?? "jev-latest",
1800
- onChange: (e) => setConfig({
1801
- ...config,
1802
- trigger: {
1803
- ...config.trigger,
1804
- jev: {
1805
- ...config.trigger.jev ?? defaultJevConfig(),
1806
- model: e.target.value
1682
+ quota: {
1683
+ ...config.quota,
1684
+ maxPerDay: Math.max(1, Number(e.target.value))
1807
1685
  }
1686
+ }),
1687
+ style: {
1688
+ ...settingsInputStyle,
1689
+ maxWidth: 160
1808
1690
  }
1809
- }),
1810
- style: settingsInputStyle
1811
- })), (0, react.createElement)("div", null, label("判断阈值(0-1)"), (0, react.createElement)("input", {
1812
- type: "number",
1813
- min: 0,
1814
- max: 1,
1815
- step: .05,
1816
- value: String(config.trigger.jev?.confidenceThreshold ?? .6),
1817
- onChange: (e) => setConfig({
1818
- ...config,
1819
- trigger: {
1820
- ...config.trigger,
1821
- jev: {
1822
- ...config.trigger.jev ?? defaultJevConfig(),
1823
- confidenceThreshold: Number(e.target.value)
1691
+ }), (0, react.createElement)("div", { style: {
1692
+ ...settingsHelpStyle,
1693
+ marginTop: 6
1694
+ } }, "续跑、后续追问和跨会话提问不占这项配额。"))), (0, react.createElement)("div", { style: settingsUsageStyle }, (0, react.createElement)("strong", { style: {
1695
+ ...settingsSubheadingStyle,
1696
+ margin: 0
1697
+ } }, "今日用量"), (0, react.createElement)("div", { style: settingsUsageValueStyle(dailyGuard?.enabled === true && dailyGuard.remaining === 0) }, dailyGuard ? dailyGuard.enabled ? `${dailyGuard.remaining} / ${dailyGuard.limit}` : `${dailyGuard.used}` : "—"), (0, react.createElement)("div", { style: settingsHelpStyle }, dailyGuard ? dailyGuard.enabled ? dailyGuard.remaining > 0 ? "今日剩余新会诊次数 · UTC 日切" : `今日已用 ${dailyGuard.used} 次,UTC 日切后恢复。` : "今日已发起的新会诊次数 · 上限已关闭" : "正在读取今日用量…")))),
1698
+ swarm: fold("swarm", "全局蜂群(跨会话)", "让已有会话互相提问;与外部模型会诊分开配置。", (0, react.createElement)("div", { style: {
1699
+ display: "flex",
1700
+ alignItems: "center",
1701
+ gap: 8
1702
+ } }, (0, react.createElement)("input", {
1703
+ type: "checkbox",
1704
+ checked: config.swarm.enabled,
1705
+ onChange: (e) => setConfig({
1706
+ ...config,
1707
+ swarm: {
1708
+ ...config.swarm,
1709
+ enabled: e.target.checked
1824
1710
  }
1825
- }
1826
- }),
1827
- style: settingsInputStyle
1828
- })), (0, react.createElement)("div", null, label("高风险阈值(0-1,空=跟随判断阈值)"), (0, react.createElement)("input", {
1829
- type: "number",
1830
- min: 0,
1831
- max: 1,
1832
- step: .05,
1833
- placeholder: String(config.trigger.jev?.confidenceThreshold ?? .6),
1834
- value: config.trigger.jev?.highRiskThreshold !== void 0 ? String(config.trigger.jev.highRiskThreshold) : "",
1835
- onChange: (e) => setConfig({
1836
- ...config,
1837
- trigger: {
1838
- ...config.trigger,
1839
- jev: {
1840
- ...config.trigger.jev ?? defaultJevConfig(),
1841
- highRiskThreshold: e.target.value === "" ? void 0 : Number(e.target.value)
1711
+ })
1712
+ }), label("启用跨会话提问")), (0, react.createElement)("div", { style: {
1713
+ ...settingsHelpStyle,
1714
+ margin: "2px 0 8px"
1715
+ } }, "关闭后,四个跨会话工具会明确拒绝调用。"), (0, react.createElement)("div", { style: settingsGridStyle }, (0, react.createElement)("div", null, label("单人提问的默认等待方式"), (0, react.createElement)("select", {
1716
+ value: config.swarm.defaultMode,
1717
+ onChange: (e) => setConfig({
1718
+ ...config,
1719
+ swarm: {
1720
+ ...config.swarm,
1721
+ defaultMode: e.target.value === "detach" ? "detach" : "wait"
1842
1722
  }
1843
- }
1844
- }),
1845
- style: settingsInputStyle
1846
- })), (0, react.createElement)("div", null, label("请求超时(毫秒)"), (0, react.createElement)("input", {
1847
- type: "number",
1848
- min: 1e3,
1849
- max: 6e4,
1850
- step: 1e3,
1851
- value: String(config.trigger.jev?.timeoutMs ?? 1e4),
1852
- onChange: (e) => setConfig({
1853
- ...config,
1854
- trigger: {
1855
- ...config.trigger,
1856
- jev: {
1857
- ...config.trigger.jev ?? defaultJevConfig(),
1858
- timeoutMs: Math.max(1e3, Number(e.target.value))
1723
+ }),
1724
+ style: settingsInputStyle
1725
+ }, (0, react.createElement)("option", { value: "wait" }, "同步等待"), (0, react.createElement)("option", { value: "detach" }, "异步投递"))), (0, react.createElement)("div", null, label("默认等待上限(分钟)"), (0, react.createElement)("input", {
1726
+ type: "number",
1727
+ min: 1,
1728
+ max: 10,
1729
+ value: String(Math.round(config.swarm.defaultWaitMs / 6e4)),
1730
+ onChange: (e) => setConfig({
1731
+ ...config,
1732
+ swarm: {
1733
+ ...config.swarm,
1734
+ defaultWaitMs: Math.min(10, Math.max(1, Number(e.target.value) || 1)) * 6e4
1859
1735
  }
1860
- }
1861
- }),
1862
- style: settingsInputStyle
1863
- }))), (0, react.createElement)("div", { style: { marginTop: 8 } }, label("API 地址(留空使用渠道默认地址)"), (0, react.createElement)("input", {
1864
- value: config.trigger.jev?.baseURL || JEV_DEFAULT_ENDPOINTS[config.trigger.jev?.provider ?? "typesafe"],
1865
- placeholder: JEV_DEFAULT_ENDPOINTS[config.trigger.jev?.provider ?? "typesafe"],
1866
- onChange: (e) => setConfig({
1867
- ...config,
1868
- trigger: {
1869
- ...config.trigger,
1870
- jev: {
1871
- ...config.trigger.jev ?? defaultJevConfig(),
1872
- baseURL: e.target.value
1736
+ }),
1737
+ style: settingsInputStyle
1738
+ }), (0, react.createElement)("div", { style: {
1739
+ ...settingsHelpStyle,
1740
+ marginTop: 4
1741
+ } }, "1–10 分钟;超时不打断目标会话。")), (0, react.createElement)("div", null, label("异步结果保留(小时)"), (0, react.createElement)("input", {
1742
+ type: "number",
1743
+ min: 1,
1744
+ max: 720,
1745
+ value: String(config.swarm.resultTtlHours),
1746
+ onChange: (e) => setConfig({
1747
+ ...config,
1748
+ swarm: {
1749
+ ...config.swarm,
1750
+ resultTtlHours: Math.min(720, Math.max(1, Number(e.target.value) || 1))
1873
1751
  }
1874
- }
1875
- }),
1876
- style: settingsInputStyle
1877
- })), (0, react.createElement)("div", { style: {
1878
- display: "grid",
1879
- gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
1880
- gap: 8,
1881
- marginTop: 8
1882
- } }, (0, react.createElement)("div", null, label("Jev API Key"), (0, react.createElement)("input", {
1883
- type: "password",
1884
- placeholder: config.trigger.jev?.apiKeyMeta?.configured ? `已配置 ••••${config.trigger.jev.apiKeyMeta.last4 ?? ""}` : "输入 API Key",
1885
- value: config.trigger.jev?.apiKey ?? "",
1886
- onChange: (e) => setConfig({
1887
- ...config,
1888
- trigger: {
1889
- ...config.trigger,
1890
- jev: {
1891
- ...config.trigger.jev ?? defaultJevConfig(),
1892
- apiKey: e.target.value
1752
+ }),
1753
+ style: settingsInputStyle
1754
+ }))), (0, react.createElement)("div", { style: {
1755
+ display: "flex",
1756
+ alignItems: "center",
1757
+ gap: 8,
1758
+ marginTop: 8
1759
+ } }, (0, react.createElement)("input", {
1760
+ type: "checkbox",
1761
+ checked: config.swarm.rosterIncludeSubagents,
1762
+ onChange: (e) => setConfig({
1763
+ ...config,
1764
+ swarm: {
1765
+ ...config.swarm,
1766
+ rosterIncludeSubagents: e.target.checked
1893
1767
  }
1894
- }
1895
- }),
1896
- style: settingsInputStyle
1897
- })), (0, react.createElement)("div", null, label("API Key 环境变量名(可选)"), (0, react.createElement)("input", {
1898
- placeholder: "TYPESAFE_API_KEY 或 OPENROUTER_API_KEY",
1899
- value: config.trigger.jev?.apiKeyEnv ?? "",
1900
- onChange: (e) => setConfig({
1901
- ...config,
1902
- trigger: {
1903
- ...config.trigger,
1904
- jev: {
1905
- ...config.trigger.jev ?? defaultJevConfig(),
1906
- apiKeyEnv: e.target.value
1768
+ })
1769
+ }), label("在会话名录中显示子代理")), (0, react.createElement)("div", { style: {
1770
+ display: "flex",
1771
+ alignItems: "center",
1772
+ gap: 8,
1773
+ marginTop: 8
1774
+ } }, (0, react.createElement)("input", {
1775
+ type: "checkbox",
1776
+ checked: config.swarm.routingEnabled !== false,
1777
+ onChange: (e) => setConfig({
1778
+ ...config,
1779
+ swarm: {
1780
+ ...config.swarm,
1781
+ routingEnabled: e.target.checked
1907
1782
  }
1908
- }
1909
- }),
1910
- style: settingsInputStyle
1911
- }))), (0, react.createElement)("div", { style: {
1912
- display: "flex",
1913
- alignItems: "center",
1914
- gap: 8,
1915
- marginTop: 8
1916
- } }, (0, react.createElement)("input", {
1917
- type: "checkbox",
1918
- checked: config.trigger.jev?.useEnglishState ?? false,
1919
- onChange: (e) => setConfig({
1920
- ...config,
1921
- trigger: {
1922
- ...config.trigger,
1923
- jev: {
1924
- ...config.trigger.jev ?? defaultJevConfig(),
1925
- useEnglishState: e.target.checked
1783
+ })
1784
+ }), label("未点名时推荐候选会话")), (0, react.createElement)("div", { style: {
1785
+ ...settingsHelpStyle,
1786
+ margin: "2px 0 8px"
1787
+ } }, "只列出候选,不自动投递。"), (0, react.createElement)("div", { style: {
1788
+ display: "flex",
1789
+ alignItems: "center",
1790
+ gap: 8
1791
+ } }, label("候选数"), (0, react.createElement)("input", {
1792
+ type: "number",
1793
+ min: 1,
1794
+ max: 8,
1795
+ value: String(config.swarm.routingTopN ?? 3),
1796
+ disabled: config.swarm.routingEnabled === false,
1797
+ onChange: (e) => setConfig({
1798
+ ...config,
1799
+ swarm: {
1800
+ ...config.swarm,
1801
+ routingTopN: Math.min(8, Math.max(1, Number(e.target.value) || 3))
1926
1802
  }
1803
+ }),
1804
+ style: {
1805
+ ...settingsInputStyle,
1806
+ width: 120
1927
1807
  }
1928
- })
1929
- }), label("Jev 英文判定(questionEn 概要)")), (0, react.createElement)("div", { style: {
1930
- fontSize: 12,
1931
- color: "var(--dsh-color-muted, #8b90a0)",
1932
- margin: "4px 0 0"
1933
- } }, "开启后,主模型提供的英文概要(questionEn)会作为 Jev 的判定输入;展示问句、会话记录与重复检测仍用中文。默认关闭。")), (0, react.createElement)("div", { style: settingsSectionStyle }, (0, react.createElement)("div", { style: {
1934
- display: "flex",
1935
- alignItems: "center",
1936
- gap: 8
1937
- } }, (0, react.createElement)("input", {
1938
- type: "checkbox",
1939
- checked: config.discussion.autoDeepen,
1940
- onChange: (e) => setConfig({
1941
- ...config,
1942
- discussion: {
1943
- ...config.discussion,
1944
- autoDeepen: e.target.checked
1808
+ })), (0, react.createElement)("div", { style: {
1809
+ display: "flex",
1810
+ alignItems: "center",
1811
+ gap: 8,
1812
+ marginTop: 8
1813
+ } }, label("候选排序方式"), (0, react.createElement)("select", {
1814
+ value: config.swarm.routingEngine === "jev" ? "jev" : "local",
1815
+ disabled: config.swarm.routingEnabled === false,
1816
+ onChange: (e) => setConfig({
1817
+ ...config,
1818
+ swarm: {
1819
+ ...config.swarm,
1820
+ routingEngine: e.target.value === "jev" ? "jev" : "local"
1821
+ }
1822
+ }),
1823
+ style: settingsInputStyle
1824
+ }, (0, react.createElement)("option", { value: "local" }, "本地计算"), (0, react.createElement)("option", { value: "jev" }, "Jev 判断"))), (0, react.createElement)("div", { style: {
1825
+ ...settingsHelpStyle,
1826
+ margin: "2px 0 8px"
1827
+ } }, "使用「触发与分类」中的 Jev 凭据;失败时回退到本地排序。"), (0, react.createElement)("div", { style: {
1828
+ display: "flex",
1829
+ alignItems: "center",
1830
+ gap: 8,
1831
+ marginTop: 8
1832
+ } }, label("跨会话提问建议"), (0, react.createElement)("select", {
1833
+ value: config.swarm.routingAsk === "off" ? "off" : config.swarm.routingAsk === "always" ? "always" : "gated",
1834
+ onChange: (e) => setConfig({
1835
+ ...config,
1836
+ swarm: {
1837
+ ...config.swarm,
1838
+ routingAsk: e.target.value === "off" ? "off" : e.target.value === "always" ? "always" : "gated"
1839
+ }
1840
+ }),
1841
+ style: settingsInputStyle
1842
+ }, (0, react.createElement)("option", { value: "off" }, "关"), (0, react.createElement)("option", { value: "gated" }, "本地预过滤 + Jev(默认)"), (0, react.createElement)("option", { value: "always" }, "每轮都问"))), (0, react.createElement)("div", { style: {
1843
+ ...settingsHelpStyle,
1844
+ margin: "2px 0 8px"
1845
+ } }, "只向主模型提供建议,不自动投递;“每轮都问”会增加 Jev 调用。"), (0, react.createElement)("div", { style: { marginTop: 8 } }, label("排除的会话"), (0, react.createElement)("textarea", {
1846
+ "aria-label": "routingExclude",
1847
+ placeholder: "每行一个:会话标题关键词或完整 sessionId",
1848
+ rows: 3,
1849
+ value: (config.swarm.routingExclude ?? []).join("\n"),
1850
+ onChange: (e) => setConfig({
1851
+ ...config,
1852
+ swarm: {
1853
+ ...config.swarm,
1854
+ routingExclude: e.target.value.split("\n").map((line) => line.trim()).filter((line) => line.length > 0)
1855
+ }
1856
+ }),
1857
+ style: {
1858
+ ...settingsInputStyle,
1859
+ marginTop: 4,
1860
+ resize: "vertical",
1861
+ width: "100%"
1945
1862
  }
1946
- })
1947
- }), label("自动多轮深挖(每轮后驱动模型生成深入追问,顾问按 A→B→C 顺序接力,直到轮数上限)")), (0, react.createElement)("div", { style: {
1948
- display: "flex",
1949
- alignItems: "center",
1950
- gap: 8,
1951
- marginTop: 4
1952
- } }, (0, react.createElement)("input", {
1953
- type: "checkbox",
1954
- checked: config.trigger.requireClassifier,
1955
- onChange: (e) => setConfig({
1956
- ...config,
1957
- trigger: {
1958
- ...config.trigger,
1959
- requireClassifier: e.target.checked
1863
+ }), (0, react.createElement)("div", { style: {
1864
+ ...settingsHelpStyle,
1865
+ marginTop: 4
1866
+ } }, "每行一个标题关键词或完整 sessionId;排除后不进入名录或候选,点名也会拒绝。"))),
1867
+ discussion: fold("discussion", "会诊行为", "设置讨论轮数、顾问人数、超时与默认工具范围。", (0, react.createElement)("div", { style: settingsGridStyle }, (0, react.createElement)("div", null, label("会诊轮数(1–5)"), (0, react.createElement)("input", {
1868
+ type: "number",
1869
+ min: 1,
1870
+ max: 5,
1871
+ value: String(config.discussion.maxRounds),
1872
+ onChange: (e) => setConfig({
1873
+ ...config,
1874
+ discussion: {
1875
+ ...config.discussion,
1876
+ maxRounds: Number(e.target.value)
1877
+ }
1878
+ }),
1879
+ style: settingsInputStyle
1880
+ })), (0, react.createElement)("div", null, label("单次最多顾问数(1–10)"), (0, react.createElement)("input", {
1881
+ type: "number",
1882
+ min: 1,
1883
+ max: 10,
1884
+ value: String(config.discussion.maxAdvisorsPerCall ?? 3),
1885
+ onChange: (e) => setConfig({
1886
+ ...config,
1887
+ discussion: {
1888
+ ...config.discussion,
1889
+ maxAdvisorsPerCall: Math.max(1, Number(e.target.value))
1890
+ }
1891
+ }),
1892
+ style: settingsInputStyle
1893
+ })), (0, react.createElement)("div", null, label("单顾问超时(毫秒)"), (0, react.createElement)("input", {
1894
+ type: "number",
1895
+ min: 1e3,
1896
+ max: 6e5,
1897
+ step: 1e3,
1898
+ value: String(config.discussion.advisorTimeoutMs ?? 6e5),
1899
+ onChange: (e) => setConfig({
1900
+ ...config,
1901
+ discussion: {
1902
+ ...config.discussion,
1903
+ advisorTimeoutMs: Math.max(1e3, Number(e.target.value))
1904
+ }
1905
+ }),
1906
+ style: settingsInputStyle
1907
+ })), (0, react.createElement)("div", null, label("驱动模型生成超时(毫秒)"), (0, react.createElement)("input", {
1908
+ type: "number",
1909
+ min: 1e3,
1910
+ max: 12e5,
1911
+ step: 1e3,
1912
+ value: String(config.discussion.driverTimeoutMs ?? 6e5),
1913
+ onChange: (e) => setConfig({
1914
+ ...config,
1915
+ discussion: {
1916
+ ...config.discussion,
1917
+ driverTimeoutMs: Math.max(1e3, Number(e.target.value))
1918
+ }
1919
+ }),
1920
+ style: settingsInputStyle
1921
+ }))), (0, react.createElement)("div", { style: { marginTop: 12 } }, label("默认顾问工具范围"), (0, react.createElement)("select", {
1922
+ value: config.discussion.advisorTools ?? "readonly",
1923
+ onChange: (e) => setConfig({
1924
+ ...config,
1925
+ discussion: {
1926
+ ...config.discussion,
1927
+ advisorTools: e.target.value
1928
+ }
1929
+ }),
1930
+ style: {
1931
+ ...settingsInputStyle,
1932
+ maxWidth: 320
1960
1933
  }
1961
- })
1962
- }), label("启用前置分类器")), (0, react.createElement)("div", { style: {
1963
- display: "flex",
1964
- alignItems: "center",
1965
- gap: 8,
1966
- marginTop: 4
1967
- } }, (0, react.createElement)("input", {
1968
- type: "checkbox",
1969
- checked: config.trigger.allowWebFallback,
1970
- onChange: (e) => setConfig({
1971
- ...config,
1972
- trigger: {
1973
- ...config.trigger,
1974
- allowWebFallback: e.target.checked
1934
+ }, (0, react.createElement)("option", { value: "readonly" }, "只读工具(推荐)"), (0, react.createElement)("option", { value: "all" }, "全部会话工具(含写入与命令,慎用)"), (0, react.createElement)("option", { value: "off" }, "关闭工具调用")), (0, react.createElement)("div", { style: {
1935
+ ...settingsHelpStyle,
1936
+ marginTop: 6
1937
+ } }, "可在顾问列表中单独覆盖;只有直连的 OpenAI / Anthropic 通道支持顾问工具调用。"))),
1938
+ trigger: fold("trigger", "触发与分类", "决定何时召集顾问群,以及是否使用 Jev 判断。", (0, react.createElement)("div", { style: { marginBottom: 12 } }, (0, react.createElement)("div", { style: settingsSubheadingStyle }, "主模型置信度"), (0, react.createElement)("input", {
1939
+ type: "number",
1940
+ min: 0,
1941
+ max: 1,
1942
+ step: .05,
1943
+ value: String(config.trigger.confidenceThreshold ?? .6),
1944
+ onChange: (e) => setConfig({
1945
+ ...config,
1946
+ trigger: {
1947
+ ...config.trigger,
1948
+ confidenceThreshold: Number(e.target.value)
1949
+ }
1950
+ }),
1951
+ style: {
1952
+ ...settingsInputStyle,
1953
+ maxWidth: 320
1975
1954
  }
1976
- })
1977
- }), label("分类器建议时允许返回联网搜索提示"))), (0, react.createElement)("div", { style: settingsSectionStyle }, (0, react.createElement)("div", { style: {
1978
- display: "flex",
1979
- justifyContent: "space-between",
1980
- alignItems: "center",
1981
- marginBottom: 8
1982
- } }, (0, react.createElement)("strong", null, `顾问列表(${config.advisors.length})`), (0, react.createElement)("button", {
1983
- type: "button",
1984
- onClick: addAdvisor,
1985
- style: { cursor: "pointer" }
1986
- }, "+ 添加顾问")), config.advisors.map((advisor, index) => (0, react.createElement)("div", {
1987
- key: advisor.id,
1988
- style: advisorCardStyle
1989
- }, (0, react.createElement)("div", { style: {
1990
- display: "flex",
1991
- justifyContent: "space-between",
1992
- gap: 8
1993
- } }, (0, react.createElement)("strong", null, advisor.name || advisor.id), (0, react.createElement)("button", {
1994
- type: "button",
1995
- onClick: () => removeAdvisor(index),
1996
- style: {
1997
- cursor: "pointer",
1998
- color: "#e11d48"
1999
- }
2000
- }, "删除")), label(`ID · ${advisor.id}`), (0, react.createElement)("input", {
2001
- value: advisor.name,
2002
- onChange: (e) => updateAdvisor(index, { name: e.target.value }),
2003
- style: settingsInputStyle
2004
- }), label("头像"), (0, react.createElement)("div", { style: {
2005
- display: "flex",
2006
- flexWrap: "wrap",
2007
- gap: 4,
2008
- marginTop: 2
2009
- } }, (0, react.createElement)("button", {
2010
- type: "button",
2011
- onClick: () => updateAdvisor(index, { avatar: void 0 }),
2012
- style: {
2013
- cursor: "pointer",
1955
+ }), (0, react.createElement)("div", { style: {
1956
+ ...settingsHelpStyle,
1957
+ marginTop: 6
1958
+ } }, "低于此值时升级会诊;@顾问群和同一问题重复三次会强制发起。")), (0, react.createElement)("div", { style: settingsSubheadingStyle }, "Jev 前置分类"), (0, react.createElement)("div", { style: {
1959
+ ...settingsHelpStyle,
1960
+ marginBottom: 10
1961
+ } }, "按问题判断是否召集顾问群;未配置或调用失败时使用本地分类器。"), (0, react.createElement)("div", { style: {
1962
+ display: "flex",
1963
+ alignItems: "center",
1964
+ gap: 8,
1965
+ marginBottom: 8
1966
+ } }, (0, react.createElement)("input", {
1967
+ type: "checkbox",
1968
+ checked: config.trigger.jev?.enabled ?? false,
1969
+ onChange: (e) => setConfig({
1970
+ ...config,
1971
+ trigger: {
1972
+ ...config.trigger,
1973
+ jev: {
1974
+ ...config.trigger.jev ?? defaultJevConfig(),
1975
+ enabled: e.target.checked
1976
+ }
1977
+ }
1978
+ })
1979
+ }), label("启用 Jev")), (0, react.createElement)("div", { style: settingsGridStyle }, (0, react.createElement)("div", null, label("Jev 渠道"), (0, react.createElement)("select", {
1980
+ value: config.trigger.jev?.provider ?? "typesafe",
1981
+ onChange: (e) => {
1982
+ const current = config.trigger.jev ?? defaultJevConfig();
1983
+ const previousProvider = current.provider ?? "typesafe";
1984
+ const currentURL = current.baseURL ?? "";
1985
+ const usesDefault = !currentURL || currentURL === JEV_DEFAULT_ENDPOINTS[previousProvider];
1986
+ setConfig({
1987
+ ...config,
1988
+ trigger: {
1989
+ ...config.trigger,
1990
+ jev: {
1991
+ ...current,
1992
+ provider: e.target.value,
1993
+ ...usesDefault ? { baseURL: JEV_DEFAULT_ENDPOINTS[e.target.value] } : {}
1994
+ }
1995
+ }
1996
+ });
1997
+ },
1998
+ style: settingsInputStyle
1999
+ }, (0, react.createElement)("option", { value: "typesafe" }, "TypeSafe 官方 API"), (0, react.createElement)("option", { value: "openrouter" }, "OpenRouter Decisions API"))), (0, react.createElement)("div", null, label("Jev 模型"), (0, react.createElement)("input", {
2000
+ value: config.trigger.jev?.model ?? "jev-latest",
2001
+ onChange: (e) => setConfig({
2002
+ ...config,
2003
+ trigger: {
2004
+ ...config.trigger,
2005
+ jev: {
2006
+ ...config.trigger.jev ?? defaultJevConfig(),
2007
+ model: e.target.value
2008
+ }
2009
+ }
2010
+ }),
2011
+ style: settingsInputStyle
2012
+ })), (0, react.createElement)("div", null, label("判断阈值(0-1)"), (0, react.createElement)("input", {
2013
+ type: "number",
2014
+ min: 0,
2015
+ max: 1,
2016
+ step: .05,
2017
+ value: String(config.trigger.jev?.confidenceThreshold ?? .6),
2018
+ onChange: (e) => setConfig({
2019
+ ...config,
2020
+ trigger: {
2021
+ ...config.trigger,
2022
+ jev: {
2023
+ ...config.trigger.jev ?? defaultJevConfig(),
2024
+ confidenceThreshold: Number(e.target.value)
2025
+ }
2026
+ }
2027
+ }),
2028
+ style: settingsInputStyle
2029
+ })), (0, react.createElement)("div", null, label("高风险阈值(0-1,空=跟随判断阈值)"), (0, react.createElement)("input", {
2030
+ type: "number",
2031
+ min: 0,
2032
+ max: 1,
2033
+ step: .05,
2034
+ placeholder: String(config.trigger.jev?.confidenceThreshold ?? .6),
2035
+ value: config.trigger.jev?.highRiskThreshold !== void 0 ? String(config.trigger.jev.highRiskThreshold) : "",
2036
+ onChange: (e) => setConfig({
2037
+ ...config,
2038
+ trigger: {
2039
+ ...config.trigger,
2040
+ jev: {
2041
+ ...config.trigger.jev ?? defaultJevConfig(),
2042
+ highRiskThreshold: e.target.value === "" ? void 0 : Number(e.target.value)
2043
+ }
2044
+ }
2045
+ }),
2046
+ style: settingsInputStyle
2047
+ })), (0, react.createElement)("div", null, label("请求超时(毫秒)"), (0, react.createElement)("input", {
2048
+ type: "number",
2049
+ min: 1e3,
2050
+ max: 6e4,
2051
+ step: 1e3,
2052
+ value: String(config.trigger.jev?.timeoutMs ?? 1e4),
2053
+ onChange: (e) => setConfig({
2054
+ ...config,
2055
+ trigger: {
2056
+ ...config.trigger,
2057
+ jev: {
2058
+ ...config.trigger.jev ?? defaultJevConfig(),
2059
+ timeoutMs: Math.max(1e3, Number(e.target.value))
2060
+ }
2061
+ }
2062
+ }),
2063
+ style: settingsInputStyle
2064
+ }))), (0, react.createElement)("div", { style: { marginTop: 8 } }, label("API 地址(留空使用渠道默认地址)"), (0, react.createElement)("input", {
2065
+ value: config.trigger.jev?.baseURL || JEV_DEFAULT_ENDPOINTS[config.trigger.jev?.provider ?? "typesafe"],
2066
+ placeholder: JEV_DEFAULT_ENDPOINTS[config.trigger.jev?.provider ?? "typesafe"],
2067
+ onChange: (e) => setConfig({
2068
+ ...config,
2069
+ trigger: {
2070
+ ...config.trigger,
2071
+ jev: {
2072
+ ...config.trigger.jev ?? defaultJevConfig(),
2073
+ baseURL: e.target.value
2074
+ }
2075
+ }
2076
+ }),
2077
+ style: settingsInputStyle
2078
+ })), (0, react.createElement)("div", { style: {
2079
+ ...settingsGridStyle,
2080
+ marginTop: 12
2081
+ } }, (0, react.createElement)("div", null, label("Jev API Key"), (0, react.createElement)("input", {
2082
+ type: "password",
2083
+ placeholder: config.trigger.jev?.apiKeyMeta?.configured ? `已配置 ••••${config.trigger.jev.apiKeyMeta.last4 ?? ""}` : "输入 API Key",
2084
+ value: config.trigger.jev?.apiKey ?? "",
2085
+ onChange: (e) => setConfig({
2086
+ ...config,
2087
+ trigger: {
2088
+ ...config.trigger,
2089
+ jev: {
2090
+ ...config.trigger.jev ?? defaultJevConfig(),
2091
+ apiKey: e.target.value
2092
+ }
2093
+ }
2094
+ }),
2095
+ style: settingsInputStyle
2096
+ })), (0, react.createElement)("div", null, label("API Key 环境变量名(可选)"), (0, react.createElement)("input", {
2097
+ placeholder: "TYPESAFE_API_KEY 或 OPENROUTER_API_KEY",
2098
+ value: config.trigger.jev?.apiKeyEnv ?? "",
2099
+ onChange: (e) => setConfig({
2100
+ ...config,
2101
+ trigger: {
2102
+ ...config.trigger,
2103
+ jev: {
2104
+ ...config.trigger.jev ?? defaultJevConfig(),
2105
+ apiKeyEnv: e.target.value
2106
+ }
2107
+ }
2108
+ }),
2109
+ style: settingsInputStyle
2110
+ }))), (0, react.createElement)("div", { style: {
2111
+ display: "flex",
2112
+ alignItems: "center",
2113
+ gap: 8,
2114
+ marginTop: 8
2115
+ } }, (0, react.createElement)("input", {
2116
+ type: "checkbox",
2117
+ checked: config.trigger.jev?.useEnglishState ?? false,
2118
+ onChange: (e) => setConfig({
2119
+ ...config,
2120
+ trigger: {
2121
+ ...config.trigger,
2122
+ jev: {
2123
+ ...config.trigger.jev ?? defaultJevConfig(),
2124
+ useEnglishState: e.target.checked
2125
+ }
2126
+ }
2127
+ })
2128
+ }), label("使用英文概要辅助判定")), (0, react.createElement)("div", { style: {
2129
+ ...settingsHelpStyle,
2130
+ margin: "4px 0 0"
2131
+ } }, "开启后,Jev 读取主模型提供的 questionEn;界面和会话记录仍保留原始问题。默认关闭。")),
2132
+ advanced: fold("advanced", "高级", "自动深挖、前置分类器与联网提示等低频选项。", (0, react.createElement)("div", { style: {
2133
+ display: "flex",
2134
+ alignItems: "center",
2135
+ gap: 8
2136
+ } }, (0, react.createElement)("input", {
2137
+ type: "checkbox",
2138
+ checked: config.discussion.autoDeepen,
2139
+ onChange: (e) => setConfig({
2140
+ ...config,
2141
+ discussion: {
2142
+ ...config.discussion,
2143
+ autoDeepen: e.target.checked
2144
+ }
2145
+ })
2146
+ }), label("轮间自动生成更深追问")), (0, react.createElement)("div", { style: {
2147
+ ...settingsHelpStyle,
2148
+ margin: "4px 0 10px"
2149
+ } }, "关闭后仍按轮数上限重复原问题,综合结论照常生成。"), (0, react.createElement)("div", { style: {
2150
+ display: "flex",
2151
+ alignItems: "center",
2152
+ gap: 8,
2153
+ marginTop: 4
2154
+ } }, (0, react.createElement)("input", {
2155
+ type: "checkbox",
2156
+ checked: config.trigger.requireClassifier,
2157
+ onChange: (e) => setConfig({
2158
+ ...config,
2159
+ trigger: {
2160
+ ...config.trigger,
2161
+ requireClassifier: e.target.checked
2162
+ }
2163
+ })
2164
+ }), label("会诊前运行分类器")), (0, react.createElement)("div", { style: {
2165
+ display: "flex",
2166
+ alignItems: "center",
2167
+ gap: 8,
2168
+ marginTop: 4
2169
+ } }, (0, react.createElement)("input", {
2170
+ type: "checkbox",
2171
+ checked: config.trigger.allowWebFallback,
2172
+ onChange: (e) => setConfig({
2173
+ ...config,
2174
+ trigger: {
2175
+ ...config.trigger,
2176
+ allowWebFallback: e.target.checked
2177
+ }
2178
+ })
2179
+ }), label("更适合联网时返回搜索提示"))),
2180
+ advisors: fold("advisors", `顾问列表(${config.advisors.length})`, "配置参与会诊的模型、凭据与连接测试。", (0, react.createElement)("div", { style: {
2181
+ display: "flex",
2182
+ justifyContent: "flex-end",
2183
+ marginBottom: 8
2184
+ } }, (0, react.createElement)("button", {
2185
+ type: "button",
2186
+ onClick: addAdvisor,
2187
+ style: settingsSecondaryButtonStyle
2188
+ }, "+ 添加顾问")), config.advisors.map((advisor, index) => (0, react.createElement)("div", {
2189
+ key: advisor.id,
2190
+ style: advisorCardStyle
2191
+ }, (0, react.createElement)("div", { style: {
2192
+ display: "flex",
2193
+ justifyContent: "space-between",
2194
+ gap: 8
2195
+ } }, (0, react.createElement)("strong", null, advisor.name || advisor.id), (0, react.createElement)("button", {
2196
+ type: "button",
2197
+ onClick: () => removeAdvisor(index),
2198
+ style: settingsDangerButtonStyle
2199
+ }, "删除")), (0, react.createElement)("div", { style: settingsHelpStyle }, `ID · ${advisor.id}`), label("顾问名称"), (0, react.createElement)("input", {
2200
+ value: advisor.name,
2201
+ onChange: (e) => updateAdvisor(index, { name: e.target.value }),
2202
+ style: settingsInputStyle
2203
+ }), label("头像"), (0, react.createElement)("div", { style: {
2204
+ display: "flex",
2205
+ flexWrap: "wrap",
2206
+ gap: 4,
2207
+ marginTop: 2
2208
+ } }, (0, react.createElement)("button", {
2209
+ type: "button",
2210
+ onClick: () => updateAdvisor(index, { avatar: void 0 }),
2211
+ style: {
2212
+ cursor: "pointer",
2213
+ fontSize: 12,
2214
+ padding: "2px 6px",
2215
+ border: advisor.avatar === void 0 || advisor.avatar === "" ? "2px solid var(--dsw-alias-brand-primary, #4ade80)" : "1px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
2216
+ background: "transparent"
2217
+ }
2218
+ }, "无"), AVATAR_OPTIONS.map((emoji) => (0, react.createElement)("button", {
2219
+ key: emoji,
2220
+ type: "button",
2221
+ onClick: () => updateAdvisor(index, { avatar: emoji }),
2222
+ style: {
2223
+ cursor: "pointer",
2224
+ fontSize: 18,
2225
+ padding: "2px 6px",
2226
+ border: advisor.avatar === emoji ? "2px solid var(--dsw-alias-brand-primary, #4ade80)" : "1px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
2227
+ background: advisor.avatar === emoji ? "var(--dsw-alias-bg-layer-2, transparent)" : "transparent"
2228
+ }
2229
+ }, emoji))), (0, react.createElement)("input", {
2230
+ placeholder: "自定义 emoji(可选)",
2231
+ value: advisor.avatar ?? "",
2232
+ onChange: (e) => updateAdvisor(index, { avatar: e.target.value }),
2233
+ style: {
2234
+ ...settingsInputStyle,
2235
+ marginTop: 4
2236
+ }
2237
+ }), label("供应商"), (0, react.createElement)("select", {
2238
+ "aria-label": "供应商",
2239
+ value: customModes.has(advisor.id) || !providers.some((p) => p.id === advisor.provider) ? "__custom__" : advisor.provider,
2240
+ onChange: (e) => {
2241
+ const value = e.target.value;
2242
+ if (value === "__custom__") {
2243
+ const next = new Set(customModes);
2244
+ next.add(advisor.id);
2245
+ setCustomModes(next);
2246
+ return;
2247
+ }
2248
+ const next = new Set(customModes);
2249
+ next.delete(advisor.id);
2250
+ setCustomModes(next);
2251
+ const provider = providers.find((p) => p.id === value);
2252
+ if (provider?.kind === "llm") updateAdvisor(index, {
2253
+ provider: value,
2254
+ baseURL: void 0,
2255
+ apiKey: "",
2256
+ clearApiKey: false,
2257
+ apiKeyEnv: void 0,
2258
+ protocol: void 0
2259
+ });
2260
+ else updateAdvisor(index, {
2261
+ provider: value,
2262
+ apiKey: "",
2263
+ clearApiKey: false,
2264
+ ...provider?.baseURL ? { baseURL: provider.baseURL } : {},
2265
+ ...provider?.apiKeyEnv ? { apiKeyEnv: provider.apiKeyEnv } : {},
2266
+ ...provider?.protocol ? { protocol: provider.protocol } : {}
2267
+ });
2268
+ },
2269
+ style: settingsInputStyle
2270
+ }, (0, react.createElement)("option", {
2271
+ value: "",
2272
+ disabled: true
2273
+ }, "请选择供应商"), providers.map((provider) => (0, react.createElement)("option", {
2274
+ key: provider.id,
2275
+ value: provider.id
2276
+ }, `${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", {
2277
+ placeholder: "自定义 provider 标识",
2278
+ value: advisor.provider,
2279
+ onChange: (e) => updateAdvisor(index, { provider: e.target.value }),
2280
+ style: {
2281
+ ...settingsInputStyle,
2282
+ marginTop: 4
2283
+ }
2284
+ }) : null, label("模型"), (0, react.createElement)("div", { style: {
2285
+ display: "flex",
2286
+ flexWrap: "wrap",
2287
+ alignItems: "center",
2288
+ gap: 8
2289
+ } }, (0, react.createElement)("input", {
2290
+ "aria-label": "模型",
2291
+ list: `advisor-models-${index}`,
2292
+ placeholder: "model",
2293
+ value: advisor.model,
2294
+ onChange: (e) => updateAdvisor(index, { model: e.target.value }),
2295
+ style: {
2296
+ ...settingsInputStyle,
2297
+ flex: "1 1 220px",
2298
+ border: advisor.model && advisor.model.trim() ? void 0 : "1px solid var(--dsw-alias-state-error-primary, #e11d48)"
2299
+ }
2300
+ }), (0, react.createElement)("button", {
2301
+ type: "button",
2302
+ onClick: () => void loadModels(index),
2303
+ disabled: !!fetchingModels[advisor.id],
2304
+ style: settingsSecondaryButtonStyle
2305
+ }, fetchingModels[advisor.id] ? "获取中…" : "获取模型列表"), (0, react.createElement)("button", {
2306
+ type: "button",
2307
+ onClick: () => void testConnection(index),
2308
+ disabled: !!testingConnection[advisor.id],
2309
+ style: settingsSecondaryButtonStyle
2310
+ }, testingConnection[advisor.id] ? "测试中…" : "测试连接")), !advisor.model?.trim() ? (0, react.createElement)("div", { style: {
2311
+ color: "var(--dsw-alias-state-error-primary, #e11d48)",
2014
2312
  fontSize: 12,
2015
- padding: "2px 6px",
2016
- border: advisor.avatar === void 0 || advisor.avatar === "" ? "2px solid #4ade80" : "1px solid var(--dsh-color-border, #333)",
2017
- background: "transparent"
2018
- }
2019
- }, "无"), AVATAR_OPTIONS.map((emoji) => (0, react.createElement)("button", {
2020
- key: emoji,
2313
+ marginTop: 2
2314
+ } }, "⚠️ 未配置模型时,无法发起会诊。") : 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", {
2315
+ key: model,
2316
+ value: model
2317
+ }, model))), (fetchedModels[advisor.id] ?? []).length > 0 ? (0, react.createElement)("div", { style: {
2318
+ maxHeight: 150,
2319
+ overflowY: "auto",
2320
+ border: "1px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
2321
+ borderRadius: 4,
2322
+ marginTop: 4
2323
+ } }, (fetchedModels[advisor.id] ?? []).map((model) => (0, react.createElement)("button", {
2324
+ key: model,
2325
+ type: "button",
2326
+ onClick: () => updateAdvisor(index, { model }),
2327
+ style: {
2328
+ display: "block",
2329
+ width: "100%",
2330
+ textAlign: "left",
2331
+ cursor: "pointer",
2332
+ padding: "3px 6px",
2333
+ background: "transparent",
2334
+ border: "none",
2335
+ color: "inherit",
2336
+ fontSize: 12
2337
+ }
2338
+ }, model))) : null, ...providers.find((p) => p.id === advisor.provider)?.kind === "llm" ? [(0, react.createElement)("div", { style: {
2339
+ ...settingsHelpStyle,
2340
+ marginTop: 4
2341
+ } }, "DSH 内置线路:复用已配置凭据;如需顾问工具,可另配直连地址和 Key。")] : (() => {
2342
+ const keyMeta = advisor.apiKeyMetaByProvider?.[advisor.provider];
2343
+ const keyConfigured = keyMeta?.configured === true;
2344
+ return [
2345
+ label("API 地址"),
2346
+ (0, react.createElement)("input", {
2347
+ "aria-label": "API 地址",
2348
+ placeholder: "https://api.example.com/v1",
2349
+ value: advisor.baseURL ?? "",
2350
+ onChange: (e) => updateAdvisor(index, { baseURL: e.target.value }),
2351
+ style: settingsInputStyle
2352
+ }),
2353
+ (0, react.createElement)("div", { style: {
2354
+ display: "flex",
2355
+ alignItems: "flex-end",
2356
+ gap: 6,
2357
+ flexWrap: "wrap"
2358
+ } }, (0, react.createElement)("div", { style: {
2359
+ flex: 1,
2360
+ minWidth: 200
2361
+ } }, (0, react.createElement)("div", { style: {
2362
+ display: "flex",
2363
+ alignItems: "center",
2364
+ gap: 8
2365
+ } }, label("API Key"), (0, react.createElement)("span", { style: {
2366
+ fontSize: 11,
2367
+ padding: "1px 8px",
2368
+ borderRadius: 999,
2369
+ background: "var(--dsw-alias-bg-layer-2, transparent)",
2370
+ color: keyConfigured ? "var(--dsw-alias-state-success-primary, #16a34a)" : "var(--dsw-alias-label-tertiary, #8b90a0)"
2371
+ } }, keyConfigured ? `已配置 ••••${keyMeta?.last4 ?? ""}` : "未配置")), (0, react.createElement)("input", {
2372
+ type: "password",
2373
+ "aria-label": "API Key",
2374
+ placeholder: keyConfigured ? "输入新 Key 可覆盖;留空保持不变" : "sk-...",
2375
+ value: advisor.apiKey ?? "",
2376
+ onChange: (e) => updateAdvisor(index, {
2377
+ apiKey: e.target.value,
2378
+ clearApiKey: false
2379
+ }),
2380
+ style: {
2381
+ ...settingsInputStyle,
2382
+ marginTop: 4
2383
+ }
2384
+ })), keyConfigured ? (0, react.createElement)("button", {
2385
+ type: "button",
2386
+ disabled: saving,
2387
+ onClick: () => updateAdvisor(index, {
2388
+ apiKey: "",
2389
+ clearApiKey: true
2390
+ }),
2391
+ style: settingsSecondaryButtonStyle
2392
+ }, "清除") : null),
2393
+ (0, react.createElement)("div", { style: {
2394
+ ...settingsHelpStyle,
2395
+ marginTop: 4
2396
+ } }, keyConfigured ? "仅显示末 4 位;留空保留原 Key,输入新 Key 覆盖。" : "输入 API Key,保存到本机配置。"),
2397
+ label("协议"),
2398
+ (0, react.createElement)("select", {
2399
+ "aria-label": "协议",
2400
+ value: advisor.protocol ?? "openai",
2401
+ onChange: (e) => updateAdvisor(index, { protocol: e.target.value }),
2402
+ style: settingsInputStyle
2403
+ }, (0, react.createElement)("option", { value: "openai" }, "OpenAI 兼容"), (0, react.createElement)("option", { value: "anthropic" }, "Anthropic"), (0, react.createElement)("option", { value: "gemini" }, "Gemini"))
2404
+ ];
2405
+ })(), label("顾问系统提示词"), (0, react.createElement)("textarea", {
2406
+ "aria-label": "systemPrompt",
2407
+ placeholder: "说明这位顾问的专业角色与回答要求",
2408
+ rows: 2,
2409
+ value: advisor.systemPrompt,
2410
+ onChange: (e) => updateAdvisor(index, { systemPrompt: e.target.value }),
2411
+ style: {
2412
+ ...settingsInputStyle,
2413
+ marginTop: 4,
2414
+ resize: "vertical"
2415
+ }
2416
+ }), (() => {
2417
+ const supportsTools = !providers.some((p) => p.id === advisor.provider && p.kind === "llm") || Boolean(advisor.baseURL || advisor.apiKeyEnv);
2418
+ const globalDefault = config.discussion.advisorTools ?? "readonly";
2419
+ const mode = advisor.tools ?? globalDefault;
2420
+ const modeLabel = {
2421
+ readonly: "只读工具",
2422
+ all: "全部会话工具(含可写,慎用)",
2423
+ off: "关闭"
2424
+ };
2425
+ const toolPromptNote = "启用工具后,系统会提醒顾问优先核实未知信息,不要编造。";
2426
+ if (supportsTools) return [
2427
+ label(`工具范围(默认跟随会诊行为:${modeLabel[globalDefault] ?? globalDefault})`),
2428
+ (0, react.createElement)("select", {
2429
+ "aria-label": "顾问工具调用范围",
2430
+ value: mode,
2431
+ onChange: (e) => updateAdvisor(index, { tools: e.target.value }),
2432
+ style: settingsInputStyle
2433
+ }, (0, react.createElement)("option", { value: "readonly" }, "只读工具"), (0, react.createElement)("option", { value: "all" }, "全部会话工具(含可写,慎用)"), (0, react.createElement)("option", { value: "off" }, "关闭")),
2434
+ mode === "off" ? null : (0, react.createElement)("div", { style: {
2435
+ ...settingsHelpStyle,
2436
+ border: "1px dashed var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
2437
+ borderRadius: 6,
2438
+ padding: "6px 8px",
2439
+ marginTop: 6
2440
+ } }, toolPromptNote)
2441
+ ];
2442
+ return [label("顾问工具调用范围"), (0, react.createElement)("div", { style: {
2443
+ ...settingsHelpStyle,
2444
+ border: "1px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
2445
+ borderRadius: 6,
2446
+ padding: "6px 8px"
2447
+ } }, "当前内置通道不支持顾问工具;配置 API 地址、Key 或环境变量后可改用直连。")];
2448
+ })(), label("思考模式与强度(未设置时跟随模型或供应商默认)"), (0, react.createElement)("div", { style: {
2449
+ display: "flex",
2450
+ gap: 8,
2451
+ marginTop: 4
2452
+ } }, (0, react.createElement)("select", {
2453
+ "aria-label": "思考模式",
2454
+ value: advisor.thinking ?? "",
2455
+ onChange: (e) => updateAdvisor(index, { thinking: e.target.value === "" ? void 0 : e.target.value }),
2456
+ style: settingsInputStyle
2457
+ }, (0, react.createElement)("option", { value: "" }, "思考:默认"), (0, react.createElement)("option", { value: "enabled" }, "思考:开启"), (0, react.createElement)("option", { value: "disabled" }, "思考:关闭")), (0, react.createElement)("select", {
2458
+ "aria-label": "思考强度",
2459
+ value: advisor.reasoningEffort ?? "",
2460
+ onChange: (e) => updateAdvisor(index, { reasoningEffort: e.target.value === "" ? void 0 : e.target.value }),
2461
+ style: settingsInputStyle
2462
+ }, (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(最大)"))), label("采样参数(可选)"), (0, react.createElement)("div", { style: {
2463
+ display: "flex",
2464
+ flexWrap: "wrap",
2465
+ gap: 8,
2466
+ marginTop: 4
2467
+ } }, (0, react.createElement)("input", {
2468
+ type: "number",
2469
+ min: 0,
2470
+ max: 2,
2471
+ step: .1,
2472
+ placeholder: "temperature(0–2)",
2473
+ value: advisor.temperature ?? "",
2474
+ onChange: (e) => updateAdvisor(index, { temperature: e.target.value === "" ? void 0 : Number(e.target.value) }),
2475
+ style: {
2476
+ ...settingsInputStyle,
2477
+ flex: "1 1 140px"
2478
+ }
2479
+ }), (0, react.createElement)("input", {
2480
+ type: "number",
2481
+ min: 1,
2482
+ placeholder: "maxTokens(输出上限)",
2483
+ value: advisor.maxTokens ?? "",
2484
+ onChange: (e) => updateAdvisor(index, { maxTokens: e.target.value === "" ? void 0 : Number(e.target.value) }),
2485
+ style: {
2486
+ ...settingsInputStyle,
2487
+ flex: "1 1 160px"
2488
+ }
2489
+ })))))
2490
+ };
2491
+ return (0, react.createElement)("li", {
2492
+ className: "dsh-advisor-group-settings",
2493
+ style: settingsCardStyle(open)
2494
+ }, (0, react.createElement)("style", null, SETTINGS_ACCESSIBILITY_CSS), settingsCardHeader(open, dirty, () => setOpen((value) => !value)), open ? (0, react.createElement)("div", { style: settingsCardBodyStyle }, (0, react.createElement)("div", { style: settingsRootStyle }, sections.basics, sections.advisors, sections.discussion, sections.trigger, sections.swarm, sections.advanced, msg || error || dirty ? (0, react.createElement)("div", { style: settingsCardFooterStyle }, msg ? (0, react.createElement)("span", { style: {
2495
+ flex: 1,
2496
+ minWidth: 0,
2497
+ color: "var(--dsw-alias-state-success-primary, #16a34a)",
2498
+ fontSize: 12
2499
+ } }, msg) : null, error ? (0, react.createElement)("span", { style: {
2500
+ flex: 1,
2501
+ minWidth: 0,
2502
+ color: "var(--dsw-alias-state-error-primary, #e11d48)",
2503
+ fontSize: 12
2504
+ } }, error) : null, (0, react.createElement)("button", {
2021
2505
  type: "button",
2022
- onClick: () => updateAdvisor(index, { avatar: emoji }),
2023
- style: {
2024
- cursor: "pointer",
2025
- fontSize: 18,
2026
- padding: "2px 6px",
2027
- border: advisor.avatar === emoji ? "2px solid #4ade80" : "1px solid var(--dsh-color-border, #333)",
2028
- background: advisor.avatar === emoji ? "rgba(74,222,128,.15)" : "transparent"
2506
+ disabled: !dirty || saving,
2507
+ onClick: () => void reload(),
2508
+ style: settingsSecondaryButtonStyle
2509
+ }, "放弃更改")) : null)) : null);
2510
+ }
2511
+ const corpsRootStyle = {
2512
+ boxSizing: "border-box",
2513
+ maxWidth: 920,
2514
+ margin: "0 auto",
2515
+ padding: "28px 24px 48px",
2516
+ fontFamily: "'Inter', 'Segoe UI', system-ui, sans-serif",
2517
+ fontSize: 13,
2518
+ color: "inherit"
2519
+ };
2520
+ const corpsHeaderStyle = {
2521
+ display: "flex",
2522
+ alignItems: "flex-start",
2523
+ justifyContent: "space-between",
2524
+ gap: 16,
2525
+ marginBottom: 18
2526
+ };
2527
+ const corpsTitleStyle = {
2528
+ margin: 0,
2529
+ fontSize: 20,
2530
+ fontWeight: 700,
2531
+ letterSpacing: .2
2532
+ };
2533
+ const corpsDescStyle = {
2534
+ margin: "6px 0 0",
2535
+ fontSize: 12,
2536
+ lineHeight: 1.6,
2537
+ color: "var(--dsw-alias-label-tertiary, var(--dsh-color-muted, #8b90a0))"
2538
+ };
2539
+ const corpsCardStyle = {
2540
+ border: "1px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
2541
+ borderRadius: 12,
2542
+ padding: "12px 14px",
2543
+ marginBottom: 10,
2544
+ background: "var(--dsw-alias-bg-layer-2, transparent)",
2545
+ display: "grid",
2546
+ gap: 9
2547
+ };
2548
+ const corpsRowHeadStyle = {
2549
+ display: "flex",
2550
+ alignItems: "center",
2551
+ gap: 8,
2552
+ flexWrap: "wrap",
2553
+ minWidth: 0
2554
+ };
2555
+ const corpsNameStyle = {
2556
+ fontWeight: 600,
2557
+ fontSize: 13,
2558
+ overflow: "hidden",
2559
+ textOverflow: "ellipsis",
2560
+ whiteSpace: "nowrap",
2561
+ maxWidth: "60%"
2562
+ };
2563
+ const corpsMetaStyle = {
2564
+ fontSize: 11,
2565
+ color: "var(--dsw-alias-label-tertiary, var(--dsh-color-muted, #8b90a0))",
2566
+ overflow: "hidden",
2567
+ textOverflow: "ellipsis",
2568
+ whiteSpace: "nowrap"
2569
+ };
2570
+ const corpsBadgeStyle = (color) => ({
2571
+ fontSize: 10,
2572
+ lineHeight: "16px",
2573
+ padding: "0 7px",
2574
+ borderRadius: 999,
2575
+ border: `1px solid ${color}`,
2576
+ color,
2577
+ flex: "none"
2578
+ });
2579
+ const corpsFieldsStyle = {
2580
+ display: "grid",
2581
+ gridTemplateColumns: "minmax(140px, 200px) minmax(200px, 1fr) auto",
2582
+ gap: 8,
2583
+ alignItems: "end"
2584
+ };
2585
+ const corpsLabelStyle = {
2586
+ display: "block",
2587
+ fontSize: 11,
2588
+ color: "var(--dsw-alias-label-tertiary, var(--dsh-color-muted, #8b90a0))",
2589
+ marginBottom: 4
2590
+ };
2591
+ const corpsInputStyle = {
2592
+ width: "100%",
2593
+ boxSizing: "border-box",
2594
+ padding: "5px 8px",
2595
+ borderRadius: 6,
2596
+ border: "1px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
2597
+ background: "transparent",
2598
+ color: "inherit",
2599
+ font: "inherit"
2600
+ };
2601
+ const corpsButtonStyle = {
2602
+ ...settingsFooterButtonStyle,
2603
+ border: "1px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
2604
+ background: "transparent",
2605
+ color: "inherit",
2606
+ padding: "5px 12px",
2607
+ height: 30,
2608
+ whiteSpace: "nowrap"
2609
+ };
2610
+ const corpsSectionTitleStyle = {
2611
+ fontSize: 12,
2612
+ fontWeight: 700,
2613
+ margin: "20px 0 8px",
2614
+ color: "var(--dsw-alias-label-secondary, inherit)"
2615
+ };
2616
+ const corpsNoticeStyle = {
2617
+ fontSize: 12,
2618
+ lineHeight: 1.6,
2619
+ padding: "8px 10px",
2620
+ borderRadius: 8,
2621
+ marginBottom: 12,
2622
+ border: "1px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
2623
+ color: "var(--dsw-alias-label-secondary, inherit)"
2624
+ };
2625
+ /**
2626
+ * 「顾问团」 conversation view: one row per visible session, with a user-curated
2627
+ * identity and duty. The role name is what `ask_session` can target later; the
2628
+ * panel never changes a session's own title.
2629
+ */
2630
+ function AdvisorCorpsView(props) {
2631
+ const currentSessionId = typeof props.sessionId === "string" ? props.sessionId : "";
2632
+ const [data, setData] = (0, react.useState)(null);
2633
+ const [drafts, setDrafts] = (0, react.useState)({});
2634
+ const [saving, setSaving] = (0, react.useState)("");
2635
+ const [generating, setGenerating] = (0, react.useState)(false);
2636
+ const [batchSaving, setBatchSaving] = (0, react.useState)(false);
2637
+ const [batchProgress, setBatchProgress] = (0, react.useState)("");
2638
+ const [copiedId, setCopiedId] = (0, react.useState)("");
2639
+ const [message, setMessage] = (0, react.useState)("");
2640
+ const [error, setError] = (0, react.useState)("");
2641
+ const applyPayload = (payload) => {
2642
+ setData(payload);
2643
+ const next = {};
2644
+ for (const row of payload.sessions) next[row.sessionId] = {
2645
+ name: row.card?.name ?? "",
2646
+ duty: row.card?.duty ?? ""
2647
+ };
2648
+ for (const row of payload.orphans) next[row.sessionId] = {
2649
+ name: row.name,
2650
+ duty: row.duty
2651
+ };
2652
+ setDrafts(next);
2653
+ };
2654
+ const load = async () => {
2655
+ setError("");
2656
+ try {
2657
+ const body = await (await authedFetch("/advisor-group/corps")).json();
2658
+ if (body.ok !== true) {
2659
+ setError(body.error ?? "加载顾问团失败。");
2660
+ return;
2661
+ }
2662
+ applyPayload(body);
2663
+ } catch (err) {
2664
+ setError(err instanceof Error ? err.message : String(err));
2029
2665
  }
2030
- }, emoji))), (0, react.createElement)("input", {
2031
- placeholder: "自定义 emoji(可选)",
2032
- value: advisor.avatar ?? "",
2033
- onChange: (e) => updateAdvisor(index, { avatar: e.target.value }),
2034
- style: {
2035
- ...settingsInputStyle,
2036
- marginTop: 4
2666
+ };
2667
+ (0, react.useEffect)(() => {
2668
+ load();
2669
+ }, []);
2670
+ const generate = async () => {
2671
+ if (currentSessionId.length === 0) {
2672
+ setError("当前会话 id 不可用,无法调用主模型。");
2673
+ return;
2037
2674
  }
2038
- }), label("供应商"), (0, react.createElement)("select", {
2039
- "aria-label": "供应商",
2040
- value: customModes.has(advisor.id) || !providers.some((p) => p.id === advisor.provider) ? "__custom__" : advisor.provider,
2041
- onChange: (e) => {
2042
- const value = e.target.value;
2043
- if (value === "__custom__") {
2044
- const next = new Set(customModes);
2045
- next.add(advisor.id);
2046
- setCustomModes(next);
2675
+ setGenerating(true);
2676
+ setError("");
2677
+ setMessage("");
2678
+ try {
2679
+ const body = await (await authedFetch("/advisor-group/corps/generate", {
2680
+ method: "POST",
2681
+ headers: { "content-type": "application/json" },
2682
+ body: JSON.stringify({ sessionId: currentSessionId })
2683
+ })).json();
2684
+ if (body.ok !== true) {
2685
+ setError(body.error ?? "生成失败。");
2047
2686
  return;
2048
2687
  }
2049
- const next = new Set(customModes);
2050
- next.delete(advisor.id);
2051
- setCustomModes(next);
2052
- const provider = providers.find((p) => p.id === value);
2053
- if (provider?.kind === "llm") updateAdvisor(index, {
2054
- provider: value,
2055
- baseURL: void 0,
2056
- apiKey: "",
2057
- clearApiKey: false,
2058
- apiKeyEnv: void 0,
2059
- protocol: void 0
2688
+ const proposals = Array.isArray(body.proposals) ? body.proposals : [];
2689
+ if (proposals.length === 0) {
2690
+ setMessage(body.note ?? "没有需要生成的会话。");
2691
+ return;
2692
+ }
2693
+ setDrafts((prev) => {
2694
+ const next = { ...prev };
2695
+ for (const proposal of proposals) next[proposal.sessionId] = {
2696
+ name: proposal.name,
2697
+ duty: proposal.duty
2698
+ };
2699
+ return next;
2060
2700
  });
2061
- else updateAdvisor(index, {
2062
- provider: value,
2063
- apiKey: "",
2064
- clearApiKey: false,
2065
- ...provider?.baseURL ? { baseURL: provider.baseURL } : {},
2066
- ...provider?.apiKeyEnv ? { apiKeyEnv: provider.apiKeyEnv } : {},
2067
- ...provider?.protocol ? { protocol: provider.protocol } : {}
2701
+ setMessage(body.note ?? `已生成 ${proposals.length} 张角色卡,确认后逐条点「分配」保存。`);
2702
+ } catch (err) {
2703
+ setError(err instanceof Error ? err.message : String(err));
2704
+ } finally {
2705
+ setGenerating(false);
2706
+ }
2707
+ };
2708
+ const persist = async (sessionId, name, duty) => {
2709
+ setSaving(sessionId);
2710
+ setError("");
2711
+ setMessage("");
2712
+ try {
2713
+ const body = await (await authedFetch("/advisor-group/corps", {
2714
+ method: "POST",
2715
+ headers: { "content-type": "application/json" },
2716
+ body: JSON.stringify({
2717
+ sessionId,
2718
+ name,
2719
+ duty
2720
+ })
2721
+ })).json();
2722
+ if (body.ok !== true || body.cards === void 0) {
2723
+ setError(body.error ?? "保存失败。");
2724
+ return;
2725
+ }
2726
+ const cards = body.cards;
2727
+ setData((prev) => prev === null ? prev : {
2728
+ ...prev,
2729
+ sessions: prev.sessions.map((row) => ({
2730
+ ...row,
2731
+ card: cards[row.sessionId] ?? null
2732
+ })),
2733
+ orphans: Object.entries(cards).filter(([id]) => !prev.sessions.some((row) => row.sessionId === id)).map(([id, card]) => ({
2734
+ sessionId: id,
2735
+ ...card
2736
+ })).sort((a, b) => a.name.localeCompare(b.name, "zh"))
2068
2737
  });
2069
- },
2070
- style: settingsInputStyle
2071
- }, (0, react.createElement)("option", {
2072
- value: "",
2073
- disabled: true
2074
- }, "请选择供应商"), providers.map((provider) => (0, react.createElement)("option", {
2075
- key: provider.id,
2076
- value: provider.id
2077
- }, `${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", {
2078
- placeholder: "自定义 provider 标识",
2079
- value: advisor.provider,
2080
- onChange: (e) => updateAdvisor(index, { provider: e.target.value }),
2081
- style: {
2082
- ...settingsInputStyle,
2083
- marginTop: 4
2738
+ setDrafts((prev) => ({
2739
+ ...prev,
2740
+ [sessionId]: {
2741
+ name: cards[sessionId]?.name ?? "",
2742
+ duty: cards[sessionId]?.duty ?? ""
2743
+ }
2744
+ }));
2745
+ setMessage(name.trim() === "" ? "已移除角色卡。" : "已保存。");
2746
+ } catch (err) {
2747
+ setError(err instanceof Error ? err.message : String(err));
2748
+ } finally {
2749
+ setSaving("");
2084
2750
  }
2085
- }) : null, label("模型"), (0, react.createElement)("div", { style: {
2086
- display: "flex",
2087
- gap: 4
2088
- } }, (0, react.createElement)("input", {
2089
- "aria-label": "模型",
2090
- list: `advisor-models-${index}`,
2091
- placeholder: "model",
2092
- value: advisor.model,
2093
- onChange: (e) => updateAdvisor(index, { model: e.target.value }),
2094
- style: {
2095
- ...settingsInputStyle,
2096
- marginTop: 4,
2097
- border: advisor.model && advisor.model.trim() ? void 0 : "1px solid #e11d48"
2751
+ };
2752
+ const save = (sessionId) => {
2753
+ const draft = drafts[sessionId] ?? {
2754
+ name: "",
2755
+ duty: ""
2756
+ };
2757
+ persist(sessionId, draft.name, draft.duty);
2758
+ };
2759
+ const draftOf = (sessionId, fallback) => drafts[sessionId] ?? fallback ?? {
2760
+ name: "",
2761
+ duty: ""
2762
+ };
2763
+ /** Every row whose draft differs from what is saved (sessions + orphans). */
2764
+ const dirtyTargets = () => {
2765
+ if (data === null) return [];
2766
+ const targets = [];
2767
+ for (const row of data.sessions) {
2768
+ const savedName = row.card?.name ?? "";
2769
+ const savedDuty = row.card?.duty ?? "";
2770
+ const draft = draftOf(row.sessionId, {
2771
+ name: savedName,
2772
+ duty: savedDuty
2773
+ });
2774
+ if (draft.name.trim() !== savedName || draft.duty.trim() !== savedDuty) targets.push({
2775
+ sessionId: row.sessionId,
2776
+ name: draft.name,
2777
+ duty: draft.duty
2778
+ });
2098
2779
  }
2099
- }), (0, react.createElement)("button", {
2100
- type: "button",
2101
- onClick: () => void loadModels(index),
2102
- disabled: !!fetchingModels[advisor.id],
2103
- style: {
2104
- cursor: "pointer",
2105
- marginTop: 4,
2106
- whiteSpace: "nowrap"
2780
+ for (const row of data.orphans) {
2781
+ const draft = draftOf(row.sessionId, {
2782
+ name: row.name,
2783
+ duty: row.duty
2784
+ });
2785
+ if (draft.name.trim() !== row.name || draft.duty.trim() !== row.duty) targets.push({
2786
+ sessionId: row.sessionId,
2787
+ name: draft.name,
2788
+ duty: draft.duty
2789
+ });
2107
2790
  }
2108
- }, fetchingModels[advisor.id] ? "获取中…" : "获取模型列表"), (0, react.createElement)("button", {
2109
- type: "button",
2110
- onClick: () => void testConnection(index),
2111
- disabled: !!testingConnection[advisor.id],
2112
- style: {
2113
- cursor: "pointer",
2114
- marginTop: 4,
2115
- whiteSpace: "nowrap"
2791
+ return targets;
2792
+ };
2793
+ const dirtyCount = dirtyTargets().length;
2794
+ const saveAll = async () => {
2795
+ const targets = dirtyTargets();
2796
+ if (targets.length === 0 || batchSaving) return;
2797
+ setBatchSaving(true);
2798
+ setBatchProgress(`0 / ${targets.length}`);
2799
+ setError("");
2800
+ setMessage("");
2801
+ let saved = 0;
2802
+ let removed = 0;
2803
+ let failed = false;
2804
+ try {
2805
+ for (const [index, target] of targets.entries()) {
2806
+ setBatchProgress(`${index + 1} / ${targets.length}`);
2807
+ const body = await (await authedFetch("/advisor-group/corps", {
2808
+ method: "POST",
2809
+ headers: { "content-type": "application/json" },
2810
+ body: JSON.stringify({
2811
+ sessionId: target.sessionId,
2812
+ name: target.name,
2813
+ duty: target.duty
2814
+ })
2815
+ })).json();
2816
+ if (body.ok !== true || body.cards === void 0) {
2817
+ setError(`第 ${index + 1} 条保存失败:${body.error ?? "未知错误"}`);
2818
+ failed = true;
2819
+ break;
2820
+ }
2821
+ saved += 1;
2822
+ if (target.name.trim() === "") removed += 1;
2823
+ }
2824
+ if (!failed) {
2825
+ await load();
2826
+ setMessage(`已保存 ${saved} 条${removed > 0 ? `,其中移除 ${removed} 条` : ""}。`);
2827
+ }
2828
+ } catch (err) {
2829
+ setError(err instanceof Error ? err.message : String(err));
2830
+ } finally {
2831
+ setBatchSaving(false);
2832
+ setBatchProgress("");
2116
2833
  }
2117
- }, testingConnection[advisor.id] ? "测试中…" : "测试连接")), !advisor.model?.trim() ? (0, react.createElement)("div", { style: {
2118
- color: "#e11d48",
2119
- fontSize: 12,
2120
- marginTop: 2
2121
- } }, "⚠️ 模型未配置,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", {
2122
- key: model,
2123
- value: model
2124
- }, model))), (fetchedModels[advisor.id] ?? []).length > 0 ? (0, react.createElement)("div", { style: {
2125
- maxHeight: 150,
2126
- overflowY: "auto",
2127
- border: "1px solid var(--dsh-color-border, #333)",
2128
- borderRadius: 4,
2129
- marginTop: 4
2130
- } }, (fetchedModels[advisor.id] ?? []).map((model) => (0, react.createElement)("button", {
2131
- key: model,
2834
+ };
2835
+ const copyName = async (sessionId, name) => {
2836
+ const text = name.trim();
2837
+ if (text.length === 0) return;
2838
+ const nav = globalThis.navigator;
2839
+ try {
2840
+ if (nav?.clipboard?.writeText === void 0) {
2841
+ setError("当前环境不支持自动复制,请手动选中姓名复制。");
2842
+ return;
2843
+ }
2844
+ await nav.clipboard.writeText(text);
2845
+ setCopiedId(sessionId);
2846
+ setTimeout(() => {
2847
+ setCopiedId((current) => current === sessionId ? "" : current);
2848
+ }, 1500);
2849
+ } catch {
2850
+ setError("复制失败,请手动选中姓名复制。");
2851
+ }
2852
+ };
2853
+ const copyButton = (sessionId, name) => (0, react.createElement)("button", {
2132
2854
  type: "button",
2133
- onClick: () => updateAdvisor(index, { model }),
2855
+ disabled: name.trim().length === 0,
2856
+ onClick: () => void copyName(sessionId, name),
2857
+ title: "复制姓名到剪贴板",
2134
2858
  style: {
2135
- display: "block",
2136
- width: "100%",
2137
- textAlign: "left",
2138
- cursor: "pointer",
2139
- padding: "3px 6px",
2140
- background: "transparent",
2141
- border: "none",
2142
- color: "inherit",
2143
- fontSize: 12
2859
+ ...corpsButtonStyle,
2860
+ marginLeft: "auto",
2861
+ height: 24,
2862
+ padding: "1px 8px",
2863
+ fontSize: 11,
2864
+ opacity: name.trim().length === 0 ? .45 : 1
2144
2865
  }
2145
- }, model))) : null, ...providers.find((p) => p.id === advisor.provider)?.kind === "llm" ? [(0, react.createElement)("div", { style: {
2146
- fontSize: 12,
2147
- color: "var(--dsh-color-muted, #8b90a0)",
2148
- marginTop: 4
2149
- } }, "✅ DSH 内置 Provider:复用 DSH 已配置凭据,无需填写 API 地址/Key。")] : (() => {
2150
- const keyMeta = advisor.apiKeyMetaByProvider?.[advisor.provider];
2151
- const keyConfigured = keyMeta?.configured === true;
2152
- return [
2153
- label("API 地址"),
2154
- (0, react.createElement)("input", {
2155
- "aria-label": "API 地址",
2156
- placeholder: "https://api.example.com/v1",
2157
- value: advisor.baseURL ?? "",
2158
- onChange: (e) => updateAdvisor(index, { baseURL: e.target.value }),
2159
- style: settingsInputStyle
2160
- }),
2161
- (0, react.createElement)("div", { style: {
2162
- display: "flex",
2163
- alignItems: "flex-end",
2164
- gap: 6,
2165
- flexWrap: "wrap"
2166
- } }, (0, react.createElement)("div", { style: {
2167
- flex: 1,
2168
- minWidth: 200
2169
- } }, (0, react.createElement)("div", { style: {
2170
- display: "flex",
2171
- alignItems: "center",
2172
- gap: 8
2173
- } }, label("API Key"), (0, react.createElement)("span", { style: {
2174
- fontSize: 11,
2175
- padding: "1px 8px",
2176
- borderRadius: 999,
2177
- background: keyConfigured ? "rgba(22,163,74,.15)" : "var(--dsh-color-muted, #8b90a0)",
2178
- color: keyConfigured ? "#16a34a" : "inherit"
2179
- } }, keyConfigured ? `已配置 ••••${keyMeta?.last4 ?? ""}` : "未配置")), (0, react.createElement)("input", {
2180
- type: "password",
2181
- "aria-label": "API Key",
2182
- placeholder: keyConfigured ? "输入新 Key 可覆盖;留空保持不变" : "sk-...",
2183
- value: advisor.apiKey ?? "",
2184
- onChange: (e) => updateAdvisor(index, {
2185
- apiKey: e.target.value,
2186
- clearApiKey: false
2187
- }),
2188
- style: {
2189
- ...settingsInputStyle,
2190
- marginTop: 4
2191
- }
2192
- })), keyConfigured ? (0, react.createElement)("button", {
2193
- type: "button",
2194
- disabled: saving,
2195
- onClick: () => updateAdvisor(index, {
2196
- apiKey: "",
2197
- clearApiKey: true
2198
- }),
2199
- style: {
2200
- cursor: "pointer",
2201
- border: "1px solid var(--dsh-color-border, #3a3f4b)",
2202
- background: "transparent",
2203
- color: "inherit",
2204
- borderRadius: 4,
2205
- padding: "4px 10px",
2206
- fontSize: 12,
2207
- whiteSpace: "nowrap"
2208
- }
2209
- }, "清除") : null),
2210
- (0, react.createElement)("div", { style: {
2211
- fontSize: 12,
2212
- color: "var(--dsh-color-muted, #8b90a0)",
2213
- marginTop: 2
2214
- } }, keyConfigured ? "已保存该供应商的 API Key(仅显示末 4 位);留空保存保持原 Key,输入新 Key 覆盖。" : "直接填入 API Key,保存到本地配置。"),
2215
- label("协议"),
2216
- (0, react.createElement)("select", {
2217
- "aria-label": "协议",
2218
- value: advisor.protocol ?? "openai",
2219
- onChange: (e) => updateAdvisor(index, { protocol: e.target.value }),
2220
- style: settingsInputStyle
2221
- }, (0, react.createElement)("option", { value: "openai" }, "OpenAI 兼容"), (0, react.createElement)("option", { value: "anthropic" }, "Anthropic"), (0, react.createElement)("option", { value: "gemini" }, "Gemini"))
2222
- ];
2223
- })(), (0, react.createElement)("textarea", {
2224
- "aria-label": "systemPrompt",
2225
- placeholder: "systemPrompt",
2226
- rows: 2,
2227
- value: advisor.systemPrompt,
2228
- onChange: (e) => updateAdvisor(index, { systemPrompt: e.target.value }),
2866
+ }, copiedId === sessionId ? "已复制" : "复制姓名");
2867
+ const fields = (sessionId, draft, dirty, label, onSave) => (0, react.createElement)("div", { style: corpsFieldsStyle }, (0, react.createElement)("div", null, (0, react.createElement)("label", { style: corpsLabelStyle }, "身份姓名"), (0, react.createElement)("input", {
2868
+ value: draft.name,
2869
+ placeholder: "例如:老王",
2870
+ maxLength: 40,
2871
+ onChange: (event) => setDrafts((prev) => ({
2872
+ ...prev,
2873
+ [sessionId]: {
2874
+ ...draft,
2875
+ name: event.target.value
2876
+ }
2877
+ })),
2878
+ style: corpsInputStyle
2879
+ })), (0, react.createElement)("div", null, (0, react.createElement)("label", { style: corpsLabelStyle }, "主要负责"), (0, react.createElement)("input", {
2880
+ value: draft.duty,
2881
+ placeholder: "例如:架构评审与代码质量",
2882
+ maxLength: 200,
2883
+ onChange: (event) => setDrafts((prev) => ({
2884
+ ...prev,
2885
+ [sessionId]: {
2886
+ ...draft,
2887
+ duty: event.target.value
2888
+ }
2889
+ })),
2890
+ style: corpsInputStyle
2891
+ })), (0, react.createElement)("button", {
2892
+ type: "button",
2893
+ disabled: saving === sessionId || batchSaving || !dirty,
2894
+ onClick: onSave,
2229
2895
  style: {
2230
- ...settingsInputStyle,
2231
- marginTop: 4,
2232
- resize: "vertical"
2896
+ ...corpsButtonStyle,
2897
+ cursor: dirty ? "pointer" : "default",
2898
+ opacity: dirty ? 1 : .55
2233
2899
  }
2234
- }), (() => {
2235
- const supportsTools = !providers.some((p) => p.id === advisor.provider && p.kind === "llm") || Boolean(advisor.baseURL || advisor.apiKeyEnv);
2236
- const globalDefault = config.discussion.advisorTools ?? "readonly";
2237
- const mode = advisor.tools ?? globalDefault;
2238
- const modeLabel = {
2239
- readonly: "只读工具",
2240
- all: "全部会话工具(含可写,慎用)",
2241
- off: "关闭"
2242
- };
2243
- const toolPromptNote = "工具启用时,系统提示词会自动追加:『对于你不了解、不熟悉的内容,尤其是项目背景、代码细节、仓库状态等不在知识范围内的情况,请优先使用联网工具(如 web_search、web_fetch)搜索核实后再作答,不要编造未核实的事实。』";
2244
- if (supportsTools) return [
2245
- label(`顾问工具调用范围(未单独设置时跟随全局默认:${modeLabel[globalDefault] ?? globalDefault})`),
2246
- (0, react.createElement)("select", {
2247
- "aria-label": "顾问工具调用范围",
2248
- value: mode,
2249
- onChange: (e) => updateAdvisor(index, { tools: e.target.value }),
2250
- style: settingsInputStyle
2251
- }, (0, react.createElement)("option", { value: "readonly" }, "只读工具"), (0, react.createElement)("option", { value: "all" }, "全部会话工具(含可写,慎用)"), (0, react.createElement)("option", { value: "off" }, "关闭")),
2252
- mode === "off" ? null : (0, react.createElement)("div", { style: {
2253
- fontSize: 12,
2254
- color: "var(--dsh-color-muted, #8b90a0)",
2255
- border: "1px dashed var(--dsh-color-border, #3a3f4b)",
2256
- borderRadius: 4,
2257
- padding: "4px 6px",
2258
- marginTop: 4
2259
- } }, toolPromptNote)
2260
- ];
2261
- return [label("顾问工具调用范围"), (0, react.createElement)("div", { style: {
2262
- fontSize: 12,
2263
- color: "var(--dsh-color-muted, #8b90a0)",
2264
- border: "1px solid var(--dsh-color-border, #3a3f4b)",
2265
- borderRadius: 4,
2266
- padding: "4px 6px"
2267
- } }, "❌ 当前为 DSH 内置通道(复用 DSH 凭据),不支持工具调用;为其配置 API 地址 / Key / 环境变量以走直连通道后即可启用(工具指引提示词也会随之生效)。")];
2268
- })(), label("思考模式 / 思考强度(未设置 = 跟随模型与供应商默认)"), (0, react.createElement)("div", { style: {
2900
+ }, saving === sessionId ? "保存中…" : label));
2901
+ return (0, react.createElement)("div", { style: corpsRootStyle }, (0, react.createElement)("div", { style: corpsHeaderStyle }, (0, react.createElement)("div", null, (0, react.createElement)("h2", { style: corpsTitleStyle }, "顾问团"), (0, react.createElement)("p", { style: corpsDescStyle }, "角色卡对整个 DSH 全局生效。给每个可跨会话对话的会话分配一个身份姓名和职责;之后 ask_session 可以直接用姓名找它,原来的会话标题、目录名和 sessionId 仍然可用。生成时使用当前打开会话的主模型,只补还没有卡的会话,已有卡不会被覆盖。改完可以逐条保存,也可以一次保存全部更改。")), (0, react.createElement)("div", { style: {
2269
2902
  display: "flex",
2270
2903
  gap: 8,
2271
- marginTop: 4
2272
- } }, (0, react.createElement)("select", {
2273
- "aria-label": "思考模式",
2274
- value: advisor.thinking ?? "",
2275
- onChange: (e) => updateAdvisor(index, { thinking: e.target.value === "" ? void 0 : e.target.value }),
2276
- style: settingsInputStyle
2277
- }, (0, react.createElement)("option", { value: "" }, "思考:默认"), (0, react.createElement)("option", { value: "enabled" }, "思考:开启"), (0, react.createElement)("option", { value: "disabled" }, "思考:关闭")), (0, react.createElement)("select", {
2278
- "aria-label": "思考强度",
2279
- value: advisor.reasoningEffort ?? "",
2280
- onChange: (e) => updateAdvisor(index, { reasoningEffort: e.target.value === "" ? void 0 : e.target.value }),
2281
- style: settingsInputStyle
2282
- }, (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: {
2283
- display: "flex",
2284
- gap: 8,
2285
- marginTop: 4
2286
- } }, (0, react.createElement)("input", {
2287
- type: "number",
2288
- placeholder: "temperature",
2289
- value: advisor.temperature ?? "",
2290
- onChange: (e) => updateAdvisor(index, { temperature: e.target.value === "" ? void 0 : Number(e.target.value) }),
2291
- style: {
2292
- ...settingsInputStyle,
2293
- width: 90
2294
- }
2295
- }), (0, react.createElement)("input", {
2296
- type: "number",
2297
- placeholder: "maxTokens",
2298
- value: advisor.maxTokens ?? "",
2299
- onChange: (e) => updateAdvisor(index, { maxTokens: e.target.value === "" ? void 0 : Number(e.target.value) }),
2300
- style: {
2301
- ...settingsInputStyle,
2302
- width: 120
2303
- }
2304
- }))))), (0, react.createElement)("div", { style: settingsCardFooterStyle }, msg ? (0, react.createElement)("span", { style: {
2305
- flex: 1,
2306
- minWidth: 0,
2307
- color: "#16a34a",
2308
- fontSize: 12
2309
- } }, msg) : null, error ? (0, react.createElement)("span", { style: {
2310
- flex: 1,
2311
- minWidth: 0,
2312
- color: "#e11d48",
2313
- fontSize: 12
2314
- } }, error) : null, (0, react.createElement)("button", {
2904
+ flexShrink: 0,
2905
+ flexWrap: "wrap"
2906
+ } }, (0, react.createElement)("button", {
2315
2907
  type: "button",
2316
- disabled: !dirty || saving,
2317
- onClick: () => void reload(),
2908
+ disabled: batchSaving || generating || dirtyCount === 0,
2909
+ onClick: () => void saveAll(),
2318
2910
  style: {
2319
- ...settingsFooterButtonStyle,
2320
- border: "1px solid var(--dsw-alias-border-l2, var(--dsh-color-border, #3a3f4b))",
2321
- background: "transparent",
2322
- color: "inherit"
2911
+ ...corpsButtonStyle,
2912
+ background: dirtyCount > 0 ? "var(--dsw-alias-label-primary, #e6e9f0)" : "transparent",
2913
+ color: dirtyCount > 0 ? "var(--dsw-alias-bg-layer-3, #151821)" : "inherit",
2914
+ opacity: dirtyCount > 0 ? 1 : .55
2323
2915
  }
2324
- }, "放弃更改"), (0, react.createElement)("button", {
2916
+ }, batchSaving ? `保存中 ${batchProgress}` : `保存全部更改${dirtyCount > 0 ? `(${dirtyCount})` : ""}`), (0, react.createElement)("button", {
2325
2917
  type: "button",
2326
- disabled: !dirty || saving,
2327
- onClick: () => void save(),
2918
+ disabled: generating,
2919
+ onClick: () => void generate(),
2328
2920
  style: {
2329
- ...settingsFooterButtonStyle,
2330
- background: "var(--dsw-alias-label-primary, #e6e9f0)",
2331
- color: "var(--dsw-alias-bg-layer-3, #151821)"
2921
+ ...corpsButtonStyle,
2922
+ borderColor: "var(--dsw-alias-label-primary, #e6e9f0)"
2332
2923
  }
2333
- }, saving ? "保存中…" : "保存设置")))) : null);
2924
+ }, generating ? "生成中…" : "用主模型生成"), (0, react.createElement)("button", {
2925
+ type: "button",
2926
+ onClick: () => void load(),
2927
+ style: corpsButtonStyle
2928
+ }, "刷新"))), error.length > 0 ? (0, react.createElement)("div", { style: {
2929
+ ...corpsNoticeStyle,
2930
+ color: "#e11d48"
2931
+ } }, error) : null, message.length > 0 ? (0, react.createElement)("div", { style: {
2932
+ ...corpsNoticeStyle,
2933
+ color: "#16a34a"
2934
+ } }, message) : null, data === null ? (0, react.createElement)("div", { style: corpsDescStyle }, "加载中…") : data.note !== void 0 ? (0, react.createElement)("div", { style: corpsNoticeStyle }, data.note) : null, data !== null && data.filtered === false ? (0, react.createElement)("div", { style: corpsNoticeStyle }, "当前宿主未提供工作区登记信息,名单未按侧栏可见范围过滤。") : null, data !== null && data.sessions.length === 0 ? (0, react.createElement)("div", { style: corpsDescStyle }, "没有可见的会话。") : null, data !== null ? data.sessions.map((row) => {
2935
+ const draft = draftOf(row.sessionId, {
2936
+ name: row.card?.name ?? "",
2937
+ duty: row.card?.duty ?? ""
2938
+ });
2939
+ const dirty = draft.name.trim() !== (row.card?.name ?? "") || draft.duty.trim() !== (row.card?.duty ?? "");
2940
+ const assigned = row.card !== null;
2941
+ const rowLabel = draft.name.trim() === "" ? assigned ? "移除" : "加入顾问团" : "保存";
2942
+ return (0, react.createElement)("div", {
2943
+ key: row.sessionId,
2944
+ style: corpsCardStyle
2945
+ }, (0, react.createElement)("div", { style: corpsRowHeadStyle }, (0, react.createElement)("span", { style: corpsNameStyle }, row.name), row.running ? (0, react.createElement)("span", { style: corpsBadgeStyle("#16a34a") }, "运行中") : null, row.subagent ? (0, react.createElement)("span", { style: corpsBadgeStyle("#8b90a0") }, "子代理") : null, (0, react.createElement)("span", { style: corpsBadgeStyle(assigned ? "#3b82f6" : "#8b90a0") }, assigned ? `顾问团 · ${row.card?.name}` : "未分配"), copyButton(row.sessionId, draft.name)), (0, react.createElement)("div", { style: corpsMetaStyle }, `${row.cwd ?? "(未知目录)"} · ${row.sessionId}`), fields(row.sessionId, draft, dirty, rowLabel, () => save(row.sessionId)));
2946
+ }) : null, data !== null && data.orphans.length > 0 ? (0, react.createElement)("div", null, (0, react.createElement)("div", { style: corpsSectionTitleStyle }, `未挂接的角色卡(${data.orphans.length})`), (0, react.createElement)("div", { style: corpsDescStyle }, "这些卡片对应的会话已归档、被排除、离开工作区或已不存在;可以删除,或保留给以后重新出现的会话用。"), data.orphans.map((row) => {
2947
+ const draft = draftOf(row.sessionId, {
2948
+ name: row.name,
2949
+ duty: row.duty
2950
+ });
2951
+ const dirty = draft.name.trim() !== row.name || draft.duty.trim() !== row.duty;
2952
+ return (0, react.createElement)("div", {
2953
+ key: row.sessionId,
2954
+ style: corpsCardStyle
2955
+ }, (0, react.createElement)("div", { style: corpsRowHeadStyle }, (0, react.createElement)("span", { style: corpsNameStyle }, row.name), (0, react.createElement)("span", { style: corpsBadgeStyle("#f59e0b") }, "未挂接"), copyButton(row.sessionId, draft.name)), (0, react.createElement)("div", { style: corpsMetaStyle }, row.sessionId), fields(row.sessionId, draft, dirty, draft.name.trim() === "" ? "移除" : "保存", () => save(row.sessionId)));
2956
+ })) : null);
2334
2957
  }
2335
2958
  const name = "dsh-advisor-group";
2336
2959
  const inject = [
@@ -2351,6 +2974,12 @@ function apply(ctx) {
2351
2974
  order: 0,
2352
2975
  label: "顾问群"
2353
2976
  }, AdvisorGroupSettingsTab));
2977
+ ctx.slots.inject("conversation.view", () => ctx.slots.register({
2978
+ name: "conversation.view",
2979
+ id: "advisor-corps",
2980
+ order: 30,
2981
+ label: "顾问团"
2982
+ }, AdvisorCorpsView));
2354
2983
  }
2355
2984
 
2356
2985
  //#endregion
@@ -2360,6 +2989,7 @@ exports.buildStepSequence = buildStepSequence;
2360
2989
  exports.buildToolStepViews = buildToolStepViews;
2361
2990
  exports.extractActionDescription = extractActionDescription;
2362
2991
  exports.inject = inject;
2992
+ exports.mergeLiveOverlay = mergeLiveOverlay;
2363
2993
  exports.name = name;
2364
2994
  exports.sanitizeClientContent = sanitizeClientContent;
2365
2995
  return module.exports; } });