@xmanrui/dsh-im 4.10.0 → 4.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROACTIVE_DELIVERY.en.md +15 -1
- package/PROACTIVE_DELIVERY.md +15 -1
- package/README.en.md +7 -2
- package/README.md +8 -3
- package/lib/client.js +2341 -1315
- package/lib/index.js +250 -245
- package/package.json +4 -3
- package/plugin-src/client/channel-card-meta.js +9 -1
- package/plugin-src/client/channels/dingtalk/api.js +4 -0
- package/plugin-src/client/channels/dingtalk/index.js +41 -7
- package/plugin-src/client/channels/feishu/api.js +4 -0
- package/plugin-src/client/channels/feishu/group-settings.js +581 -0
- package/plugin-src/client/channels/feishu/index.js +64 -233
- package/plugin-src/client/channels/qq/api.js +4 -0
- package/plugin-src/client/channels/qq/index.js +41 -7
- package/plugin-src/client/channels/shared/collapsible-account.js +80 -0
- package/plugin-src/client/channels/shared/token-api.js +4 -0
- package/plugin-src/client/channels/shared/token-channel.js +62 -29
- package/plugin-src/client/channels/wecom/api.js +4 -0
- package/plugin-src/client/channels/wecom/index.js +41 -7
- package/plugin-src/client/channels/weixin/api.js +4 -0
- package/plugin-src/client/channels/weixin/index.js +41 -7
- package/plugin-src/client/channels/whatsapp/api.js +4 -0
- package/plugin-src/client/channels/whatsapp/index.js +40 -7
- package/plugin-src/client/delivery-settings.js +73 -5
- package/plugin-src/client/i18n.js +22 -0
- package/plugin-src/client/model-setting.js +104 -0
- package/plugin-src/client/styles.js +62 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +3 -0
- package/plugin-src/host/channels/dingtalk/rpc.mjs +11 -0
- package/plugin-src/host/channels/feishu/production.mjs +3 -0
- package/plugin-src/host/channels/feishu/rpc.mjs +16 -0
- package/plugin-src/host/channels/qq/production.mjs +3 -0
- package/plugin-src/host/channels/qq/rpc.mjs +11 -0
- package/plugin-src/host/channels/shared/model-setting-rpc.mjs +19 -0
- package/plugin-src/host/channels/shared/production.mjs +3 -0
- package/plugin-src/host/channels/shared/rpc.mjs +9 -0
- package/plugin-src/host/channels/shared/workspace-rpc.mjs +2 -0
- package/plugin-src/host/channels/slack/production.mjs +3 -0
- package/plugin-src/host/channels/slack/rpc.mjs +9 -0
- package/plugin-src/host/channels/wecom/production.mjs +3 -0
- package/plugin-src/host/channels/wecom/rpc.mjs +11 -0
- package/plugin-src/host/channels/weixin/production.mjs +3 -0
- package/plugin-src/host/channels/weixin/rpc.mjs +11 -0
- package/plugin-src/host/channels/whatsapp/production.mjs +3 -0
- package/plugin-src/host/channels/whatsapp/rpc.mjs +11 -0
- package/plugin-src/host/delivery-adapter.mjs +141 -3
- package/plugin-src/host/delivery-rpc.mjs +12 -0
- package/plugin-src/host/delivery-service.mjs +110 -3
- package/plugin-src/host/delivery-suggestions.mjs +46 -0
- package/plugin-src/host/harness-session-coordinator.mjs +6 -5
- package/plugin-src/host/index.mjs +18 -1
- package/plugin-src/host/session-sync-coordinator.mjs +217 -0
- package/scripts/verify-package.mjs +12 -6
- package/src/channels/shared/bot-workspace-store.mjs +285 -38
- package/src/channels/shared/harness-client.mjs +124 -43
- package/src/channels/shared/i18n-en/wecom.mjs +24 -0
- package/src/channels/shared/model-setting.mjs +110 -0
- package/src/channels/wecom/wecom-bridge.mjs +275 -2
- package/src/channels/wecom/wecom-cards.mjs +101 -0
- package/src/channels/wecom/wecom-runtime.mjs +2 -0
- package/src/channels/whatsapp/whatsapp-runtime.mjs +78 -26
|
@@ -2,13 +2,13 @@ import * as React from "react";
|
|
|
2
2
|
|
|
3
3
|
import { FeishuLogoGlyph } from "../../channel-logos.js";
|
|
4
4
|
import { CredentialActionIcon, CredentialBindingPanel, QrActionIcon } from "../../credential-binding.js";
|
|
5
|
+
import { CollapsibleAccountSection } from "../shared/collapsible-account.js";
|
|
5
6
|
import { h } from "../../i18n.js";
|
|
6
7
|
import {
|
|
7
8
|
FEISHU_ENDPOINTS,
|
|
8
9
|
FEISHU_REGISTRATION_OPERATIONS,
|
|
9
10
|
formatRemaining,
|
|
10
11
|
normalizeBotsSnapshot,
|
|
11
|
-
normalizeGroupResponseMode,
|
|
12
12
|
normalizePollResult,
|
|
13
13
|
normalizeProvisioning,
|
|
14
14
|
presentError,
|
|
@@ -22,6 +22,11 @@ import {
|
|
|
22
22
|
AgentPresetEditor,
|
|
23
23
|
EMPTY_AGENT_PRESET_CATALOG,
|
|
24
24
|
} from "../../agent-preset.js";
|
|
25
|
+
import {
|
|
26
|
+
EMPTY_MODEL_CATALOG,
|
|
27
|
+
ModelCatalogContext,
|
|
28
|
+
ModelEditor,
|
|
29
|
+
} from "../../model-setting.js";
|
|
25
30
|
import { useWorkspaceSnapshotFence } from "../../workspace-snapshot-fence.js";
|
|
26
31
|
import {
|
|
27
32
|
BotSettingsButton,
|
|
@@ -459,133 +464,6 @@ function RemoveConfirmation({ bot, busy, onConfirm, onCancel }) {
|
|
|
459
464
|
);
|
|
460
465
|
}
|
|
461
466
|
|
|
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
467
|
export function BotCard({
|
|
590
468
|
connection,
|
|
591
469
|
busy,
|
|
@@ -598,11 +476,9 @@ export function BotCard({
|
|
|
598
476
|
onReconnect,
|
|
599
477
|
onRepairCallback,
|
|
600
478
|
onWorkspaceSave,
|
|
479
|
+
onModelSave,
|
|
601
480
|
onAgentPresetSave,
|
|
602
481
|
onContextEnhancementSave,
|
|
603
|
-
onGroupResponseModeSave,
|
|
604
|
-
onGroupTopicReplySave,
|
|
605
|
-
onGroupMessagePermissionAuthorize,
|
|
606
482
|
onRequestRemove,
|
|
607
483
|
onConfirmRemove,
|
|
608
484
|
onCancelRemove,
|
|
@@ -631,36 +507,53 @@ export function BotCard({
|
|
|
631
507
|
ref: cardRef,
|
|
632
508
|
},
|
|
633
509
|
h("div", { className: "bxf-cardBody dim-botCardBody" },
|
|
634
|
-
h(
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
h("
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
510
|
+
h(CollapsibleAccountSection, {
|
|
511
|
+
id: `bxf-settings-${connection.botId.replace(/[^a-zA-Z0-9_-]/g, "-")}`,
|
|
512
|
+
header: h("div", { className: "bxf-connectedTop dim-botCardTop" },
|
|
513
|
+
h("div", { className: "bxf-botIdentity dim-botIdentity" },
|
|
514
|
+
h("div", { className: "bxf-avatar dim-botAvatar", "aria-hidden": "true" },
|
|
515
|
+
h(FeishuLogoGlyph, { size: 34 })),
|
|
516
|
+
h("div", { className: "bxf-botName dim-botName" },
|
|
517
|
+
h("h3", { id: titleId, title: bot.name }, bot.name),
|
|
518
|
+
h("p", { title: bot.appIdMasked }, bot.appIdMasked ?? "应用标识已安全保存")),
|
|
519
|
+
),
|
|
520
|
+
h("div", {
|
|
521
|
+
className: "dim-botCardTools",
|
|
522
|
+
// The header is the collapse toggle; keep inner controls clickable.
|
|
523
|
+
onClick: (event) => { event.stopPropagation(); },
|
|
524
|
+
onKeyDown: (event) => { if (event.key === "Enter" || event.key === " ") event.stopPropagation(); },
|
|
525
|
+
},
|
|
526
|
+
h(BotStatusMeta, {
|
|
527
|
+
className: "bxf-healthPill",
|
|
528
|
+
dotClassName: "bxf-dot",
|
|
529
|
+
tone,
|
|
530
|
+
stateLabel: HEALTH_LABELS[stateForDisplay] ?? "状态未知",
|
|
531
|
+
lastCheckedAt: health.lastCheckedAt,
|
|
532
|
+
formatCheckedTime,
|
|
533
|
+
healthState: stateForDisplay,
|
|
534
|
+
}),
|
|
535
|
+
h(BotSettingsButton, {
|
|
536
|
+
channel: "feishu",
|
|
537
|
+
botId: connection.botId,
|
|
538
|
+
botName: bot.name,
|
|
539
|
+
connected,
|
|
540
|
+
accessPolicy: connection.accessPolicy,
|
|
541
|
+
channelSettings: {
|
|
542
|
+
groupResponseMode: connection.groupResponseMode,
|
|
543
|
+
groupTopicReply: connection.groupTopicReply,
|
|
544
|
+
groupMessagePermissionGranted: connection.groupMessagePermissionGranted,
|
|
545
|
+
},
|
|
546
|
+
}))),
|
|
547
|
+
},
|
|
548
|
+
h(WorkspaceEditor, {
|
|
549
|
+
workspace: connection.workspace,
|
|
550
|
+
disabled: Boolean(busy),
|
|
551
|
+
onSave: onWorkspaceSave,
|
|
552
|
+
}),
|
|
553
|
+
h(ModelEditor, {
|
|
554
|
+
model: connection.model,
|
|
662
555
|
disabled: Boolean(busy),
|
|
663
|
-
onSave:
|
|
556
|
+
onSave: onModelSave,
|
|
664
557
|
}),
|
|
665
558
|
h(AgentPresetEditor, {
|
|
666
559
|
agentPreset: connection.agentPreset,
|
|
@@ -672,19 +565,6 @@ export function BotCard({
|
|
|
672
565
|
disabled: Boolean(busy),
|
|
673
566
|
onSave: onContextEnhancementSave,
|
|
674
567
|
}),
|
|
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
568
|
provisionContent
|
|
689
569
|
? h("section", {
|
|
690
570
|
className: "bxf-botProvision dim-botProvision",
|
|
@@ -733,6 +613,7 @@ export function BotCard({
|
|
|
733
613
|
className: "bxf-healthSummary dim-cardFeedback",
|
|
734
614
|
role: "status",
|
|
735
615
|
}, testNotice) : null),
|
|
616
|
+
),
|
|
736
617
|
),
|
|
737
618
|
),
|
|
738
619
|
removing
|
|
@@ -773,11 +654,9 @@ function BotList(props) {
|
|
|
773
654
|
onReconnect: () => props.onReconnect(bot),
|
|
774
655
|
onRepairCallback: () => props.onRepairCallback(bot),
|
|
775
656
|
onWorkspaceSave: (workspace) => props.onWorkspaceSave(bot, workspace),
|
|
657
|
+
onModelSave: (model) => props.onModelSave(bot, model),
|
|
776
658
|
onAgentPresetSave: (agentPreset) => props.onAgentPresetSave(bot, agentPreset),
|
|
777
659
|
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
660
|
onRequestRemove: () => props.onRequestRemove(bot),
|
|
782
661
|
onConfirmRemove: () => props.onConfirmRemove(bot),
|
|
783
662
|
onCancelRemove: props.onCancelRemove,
|
|
@@ -830,6 +709,7 @@ export function mergeFeishuSnapshotState(
|
|
|
830
709
|
pageError: null,
|
|
831
710
|
statusError: null,
|
|
832
711
|
agentPresetCatalog: snapshot.agentPresetCatalog ?? current.agentPresetCatalog,
|
|
712
|
+
modelCatalog: snapshot.modelCatalog ?? current.modelCatalog,
|
|
833
713
|
};
|
|
834
714
|
}
|
|
835
715
|
|
|
@@ -843,6 +723,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
843
723
|
pageError: null,
|
|
844
724
|
statusError: null,
|
|
845
725
|
agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
|
|
726
|
+
modelCatalog: EMPTY_MODEL_CATALOG,
|
|
846
727
|
});
|
|
847
728
|
const [pageBusy, setPageBusy] = React.useState(false);
|
|
848
729
|
const [provisionBusy, setProvisionBusy] = React.useState(false);
|
|
@@ -1384,56 +1265,6 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1384
1265
|
}
|
|
1385
1266
|
}, [invoke, loadStatus, mergeSnapshot, setBotBusy, setBotError, workspaceFence]);
|
|
1386
1267
|
|
|
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
1268
|
const requestRemove = React.useCallback((connection) => {
|
|
1438
1269
|
setRemoveTargetId(connection.botId);
|
|
1439
1270
|
}, []);
|
|
@@ -1541,7 +1372,9 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1541
1372
|
else removeButtonRefs.current.delete(botId);
|
|
1542
1373
|
}, []);
|
|
1543
1374
|
|
|
1544
|
-
return h(
|
|
1375
|
+
return h(ModelCatalogContext.Provider, {
|
|
1376
|
+
value: model.modelCatalog ?? EMPTY_MODEL_CATALOG,
|
|
1377
|
+
}, h(AgentPresetCatalogContext.Provider, {
|
|
1545
1378
|
value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
1546
1379
|
}, h("section", { className: "bxf-page dim-channelPage", "aria-label": "飞书机器人设置" },
|
|
1547
1380
|
h(Heading, {
|
|
@@ -1589,17 +1422,15 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1589
1422
|
onReconnect: (bot) => void reconnectOneBot(bot),
|
|
1590
1423
|
onRepairCallback: repairCallback,
|
|
1591
1424
|
onWorkspaceSave: saveWorkspace,
|
|
1425
|
+
onModelSave: (connection, selectedModel) => saveBotSetting(
|
|
1426
|
+
connection, "model", FEISHU_ENDPOINTS.setModel, { model: selectedModel },
|
|
1427
|
+
),
|
|
1592
1428
|
onAgentPresetSave: (connection, agentPreset) => saveBotSetting(
|
|
1593
1429
|
connection, "preset", FEISHU_ENDPOINTS.setAgentPreset, { agentPreset },
|
|
1594
1430
|
),
|
|
1595
1431
|
onContextEnhancementSave: (connection, config) => saveBotSetting(
|
|
1596
1432
|
connection, "context-enhancement", FEISHU_ENDPOINTS.setContextEnhancement, { config },
|
|
1597
1433
|
),
|
|
1598
|
-
onGroupResponseModeSave: saveGroupResponseMode,
|
|
1599
|
-
onGroupTopicReplySave: (connection, groupTopicReply) => saveBotSetting(
|
|
1600
|
-
connection, "group-topic-reply", FEISHU_ENDPOINTS.setGroupTopicReply, { groupTopicReply },
|
|
1601
|
-
),
|
|
1602
|
-
onGroupMessagePermissionAuthorize: authorizeGroupMessages,
|
|
1603
1434
|
onRequestRemove: requestRemove,
|
|
1604
1435
|
onConfirmRemove: (bot) => void confirmRemove(bot),
|
|
1605
1436
|
onCancelRemove: cancelRemove,
|
|
@@ -1608,5 +1439,5 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1608
1439
|
})
|
|
1609
1440
|
: null,
|
|
1610
1441
|
),
|
|
1611
|
-
));
|
|
1442
|
+
)));
|
|
1612
1443
|
}
|
|
@@ -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
|
}
|
|
@@ -2,6 +2,7 @@ import * as React from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { QqLogoGlyph } from '../../channel-logos.js';
|
|
4
4
|
import { CredentialActionIcon, CredentialBindingPanel, QrActionIcon } from '../../credential-binding.js';
|
|
5
|
+
import { CollapsibleAccountSection } from '../shared/collapsible-account.js';
|
|
5
6
|
import { h } from '../../i18n.js';
|
|
6
7
|
import { WorkspaceEditor } from '../../workspace-editor.js';
|
|
7
8
|
import { ContextEnhancementEditor } from '../../context-enhancement.js';
|
|
@@ -10,6 +11,11 @@ import {
|
|
|
10
11
|
AgentPresetEditor,
|
|
11
12
|
EMPTY_AGENT_PRESET_CATALOG,
|
|
12
13
|
} from '../../agent-preset.js';
|
|
14
|
+
import {
|
|
15
|
+
EMPTY_MODEL_CATALOG,
|
|
16
|
+
ModelCatalogContext,
|
|
17
|
+
ModelEditor,
|
|
18
|
+
} from '../../model-setting.js';
|
|
13
19
|
import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
|
|
14
20
|
import {
|
|
15
21
|
BotSettingsButton,
|
|
@@ -167,6 +173,7 @@ export function AccountCard({
|
|
|
167
173
|
removing,
|
|
168
174
|
onReconnect,
|
|
169
175
|
onWorkspaceSave,
|
|
176
|
+
onModelSave,
|
|
170
177
|
onAgentPresetSave,
|
|
171
178
|
onContextEnhancementSave,
|
|
172
179
|
onRequestRemove,
|
|
@@ -178,12 +185,19 @@ export function AccountCard({
|
|
|
178
185
|
const summary = account.error?.message ?? (account.connected ? null : account.health.summary);
|
|
179
186
|
return h('article', { className: 'ddt-card dim-botCard', 'data-bot-id': account.botId },
|
|
180
187
|
h('div', { className: 'ddt-cardBody dim-botCardBody' },
|
|
181
|
-
h(
|
|
188
|
+
h(CollapsibleAccountSection, {
|
|
189
|
+
id: `qq-settings-${account.botId.replace(/[^a-zA-Z0-9_-]/g, '-')}`,
|
|
190
|
+
header: h('div', { className: 'ddt-accountTop dim-botCardTop' },
|
|
182
191
|
h('div', { className: 'ddt-accountIdentity dim-botIdentity' },
|
|
183
192
|
h('div', { className: 'ddt-avatar dim-botAvatar dqq-avatar', 'aria-hidden': 'true' }, h(QqLogoGlyph, { size: 29 })),
|
|
184
193
|
h('div', { className: 'dim-botName' },
|
|
185
194
|
h('h3', null, account.bot.name), h('p', null, account.bot.appIdMasked))),
|
|
186
|
-
h('div', {
|
|
195
|
+
h('div', {
|
|
196
|
+
className: 'dim-botCardTools',
|
|
197
|
+
// The header is the collapse toggle; keep inner controls clickable.
|
|
198
|
+
onClick: (event) => { event.stopPropagation(); },
|
|
199
|
+
onKeyDown: (event) => { if (event.key === 'Enter' || event.key === ' ') event.stopPropagation(); },
|
|
200
|
+
},
|
|
187
201
|
h(BotStatusMeta, {
|
|
188
202
|
className: 'ddt-health',
|
|
189
203
|
dotClassName: 'ddt-dot',
|
|
@@ -198,12 +212,18 @@ export function AccountCard({
|
|
|
198
212
|
botName: account.bot.name,
|
|
199
213
|
connected: account.connected,
|
|
200
214
|
accessPolicy: account.accessPolicy,
|
|
201
|
-
})))
|
|
202
|
-
|
|
215
|
+
})))
|
|
216
|
+
},
|
|
217
|
+
h(WorkspaceEditor, {
|
|
203
218
|
workspace: account.workspace,
|
|
204
219
|
disabled: Boolean(busy),
|
|
205
220
|
onSave: onWorkspaceSave,
|
|
206
221
|
}),
|
|
222
|
+
h(ModelEditor, {
|
|
223
|
+
model: account.model,
|
|
224
|
+
disabled: Boolean(busy),
|
|
225
|
+
onSave: onModelSave,
|
|
226
|
+
}),
|
|
207
227
|
h(AgentPresetEditor, {
|
|
208
228
|
agentPreset: account.agentPreset,
|
|
209
229
|
disabled: Boolean(busy),
|
|
@@ -228,7 +248,9 @@ export function AccountCard({
|
|
|
228
248
|
className: 'ddt-summary dim-cardFeedback',
|
|
229
249
|
role: 'status',
|
|
230
250
|
'aria-live': 'polite',
|
|
231
|
-
}, feedback) : null))
|
|
251
|
+
}, feedback) : null)),
|
|
252
|
+
),
|
|
253
|
+
),
|
|
232
254
|
removing ? h(RemoveConfirmation, {
|
|
233
255
|
account, busy: busy === 'delete', onConfirm: onConfirmRemove, onCancel: onCancelRemove,
|
|
234
256
|
}) : null);
|
|
@@ -238,6 +260,7 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
238
260
|
const [model, setModel] = React.useState({
|
|
239
261
|
phase: 'loading', bots: [], totals: { configured: 0, connected: 0 }, error: null,
|
|
240
262
|
agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
|
|
263
|
+
modelCatalog: EMPTY_MODEL_CATALOG,
|
|
241
264
|
});
|
|
242
265
|
const [provision, setProvision] = React.useState(null);
|
|
243
266
|
const [busy, setBusy] = React.useState(false);
|
|
@@ -278,6 +301,7 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
278
301
|
setModel({
|
|
279
302
|
phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
|
|
280
303
|
agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
304
|
+
modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
|
|
281
305
|
});
|
|
282
306
|
if (restore && snapshot.provisioning) setProvision({
|
|
283
307
|
...snapshot.provisioning,
|
|
@@ -345,6 +369,7 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
345
369
|
setModel({
|
|
346
370
|
phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
|
|
347
371
|
agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
372
|
+
modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
|
|
348
373
|
});
|
|
349
374
|
}
|
|
350
375
|
setCredentialOpen(false);
|
|
@@ -408,6 +433,7 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
408
433
|
setModel({
|
|
409
434
|
phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
|
|
410
435
|
agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
436
|
+
modelCatalog: snapshot.modelCatalog ?? EMPTY_MODEL_CATALOG,
|
|
411
437
|
});
|
|
412
438
|
}
|
|
413
439
|
return snapshot;
|
|
@@ -478,6 +504,12 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
478
504
|
QQ_ENDPOINTS.setWorkspace,
|
|
479
505
|
{ botId: account.botId, workspace },
|
|
480
506
|
),
|
|
507
|
+
onModelSave: (selectedModel) => botAction(
|
|
508
|
+
account,
|
|
509
|
+
'model',
|
|
510
|
+
QQ_ENDPOINTS.setModel,
|
|
511
|
+
{ botId: account.botId, model: selectedModel },
|
|
512
|
+
),
|
|
481
513
|
onAgentPresetSave: (agentPreset) => botAction(
|
|
482
514
|
account,
|
|
483
515
|
'preset',
|
|
@@ -513,7 +545,9 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
513
545
|
})
|
|
514
546
|
: null;
|
|
515
547
|
|
|
516
|
-
return h(
|
|
548
|
+
return h(ModelCatalogContext.Provider, {
|
|
549
|
+
value: model.modelCatalog ?? EMPTY_MODEL_CATALOG,
|
|
550
|
+
}, h(AgentPresetCatalogContext.Provider, {
|
|
517
551
|
value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
518
552
|
}, h('section', { className: 'ddt-page dqq-page dim-channelPage', 'aria-label': 'QQ 设置' },
|
|
519
553
|
h(Heading, {
|
|
@@ -533,5 +567,5 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
533
567
|
provisionView,
|
|
534
568
|
model.bots.length === 0 && !provision && !credentialOpen
|
|
535
569
|
? h(EmptyView, { busy, onStart: () => void startProvisioning() }) : null,
|
|
536
|
-
botList)));
|
|
570
|
+
botList))));
|
|
537
571
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collapsible per-account card, shared by every IM channel.
|
|
3
|
+
*
|
|
4
|
+
* When several accounts are connected, each account's workspace / preset /
|
|
5
|
+
* context-enhancement editors make the list extremely long. This component
|
|
6
|
+
* renders the account header as the toggle itself: collapsed, the card shows
|
|
7
|
+
* only the header line; clicking the header expands the settings details.
|
|
8
|
+
*
|
|
9
|
+
* Design notes:
|
|
10
|
+
* - The whole header is the toggle (click + keyboard). Buttons rendered inside
|
|
11
|
+
* the header must call stopPropagation so they keep their own behaviour.
|
|
12
|
+
* - A chevron sits at the end of the header line as the affordance.
|
|
13
|
+
* - Height animation uses grid-template-rows: 0fr -> 1fr (pure CSS).
|
|
14
|
+
* - Full a11y: the header is a button-like region with aria-expanded /
|
|
15
|
+
* aria-controls; the details region keeps native semantics.
|
|
16
|
+
* - Honors prefers-reduced-motion.
|
|
17
|
+
* - Styles are managed by the IM settings plugin, not individual accounts.
|
|
18
|
+
*/
|
|
19
|
+
import * as React from 'react';
|
|
20
|
+
import { h } from '../../i18n.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Collapsible account card.
|
|
24
|
+
*
|
|
25
|
+
* `header` renders the always-visible header line; clicking it toggles.
|
|
26
|
+
* Any interactive control inside the header should stop the click event so it
|
|
27
|
+
* does not toggle the section (e.g. the bot settings button).
|
|
28
|
+
* `children` is the collapsed details region.
|
|
29
|
+
*/
|
|
30
|
+
export function CollapsibleAccountSection({
|
|
31
|
+
header,
|
|
32
|
+
defaultOpen = false,
|
|
33
|
+
open: controlledOpen,
|
|
34
|
+
onToggle,
|
|
35
|
+
id,
|
|
36
|
+
className = '',
|
|
37
|
+
children,
|
|
38
|
+
}) {
|
|
39
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);
|
|
40
|
+
const open = controlledOpen ?? uncontrolledOpen;
|
|
41
|
+
const contentId = id ? `${id}-content` : undefined;
|
|
42
|
+
|
|
43
|
+
const toggle = () => {
|
|
44
|
+
if (controlledOpen === undefined) setUncontrolledOpen((value) => !value);
|
|
45
|
+
onToggle?.(!open);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const onKeyDown = (event) => {
|
|
49
|
+
if (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') {
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
toggle();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return h('div', {
|
|
56
|
+
className: `dim-collapsibleAccount ${open ? 'is-open' : ''} ${className}`.trim(),
|
|
57
|
+
'data-open': open ? 'true' : 'false',
|
|
58
|
+
},
|
|
59
|
+
h('div', {
|
|
60
|
+
className: 'dim-collapsibleHead',
|
|
61
|
+
role: 'button',
|
|
62
|
+
tabIndex: 0,
|
|
63
|
+
onClick: toggle,
|
|
64
|
+
onKeyDown,
|
|
65
|
+
'aria-expanded': open ? 'true' : 'false',
|
|
66
|
+
'aria-controls': contentId,
|
|
67
|
+
'aria-label': open ? '收起该账号的设置' : '展开该账号的设置',
|
|
68
|
+
},
|
|
69
|
+
h('div', { className: 'dim-collapsibleHeaderContent' }, header),
|
|
70
|
+
h('span', { className: 'dim-collapsibleChevron', 'aria-hidden': 'true' }),
|
|
71
|
+
),
|
|
72
|
+
h('div', {
|
|
73
|
+
id: contentId,
|
|
74
|
+
className: 'dim-collapsibleBody',
|
|
75
|
+
role: 'region',
|
|
76
|
+
},
|
|
77
|
+
h('div', { className: 'dim-collapsibleBodyInner' }, children),
|
|
78
|
+
),
|
|
79
|
+
);
|
|
80
|
+
}
|