@xmanrui/dsh-im 4.10.0 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/PROACTIVE_DELIVERY.en.md +15 -1
  2. package/PROACTIVE_DELIVERY.md +15 -1
  3. package/README.en.md +5 -1
  4. package/README.md +6 -2
  5. package/lib/client.js +1619 -769
  6. package/lib/index.js +248 -244
  7. package/package.json +4 -3
  8. package/plugin-src/client/channel-card-meta.js +9 -1
  9. package/plugin-src/client/channels/dingtalk/api.js +4 -0
  10. package/plugin-src/client/channels/dingtalk/index.js +25 -2
  11. package/plugin-src/client/channels/feishu/api.js +4 -0
  12. package/plugin-src/client/channels/feishu/group-settings.js +581 -0
  13. package/plugin-src/client/channels/feishu/index.js +26 -204
  14. package/plugin-src/client/channels/qq/api.js +4 -0
  15. package/plugin-src/client/channels/qq/index.js +25 -2
  16. package/plugin-src/client/channels/shared/token-api.js +4 -0
  17. package/plugin-src/client/channels/shared/token-channel.js +25 -3
  18. package/plugin-src/client/channels/wecom/api.js +4 -0
  19. package/plugin-src/client/channels/wecom/index.js +25 -2
  20. package/plugin-src/client/channels/weixin/api.js +4 -0
  21. package/plugin-src/client/channels/weixin/index.js +25 -2
  22. package/plugin-src/client/channels/whatsapp/api.js +4 -0
  23. package/plugin-src/client/channels/whatsapp/index.js +24 -2
  24. package/plugin-src/client/delivery-settings.js +73 -5
  25. package/plugin-src/client/i18n.js +20 -0
  26. package/plugin-src/client/model-setting.js +104 -0
  27. package/plugin-src/client/styles.js +50 -0
  28. package/plugin-src/host/channels/dingtalk/production.mjs +3 -0
  29. package/plugin-src/host/channels/dingtalk/rpc.mjs +11 -0
  30. package/plugin-src/host/channels/feishu/production.mjs +3 -0
  31. package/plugin-src/host/channels/feishu/rpc.mjs +16 -0
  32. package/plugin-src/host/channels/qq/production.mjs +3 -0
  33. package/plugin-src/host/channels/qq/rpc.mjs +11 -0
  34. package/plugin-src/host/channels/shared/model-setting-rpc.mjs +19 -0
  35. package/plugin-src/host/channels/shared/production.mjs +3 -0
  36. package/plugin-src/host/channels/shared/rpc.mjs +9 -0
  37. package/plugin-src/host/channels/shared/workspace-rpc.mjs +2 -0
  38. package/plugin-src/host/channels/slack/production.mjs +3 -0
  39. package/plugin-src/host/channels/slack/rpc.mjs +9 -0
  40. package/plugin-src/host/channels/wecom/production.mjs +3 -0
  41. package/plugin-src/host/channels/wecom/rpc.mjs +11 -0
  42. package/plugin-src/host/channels/weixin/production.mjs +3 -0
  43. package/plugin-src/host/channels/weixin/rpc.mjs +11 -0
  44. package/plugin-src/host/channels/whatsapp/production.mjs +3 -0
  45. package/plugin-src/host/channels/whatsapp/rpc.mjs +11 -0
  46. package/plugin-src/host/delivery-adapter.mjs +141 -3
  47. package/plugin-src/host/delivery-rpc.mjs +12 -0
  48. package/plugin-src/host/delivery-service.mjs +110 -3
  49. package/plugin-src/host/delivery-suggestions.mjs +46 -0
  50. package/plugin-src/host/harness-session-coordinator.mjs +6 -5
  51. package/plugin-src/host/index.mjs +18 -1
  52. package/plugin-src/host/session-sync-coordinator.mjs +217 -0
  53. package/scripts/verify-package.mjs +12 -6
  54. package/src/channels/shared/bot-workspace-store.mjs +285 -38
  55. package/src/channels/shared/harness-client.mjs +124 -43
  56. package/src/channels/shared/model-setting.mjs +110 -0
@@ -8,7 +8,6 @@ import {
8
8
  FEISHU_REGISTRATION_OPERATIONS,
9
9
  formatRemaining,
10
10
  normalizeBotsSnapshot,
11
- normalizeGroupResponseMode,
12
11
  normalizePollResult,
13
12
  normalizeProvisioning,
14
13
  presentError,
@@ -22,6 +21,11 @@ import {
22
21
  AgentPresetEditor,
23
22
  EMPTY_AGENT_PRESET_CATALOG,
24
23
  } from "../../agent-preset.js";
24
+ import {
25
+ EMPTY_MODEL_CATALOG,
26
+ ModelCatalogContext,
27
+ ModelEditor,
28
+ } from "../../model-setting.js";
25
29
  import { useWorkspaceSnapshotFence } from "../../workspace-snapshot-fence.js";
26
30
  import {
27
31
  BotSettingsButton,
@@ -459,133 +463,6 @@ function RemoveConfirmation({ bot, busy, onConfirm, onCancel }) {
459
463
  );
460
464
  }
461
465
 
462
- function GroupResponseModeEditor({
463
- value,
464
- permissionGranted = false,
465
- disabled = false,
466
- authorizationDisabled = false,
467
- onSave,
468
- onAuthorize,
469
- }) {
470
- const current = normalizeGroupResponseMode(value);
471
- const [saving, setSaving] = React.useState(false);
472
- const [authorizing, setAuthorizing] = React.useState(false);
473
- const [error, setError] = React.useState(null);
474
-
475
- const change = async (event) => {
476
- const next = normalizeGroupResponseMode(event.target.value);
477
- if (next === current || saving || disabled) return;
478
- setSaving(true);
479
- setError(null);
480
- try {
481
- await onSave?.(next);
482
- } catch (cause) {
483
- setError(cause?.message ?? "群聊响应方式修改失败,请重试。");
484
- } finally {
485
- setSaving(false);
486
- }
487
- };
488
-
489
- const authorize = async () => {
490
- if (current !== "all" || saving || authorizing || disabled || authorizationDisabled) return;
491
- setAuthorizing(true);
492
- setError(null);
493
- try {
494
- await onAuthorize?.();
495
- } catch (cause) {
496
- setError(cause?.message ?? "群消息权限授权失败,请重试。");
497
- } finally {
498
- setAuthorizing(false);
499
- }
500
- };
501
-
502
- return h("div", { className: "bxf-responseMode dim-responseMode" },
503
- h("div", { className: "bxf-responseModeHeader dim-responseModeHeader" },
504
- h("span", null, "群聊响应方式"),
505
- saving || authorizing
506
- ? h("span", { className: "bxf-responseModeStatus dim-responseModeStatus" },
507
- saving ? "保存中…" : "正在准备授权…")
508
- : null),
509
- h("select", {
510
- className: "bxf-responseModeSelect dim-responseModeSelect",
511
- value: current,
512
- disabled: disabled || saving,
513
- "aria-label": "群聊响应方式",
514
- onChange: (event) => { void change(event); },
515
- },
516
- h("option", { value: "mention" }, "仅在 @机器人时响应(推荐)"),
517
- h("option", { value: "all" }, "响应所有群消息"),
518
- ),
519
- h("small", { className: "bxf-responseModeHelp dim-responseModeHelp" },
520
- current === "mention"
521
- ? permissionGranted
522
- ? "私聊始终响应;群聊仅处理明确 @当前机器人的消息。群消息权限已开通,再次切换无需授权。"
523
- : "私聊始终响应;群聊仅处理明确 @当前机器人的消息。选择全部消息后会打开飞书官方授权流程。"
524
- : permissionGranted
525
- ? "已开通“获取群组中所有消息”权限(im:message.group_msg);机器人会处理群聊中的所有可见消息。"
526
- : "尚未确认“获取群组中所有消息”权限,请完成飞书授权。"),
527
- current === "all"
528
- ? h("div", { className: "bxf-responseModePermissionAction dim-responseModePermissionAction" },
529
- h(Button, {
530
- className: "bxf-responseModePermissionButton",
531
- size: "small",
532
- disabled: disabled || authorizationDisabled || saving || authorizing,
533
- "aria-busy": authorizing ? "true" : undefined,
534
- "aria-label": permissionGranted ? "重新授权群消息权限" : "授权群消息权限",
535
- onClick: () => { void authorize(); },
536
- }, authorizing ? "正在准备…" : permissionGranted ? "重新授权" : "去授权"))
537
- : null,
538
- error ? h("p", {
539
- className: "bxf-responseModeError dim-responseModeError",
540
- role: "alert",
541
- }, error) : null,
542
- );
543
- }
544
-
545
- function GroupTopicReplyEditor({ value = false, disabled = false, onSave }) {
546
- const current = value === true ? "on" : "off";
547
- const [saving, setSaving] = React.useState(false);
548
- const [error, setError] = React.useState(null);
549
-
550
- const change = async (event) => {
551
- const next = event.target.value === "on";
552
- if ((next ? "on" : "off") === current || saving || disabled) return;
553
- setSaving(true);
554
- setError(null);
555
- try {
556
- await onSave?.(next);
557
- } catch (cause) {
558
- setError(cause?.message ?? "群聊以话题方式回复设置保存失败,请重试。");
559
- } finally {
560
- setSaving(false);
561
- }
562
- };
563
-
564
- return h("div", { className: "bxf-responseMode dim-responseMode" },
565
- h("div", { className: "bxf-responseModeHeader dim-responseModeHeader" },
566
- h("span", null, "群聊以话题方式回复"),
567
- saving
568
- ? h("span", { className: "bxf-responseModeStatus dim-responseModeStatus" }, "保存中…")
569
- : null),
570
- h("select", {
571
- className: "bxf-responseModeSelect dim-responseModeSelect",
572
- value: current,
573
- disabled: disabled || saving,
574
- "aria-label": "群聊以话题方式回复",
575
- onChange: (event) => { void change(event); },
576
- },
577
- h("option", { value: "off" }, "关闭(群内直接回复)"),
578
- h("option", { value: "on" }, "开启(自动开启独立飞书话题)"),
579
- ),
580
- h("small", { className: "bxf-responseModeHelp dim-responseModeHelp" },
581
- "开启后,群聊中向机器人提问会自动开启独立飞书话题,回复落在话题内;每个话题是 dsh 会话列表里一条独立会话,上下文互不串。私聊不受影响。"),
582
- error ? h("p", {
583
- className: "bxf-responseModeError dim-responseModeError",
584
- role: "alert",
585
- }, error) : null,
586
- );
587
- }
588
-
589
466
  export function BotCard({
590
467
  connection,
591
468
  busy,
@@ -598,11 +475,9 @@ export function BotCard({
598
475
  onReconnect,
599
476
  onRepairCallback,
600
477
  onWorkspaceSave,
478
+ onModelSave,
601
479
  onAgentPresetSave,
602
480
  onContextEnhancementSave,
603
- onGroupResponseModeSave,
604
- onGroupTopicReplySave,
605
- onGroupMessagePermissionAuthorize,
606
481
  onRequestRemove,
607
482
  onConfirmRemove,
608
483
  onCancelRemove,
@@ -655,6 +530,11 @@ export function BotCard({
655
530
  botName: bot.name,
656
531
  connected,
657
532
  accessPolicy: connection.accessPolicy,
533
+ channelSettings: {
534
+ groupResponseMode: connection.groupResponseMode,
535
+ groupTopicReply: connection.groupTopicReply,
536
+ groupMessagePermissionGranted: connection.groupMessagePermissionGranted,
537
+ },
658
538
  })),
659
539
  ),
660
540
  h(WorkspaceEditor, {
@@ -662,6 +542,11 @@ export function BotCard({
662
542
  disabled: Boolean(busy),
663
543
  onSave: onWorkspaceSave,
664
544
  }),
545
+ h(ModelEditor, {
546
+ model: connection.model,
547
+ disabled: Boolean(busy),
548
+ onSave: onModelSave,
549
+ }),
665
550
  h(AgentPresetEditor, {
666
551
  agentPreset: connection.agentPreset,
667
552
  disabled: Boolean(busy),
@@ -672,19 +557,6 @@ export function BotCard({
672
557
  disabled: Boolean(busy),
673
558
  onSave: onContextEnhancementSave,
674
559
  }),
675
- h(GroupResponseModeEditor, {
676
- value: connection.groupResponseMode,
677
- permissionGranted: connection.groupMessagePermissionGranted,
678
- disabled: Boolean(busy),
679
- authorizationDisabled: repairDisabled,
680
- onSave: onGroupResponseModeSave,
681
- onAuthorize: onGroupMessagePermissionAuthorize,
682
- }),
683
- h(GroupTopicReplyEditor, {
684
- value: connection.groupTopicReply,
685
- disabled: Boolean(busy),
686
- onSave: onGroupTopicReplySave,
687
- }),
688
560
  provisionContent
689
561
  ? h("section", {
690
562
  className: "bxf-botProvision dim-botProvision",
@@ -773,11 +645,9 @@ function BotList(props) {
773
645
  onReconnect: () => props.onReconnect(bot),
774
646
  onRepairCallback: () => props.onRepairCallback(bot),
775
647
  onWorkspaceSave: (workspace) => props.onWorkspaceSave(bot, workspace),
648
+ onModelSave: (model) => props.onModelSave(bot, model),
776
649
  onAgentPresetSave: (agentPreset) => props.onAgentPresetSave(bot, agentPreset),
777
650
  onContextEnhancementSave: (config) => props.onContextEnhancementSave(bot, config),
778
- onGroupResponseModeSave: (groupResponseMode) => props.onGroupResponseModeSave(bot, groupResponseMode),
779
- onGroupTopicReplySave: (groupTopicReply) => props.onGroupTopicReplySave(bot, groupTopicReply),
780
- onGroupMessagePermissionAuthorize: () => props.onGroupMessagePermissionAuthorize(bot),
781
651
  onRequestRemove: () => props.onRequestRemove(bot),
782
652
  onConfirmRemove: () => props.onConfirmRemove(bot),
783
653
  onCancelRemove: props.onCancelRemove,
@@ -830,6 +700,7 @@ export function mergeFeishuSnapshotState(
830
700
  pageError: null,
831
701
  statusError: null,
832
702
  agentPresetCatalog: snapshot.agentPresetCatalog ?? current.agentPresetCatalog,
703
+ modelCatalog: snapshot.modelCatalog ?? current.modelCatalog,
833
704
  };
834
705
  }
835
706
 
@@ -843,6 +714,7 @@ export function FeishuSettingsTab({ rpcCall }) {
843
714
  pageError: null,
844
715
  statusError: null,
845
716
  agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
717
+ modelCatalog: EMPTY_MODEL_CATALOG,
846
718
  });
847
719
  const [pageBusy, setPageBusy] = React.useState(false);
848
720
  const [provisionBusy, setProvisionBusy] = React.useState(false);
@@ -1384,56 +1256,6 @@ export function FeishuSettingsTab({ rpcCall }) {
1384
1256
  }
1385
1257
  }, [invoke, loadStatus, mergeSnapshot, setBotBusy, setBotError, workspaceFence]);
1386
1258
 
1387
- const authorizeGroupMessages = React.useCallback(async (connection) => {
1388
- const { botId } = connection;
1389
- if (model.provisioning) {
1390
- throw new Error("请先完成当前飞书授权操作,再开通群消息权限。");
1391
- }
1392
- setRemoveTargetId(null);
1393
- setBotError(botId, null);
1394
- setTestNoticesByBot((current) => {
1395
- const next = { ...current };
1396
- delete next[botId];
1397
- return next;
1398
- });
1399
- await startProvisioning({
1400
- operation: GROUP_MESSAGE_PERMISSION_OPERATION,
1401
- bot: connection,
1402
- });
1403
- }, [model.provisioning, setBotError, startProvisioning]);
1404
-
1405
- const saveGroupResponseMode = React.useCallback(async (connection, groupResponseMode) => {
1406
- const { botId } = connection;
1407
- if (groupResponseMode === "all" && connection.groupMessagePermissionGranted !== true) {
1408
- await authorizeGroupMessages(connection);
1409
- return;
1410
- }
1411
- const snapshotVersion = workspaceFence.beginMutation();
1412
- setBotBusy(botId, "group-response-mode");
1413
- setBotError(botId, null);
1414
- try {
1415
- const snapshot = normalizeBotsSnapshot(await invoke(
1416
- FEISHU_ENDPOINTS.setGroupResponseMode,
1417
- { botId, groupResponseMode },
1418
- ));
1419
- if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
1420
- mergeSnapshot(snapshot);
1421
- }
1422
- } finally {
1423
- const shouldRefresh = workspaceFence.endMutation();
1424
- if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
1425
- if (mountedRef.current) setBotBusy(botId, null);
1426
- }
1427
- }, [
1428
- invoke,
1429
- authorizeGroupMessages,
1430
- loadStatus,
1431
- mergeSnapshot,
1432
- setBotBusy,
1433
- setBotError,
1434
- workspaceFence,
1435
- ]);
1436
-
1437
1259
  const requestRemove = React.useCallback((connection) => {
1438
1260
  setRemoveTargetId(connection.botId);
1439
1261
  }, []);
@@ -1541,7 +1363,9 @@ export function FeishuSettingsTab({ rpcCall }) {
1541
1363
  else removeButtonRefs.current.delete(botId);
1542
1364
  }, []);
1543
1365
 
1544
- return h(AgentPresetCatalogContext.Provider, {
1366
+ return h(ModelCatalogContext.Provider, {
1367
+ value: model.modelCatalog ?? EMPTY_MODEL_CATALOG,
1368
+ }, h(AgentPresetCatalogContext.Provider, {
1545
1369
  value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
1546
1370
  }, h("section", { className: "bxf-page dim-channelPage", "aria-label": "飞书机器人设置" },
1547
1371
  h(Heading, {
@@ -1589,17 +1413,15 @@ export function FeishuSettingsTab({ rpcCall }) {
1589
1413
  onReconnect: (bot) => void reconnectOneBot(bot),
1590
1414
  onRepairCallback: repairCallback,
1591
1415
  onWorkspaceSave: saveWorkspace,
1416
+ onModelSave: (connection, selectedModel) => saveBotSetting(
1417
+ connection, "model", FEISHU_ENDPOINTS.setModel, { model: selectedModel },
1418
+ ),
1592
1419
  onAgentPresetSave: (connection, agentPreset) => saveBotSetting(
1593
1420
  connection, "preset", FEISHU_ENDPOINTS.setAgentPreset, { agentPreset },
1594
1421
  ),
1595
1422
  onContextEnhancementSave: (connection, config) => saveBotSetting(
1596
1423
  connection, "context-enhancement", FEISHU_ENDPOINTS.setContextEnhancement, { config },
1597
1424
  ),
1598
- onGroupResponseModeSave: saveGroupResponseMode,
1599
- onGroupTopicReplySave: (connection, groupTopicReply) => saveBotSetting(
1600
- connection, "group-topic-reply", FEISHU_ENDPOINTS.setGroupTopicReply, { groupTopicReply },
1601
- ),
1602
- onGroupMessagePermissionAuthorize: authorizeGroupMessages,
1603
1425
  onRequestRemove: requestRemove,
1604
1426
  onConfirmRemove: (bot) => void confirmRemove(bot),
1605
1427
  onCancelRemove: cancelRemove,
@@ -1608,5 +1430,5 @@ export function FeishuSettingsTab({ rpcCall }) {
1608
1430
  })
1609
1431
  : null,
1610
1432
  ),
1611
- ));
1433
+ )));
1612
1434
  }
@@ -1,4 +1,5 @@
1
1
  import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
2
+ import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
2
3
  import { normalizeLastMessageError } from '../../last-message-error.js';
3
4
  import { normalizeAccessPolicy } from '../../../../src/channels/shared/access-policy.mjs';
4
5
  import { normalizeContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
@@ -14,6 +15,7 @@ export const QQ_ENDPOINTS = Object.freeze({
14
15
  reconnectBot: 'bot.reconnect',
15
16
  deleteBot: 'bot.delete',
16
17
  setWorkspace: 'bot.workspace.set',
18
+ setModel: SET_MODEL_ENDPOINT,
17
19
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
18
20
  setContextEnhancement: 'bot.context-enhancement.set',
19
21
  setAccessPolicy: 'bot.access-policy.set',
@@ -90,6 +92,7 @@ function normalizeBot(value) {
90
92
  connected,
91
93
  state: connected ? 'connected' : state,
92
94
  workspace: text(value.workspace, '', 4_096),
95
+ model: normalizeModelSelection(value.model),
93
96
  agentPreset: normalizeAgentPresetId(value.agentPreset),
94
97
  contextEnhancement: normalizeContextEnhancementConfig(value.contextEnhancement),
95
98
  ...(Object.hasOwn(value, 'accessPolicy')
@@ -132,6 +135,7 @@ export function normalizeSnapshot(value) {
132
135
  totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
133
136
  provisioning: source.provisioning ? normalizeProvisioning(source.provisioning) : null,
134
137
  agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog),
138
+ modelCatalog: normalizeModelCatalog(source.modelCatalog),
135
139
  ...(testMessage ? { testMessage } : {}),
136
140
  };
137
141
  }
@@ -10,6 +10,11 @@ import {
10
10
  AgentPresetEditor,
11
11
  EMPTY_AGENT_PRESET_CATALOG,
12
12
  } from '../../agent-preset.js';
13
+ import {
14
+ EMPTY_MODEL_CATALOG,
15
+ ModelCatalogContext,
16
+ ModelEditor,
17
+ } from '../../model-setting.js';
13
18
  import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
14
19
  import {
15
20
  BotSettingsButton,
@@ -167,6 +172,7 @@ export function AccountCard({
167
172
  removing,
168
173
  onReconnect,
169
174
  onWorkspaceSave,
175
+ onModelSave,
170
176
  onAgentPresetSave,
171
177
  onContextEnhancementSave,
172
178
  onRequestRemove,
@@ -204,6 +210,11 @@ export function AccountCard({
204
210
  disabled: Boolean(busy),
205
211
  onSave: onWorkspaceSave,
206
212
  }),
213
+ h(ModelEditor, {
214
+ model: account.model,
215
+ disabled: Boolean(busy),
216
+ onSave: onModelSave,
217
+ }),
207
218
  h(AgentPresetEditor, {
208
219
  agentPreset: account.agentPreset,
209
220
  disabled: Boolean(busy),
@@ -238,6 +249,7 @@ export function QqSettingsTab({ rpcCall }) {
238
249
  const [model, setModel] = React.useState({
239
250
  phase: 'loading', bots: [], totals: { configured: 0, connected: 0 }, error: null,
240
251
  agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
252
+ modelCatalog: EMPTY_MODEL_CATALOG,
241
253
  });
242
254
  const [provision, setProvision] = React.useState(null);
243
255
  const [busy, setBusy] = React.useState(false);
@@ -278,6 +290,7 @@ export function QqSettingsTab({ rpcCall }) {
278
290
  setModel({
279
291
  phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
280
292
  agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
293
+ modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
281
294
  });
282
295
  if (restore && snapshot.provisioning) setProvision({
283
296
  ...snapshot.provisioning,
@@ -345,6 +358,7 @@ export function QqSettingsTab({ rpcCall }) {
345
358
  setModel({
346
359
  phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
347
360
  agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
361
+ modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
348
362
  });
349
363
  }
350
364
  setCredentialOpen(false);
@@ -408,6 +422,7 @@ export function QqSettingsTab({ rpcCall }) {
408
422
  setModel({
409
423
  phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
410
424
  agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
425
+ modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
411
426
  });
412
427
  }
413
428
  return snapshot;
@@ -478,6 +493,12 @@ export function QqSettingsTab({ rpcCall }) {
478
493
  QQ_ENDPOINTS.setWorkspace,
479
494
  { botId: account.botId, workspace },
480
495
  ),
496
+ onModelSave: (selectedModel) => botAction(
497
+ account,
498
+ 'model',
499
+ QQ_ENDPOINTS.setModel,
500
+ { botId: account.botId, model: selectedModel },
501
+ ),
481
502
  onAgentPresetSave: (agentPreset) => botAction(
482
503
  account,
483
504
  'preset',
@@ -513,7 +534,9 @@ export function QqSettingsTab({ rpcCall }) {
513
534
  })
514
535
  : null;
515
536
 
516
- return h(AgentPresetCatalogContext.Provider, {
537
+ return h(ModelCatalogContext.Provider, {
538
+ value: model.modelCatalog ?? EMPTY_MODEL_CATALOG,
539
+ }, h(AgentPresetCatalogContext.Provider, {
517
540
  value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
518
541
  }, h('section', { className: 'ddt-page dqq-page dim-channelPage', 'aria-label': 'QQ 设置' },
519
542
  h(Heading, {
@@ -533,5 +556,5 @@ export function QqSettingsTab({ rpcCall }) {
533
556
  provisionView,
534
557
  model.bots.length === 0 && !provision && !credentialOpen
535
558
  ? h(EmptyView, { busy, onStart: () => void startProvisioning() }) : null,
536
- botList)));
559
+ botList))));
537
560
  }
@@ -1,4 +1,5 @@
1
1
  import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
2
+ import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
2
3
  import { normalizeLastMessageError } from '../../last-message-error.js';
3
4
  import { normalizeAccessPolicy } from '../../../../src/channels/shared/access-policy.mjs';
4
5
  import { normalizeContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
@@ -30,6 +31,7 @@ export const TOKEN_BOT_ENDPOINTS = Object.freeze({
30
31
  reconnectBot: 'bot.reconnect',
31
32
  deleteBot: 'bot.delete',
32
33
  setWorkspace: 'bot.workspace.set',
34
+ setModel: SET_MODEL_ENDPOINT,
33
35
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
34
36
  setContextEnhancement: 'bot.context-enhancement.set',
35
37
  setAccessPolicy: 'bot.access-policy.set',
@@ -60,6 +62,7 @@ export function createTokenChannelApi(channel, connectionSummary, {
60
62
  connected,
61
63
  state: connected ? 'connected' : state,
62
64
  workspace: text(value.workspace, '', 4_096),
65
+ model: normalizeModelSelection(value.model),
63
66
  agentPreset: normalizeAgentPresetId(value.agentPreset),
64
67
  contextEnhancement: normalizeContextEnhancementConfig(value.contextEnhancement),
65
68
  ...(Object.hasOwn(value, 'accessPolicy')
@@ -97,6 +100,7 @@ export function createTokenChannelApi(channel, connectionSummary, {
97
100
  bots,
98
101
  totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
99
102
  agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog),
103
+ modelCatalog: normalizeModelCatalog(source.modelCatalog),
100
104
  };
101
105
  };
102
106
 
@@ -10,6 +10,11 @@ import {
10
10
  AgentPresetEditor,
11
11
  EMPTY_AGENT_PRESET_CATALOG,
12
12
  } from '../../agent-preset.js';
13
+ import {
14
+ EMPTY_MODEL_CATALOG,
15
+ ModelCatalogContext,
16
+ ModelEditor,
17
+ } from '../../model-setting.js';
13
18
  import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
14
19
  import {
15
20
  BotSettingsButton,
@@ -75,7 +80,7 @@ export function createTokenChannelSettings(definition) {
75
80
  accountSettingsEndpoint = null,
76
81
  } = definition;
77
82
 
78
- function AccountCard({ account, busy, testNotice, removing, onReconnect, onWorkspaceSave, onAgentPresetSave, onContextEnhancementSave, onAccountSettingsSave, onRequestRemove, onConfirmRemove, onCancelRemove }) {
83
+ function AccountCard({ account, busy, testNotice, removing, onReconnect, onWorkspaceSave, onModelSave, onAgentPresetSave, onContextEnhancementSave, onAccountSettingsSave, onRequestRemove, onConfirmRemove, onCancelRemove }) {
79
84
  const state = busy === 'reconnect' ? 'connecting' : account.state;
80
85
  const tone = account.connected ? 'success' : state === 'error' ? 'error' : 'warning';
81
86
  const stateLabel = account.connected ? '运行正常' : state === 'connecting' ? '正在连接' : '连接未就绪';
@@ -110,6 +115,11 @@ export function createTokenChannelSettings(definition) {
110
115
  disabled: Boolean(busy),
111
116
  onSave: onWorkspaceSave,
112
117
  }),
118
+ h(ModelEditor, {
119
+ model: account.model,
120
+ disabled: Boolean(busy),
121
+ onSave: onModelSave,
122
+ }),
113
123
  h(AgentPresetEditor, {
114
124
  agentPreset: account.agentPreset,
115
125
  disabled: Boolean(busy),
@@ -161,6 +171,7 @@ export function createTokenChannelSettings(definition) {
161
171
  const [model, setModel] = React.useState({
162
172
  phase: 'loading', bots: [], totals: { configured: 0, connected: 0 }, error: null,
163
173
  agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
174
+ modelCatalog: EMPTY_MODEL_CATALOG,
164
175
  });
165
176
  const [credentialOpen, setCredentialOpen] = React.useState(false);
166
177
  const [credentialError, setCredentialError] = React.useState(null);
@@ -198,6 +209,7 @@ export function createTokenChannelSettings(definition) {
198
209
  setModel({
199
210
  phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
200
211
  agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
212
+ modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
201
213
  });
202
214
  } catch (error) {
203
215
  if (error?.name !== 'AbortError' && mounted.current && !signal?.aborted
@@ -244,6 +256,7 @@ export function createTokenChannelSettings(definition) {
244
256
  setModel({
245
257
  phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
246
258
  agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
259
+ modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
247
260
  });
248
261
  }
249
262
  setCredentialOpen(false);
@@ -266,6 +279,7 @@ export function createTokenChannelSettings(definition) {
266
279
  setModel({
267
280
  phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
268
281
  agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
282
+ modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
269
283
  });
270
284
  }
271
285
  if (mounted.current && operation === 'reconnect') {
@@ -318,6 +332,12 @@ export function createTokenChannelSettings(definition) {
318
332
  endpoints.setWorkspace,
319
333
  { botId: account.botId, workspace },
320
334
  ),
335
+ onModelSave: (selectedModel) => botAction(
336
+ account,
337
+ 'model',
338
+ endpoints.setModel,
339
+ { botId: account.botId, model: selectedModel },
340
+ ),
321
341
  onAgentPresetSave: (agentPreset) => botAction(
322
342
  account,
323
343
  'preset',
@@ -350,7 +370,9 @@ export function createTokenChannelSettings(definition) {
350
370
  })))))
351
371
  : null;
352
372
 
353
- return h(AgentPresetCatalogContext.Provider, {
373
+ return h(ModelCatalogContext.Provider, {
374
+ value: model.modelCatalog ?? EMPTY_MODEL_CATALOG,
375
+ }, h(AgentPresetCatalogContext.Provider, {
354
376
  value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
355
377
  }, h('section', {
356
378
  className: `ddt-page ${pageClass} dim-channelPage`,
@@ -418,7 +440,7 @@ export function createTokenChannelSettings(definition) {
418
440
  'aria-hidden': 'true',
419
441
  }, h(LogoGlyph, { size: 64 }))))
420
442
  : null,
421
- botList)));
443
+ botList))));
422
444
  }
423
445
 
424
446
  return { SettingsTab, AccountCard };
@@ -1,4 +1,5 @@
1
1
  import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
2
+ import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
2
3
  import { normalizeLastMessageError } from '../../last-message-error.js';
3
4
  import { normalizeAccessPolicy } from '../../../../src/channels/shared/access-policy.mjs';
4
5
  import { normalizeContextEnhancementConfig } from '../../../../src/channels/shared/context-enhancement.mjs';
@@ -14,6 +15,7 @@ export const WECOM_ENDPOINTS = Object.freeze({
14
15
  reconnectBot: 'bot.reconnect',
15
16
  deleteBot: 'bot.delete',
16
17
  setWorkspace: 'bot.workspace.set',
18
+ setModel: SET_MODEL_ENDPOINT,
17
19
  setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
18
20
  setContextEnhancement: 'bot.context-enhancement.set',
19
21
  setAccessPolicy: 'bot.access-policy.set',
@@ -99,6 +101,7 @@ function normalizeBot(value) {
99
101
  connected,
100
102
  state: connected ? 'connected' : state,
101
103
  workspace: text(value.workspace, '', 4_096),
104
+ model: normalizeModelSelection(value.model),
102
105
  agentPreset: normalizeAgentPresetId(value.agentPreset),
103
106
  contextEnhancement: normalizeContextEnhancementConfig(value.contextEnhancement),
104
107
  ...(Object.hasOwn(value, 'accessPolicy')
@@ -131,6 +134,7 @@ export function normalizeSnapshot(value) {
131
134
  provisioning: source.provisioning ? normalizeProvisioning(source.provisioning) : null,
132
135
  testMessage: normalizeTestMessage(source.testMessage),
133
136
  agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog),
137
+ modelCatalog: normalizeModelCatalog(source.modelCatalog),
134
138
  };
135
139
  }
136
140