@xmanrui/dsh-im 0.17.0 → 0.18.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/README.en.md +3 -2
- package/README.md +3 -2
- package/lib/client.js +964 -538
- package/lib/index.js +189 -144
- package/package.json +1 -1
- package/plugin-src/client/agent-preset.js +110 -0
- package/plugin-src/client/channels/dingtalk/api.js +5 -0
- package/plugin-src/client/channels/dingtalk/index.js +48 -2
- package/plugin-src/client/channels/feishu/api.js +5 -0
- package/plugin-src/client/channels/feishu/index.js +39 -2
- package/plugin-src/client/channels/qq/api.js +5 -0
- package/plugin-src/client/channels/qq/index.js +37 -6
- package/plugin-src/client/channels/shared/token-api.js +5 -0
- package/plugin-src/client/channels/shared/token-channel.js +34 -6
- package/plugin-src/client/channels/wecom/api.js +5 -0
- package/plugin-src/client/channels/wecom/index.js +37 -6
- package/plugin-src/client/channels/weixin/api.js +15 -0
- package/plugin-src/client/channels/weixin/index.js +54 -4
- package/plugin-src/client/channels/whatsapp/api.js +5 -0
- package/plugin-src/client/channels/whatsapp/index.js +30 -4
- package/plugin-src/client/i18n.js +29 -0
- package/plugin-src/client/styles.js +8 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +10 -4
- package/plugin-src/host/channels/dingtalk/rpc.mjs +12 -0
- package/plugin-src/host/channels/feishu/connection-supervisor.mjs +1 -1
- package/plugin-src/host/channels/feishu/production.mjs +6 -3
- package/plugin-src/host/channels/feishu/rpc.mjs +17 -0
- package/plugin-src/host/channels/qq/production.mjs +10 -4
- package/plugin-src/host/channels/qq/rpc.mjs +12 -0
- package/plugin-src/host/channels/shared/agent-preset-rpc.mjs +25 -0
- package/plugin-src/host/channels/shared/production.mjs +10 -4
- package/plugin-src/host/channels/shared/rpc.mjs +12 -0
- package/plugin-src/host/channels/shared/workspace-rpc.mjs +2 -0
- package/plugin-src/host/channels/slack/production.mjs +10 -4
- package/plugin-src/host/channels/slack/rpc.mjs +13 -0
- package/plugin-src/host/channels/wecom/production.mjs +10 -4
- package/plugin-src/host/channels/wecom/rpc.mjs +12 -0
- package/plugin-src/host/channels/weixin/production.mjs +10 -4
- package/plugin-src/host/channels/weixin/rpc.mjs +15 -0
- package/plugin-src/host/channels/whatsapp/production.mjs +10 -4
- package/plugin-src/host/channels/whatsapp/rpc.mjs +12 -0
- package/src/channels/discord/discord-api.mjs +1 -1
- package/src/channels/shared/agent-preset.mjs +74 -0
- package/src/channels/shared/bot-workspace-store.mjs +141 -14
- package/src/channels/shared/harness-client.mjs +6 -4
- package/src/channels/shared/image-prompt.mjs +32 -1
- package/src/channels/weixin/weixin-api.mjs +1 -1
- package/src/channels/weixin/weixin-bridge.mjs +23 -3
- package/src/channels/weixin/weixin-controller.mjs +15 -0
package/package.json
CHANGED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { h } from './i18n.js';
|
|
4
|
+
|
|
5
|
+
export const SET_AGENT_PRESET_ENDPOINT = 'bot.preset.set';
|
|
6
|
+
|
|
7
|
+
const PRESET_ID = /^[a-z0-9][a-z0-9-]*$/;
|
|
8
|
+
|
|
9
|
+
export const EMPTY_AGENT_PRESET_CATALOG = Object.freeze({
|
|
10
|
+
defaultId: '',
|
|
11
|
+
items: Object.freeze([]),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const AgentPresetCatalogContext = React.createContext(EMPTY_AGENT_PRESET_CATALOG);
|
|
15
|
+
|
|
16
|
+
export function normalizeAgentPresetId(value) {
|
|
17
|
+
if (typeof value !== 'string') return '';
|
|
18
|
+
const id = value.trim();
|
|
19
|
+
return PRESET_ID.test(id) ? id : '';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function normalizeAgentPresetCatalog(value) {
|
|
23
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
24
|
+
return { defaultId: '', items: [] };
|
|
25
|
+
}
|
|
26
|
+
const items = [];
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
for (const entry of Array.isArray(value.items) ? value.items : []) {
|
|
29
|
+
const id = typeof entry === 'string'
|
|
30
|
+
? normalizeAgentPresetId(entry)
|
|
31
|
+
: normalizeAgentPresetId(entry?.id);
|
|
32
|
+
if (!id || seen.has(id)) continue;
|
|
33
|
+
seen.add(id);
|
|
34
|
+
const label = typeof entry?.label === 'string' && entry.label.trim()
|
|
35
|
+
? entry.label.trim().slice(0, 128)
|
|
36
|
+
: typeof entry?.name === 'string' && entry.name.trim()
|
|
37
|
+
? entry.name.trim().slice(0, 128)
|
|
38
|
+
: id;
|
|
39
|
+
items.push({ id, label });
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
defaultId: normalizeAgentPresetId(value.defaultId),
|
|
43
|
+
items,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function AgentPresetEditor({ agentPreset = '', disabled = false, onSave }) {
|
|
48
|
+
const catalog = React.useContext(AgentPresetCatalogContext) ?? EMPTY_AGENT_PRESET_CATALOG;
|
|
49
|
+
const current = normalizeAgentPresetId(agentPreset);
|
|
50
|
+
const [saving, setSaving] = React.useState(false);
|
|
51
|
+
const [error, setError] = React.useState(null);
|
|
52
|
+
|
|
53
|
+
const items = [];
|
|
54
|
+
const seen = new Set();
|
|
55
|
+
for (const item of Array.isArray(catalog.items) ? catalog.items : []) {
|
|
56
|
+
if (!item?.id || seen.has(item.id)) continue;
|
|
57
|
+
seen.add(item.id);
|
|
58
|
+
items.push(item);
|
|
59
|
+
}
|
|
60
|
+
const currentUnavailable = Boolean(current && !seen.has(current));
|
|
61
|
+
if (currentUnavailable) items.push({ id: current, label: current, unavailable: true });
|
|
62
|
+
|
|
63
|
+
const inheritLabel = '跟随 Host 默认';
|
|
64
|
+
|
|
65
|
+
const change = async (event) => {
|
|
66
|
+
const next = event.target.value;
|
|
67
|
+
if (next === current || saving || disabled) return;
|
|
68
|
+
setSaving(true);
|
|
69
|
+
setError(null);
|
|
70
|
+
try {
|
|
71
|
+
await onSave?.(next || null);
|
|
72
|
+
} catch (cause) {
|
|
73
|
+
setError(cause?.message ?? 'Agent Preset 修改失败,请重试。');
|
|
74
|
+
} finally {
|
|
75
|
+
setSaving(false);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return h('div', { className: 'dim-preset' },
|
|
80
|
+
h('div', { className: 'dim-presetHeader' },
|
|
81
|
+
h('span', null, 'Agent Preset'),
|
|
82
|
+
saving ? h('span', { className: 'dim-presetStatus' }, '保存中…') : null),
|
|
83
|
+
React.createElement('select', {
|
|
84
|
+
className: 'dim-presetSelect',
|
|
85
|
+
value: current,
|
|
86
|
+
disabled: disabled || saving,
|
|
87
|
+
'aria-label': 'Agent Preset',
|
|
88
|
+
onChange: (event) => { void change(event); },
|
|
89
|
+
},
|
|
90
|
+
h('option', { value: '' }, inheritLabel),
|
|
91
|
+
...items.map((item) => h(
|
|
92
|
+
'option',
|
|
93
|
+
{ key: item.id, value: item.id },
|
|
94
|
+
item.unavailable
|
|
95
|
+
? [item.id, '(已不可用)']
|
|
96
|
+
: item.label && item.label !== item.id ? `${item.label}(${item.id})` : item.id,
|
|
97
|
+
)),
|
|
98
|
+
),
|
|
99
|
+
h(
|
|
100
|
+
'small',
|
|
101
|
+
{ className: 'dim-presetHelp' },
|
|
102
|
+
'只影响新建会话;若当前聊天已有会话,先发送 /new,再发送普通消息生效。',
|
|
103
|
+
),
|
|
104
|
+
error || currentUnavailable ? h(
|
|
105
|
+
'p',
|
|
106
|
+
{ className: 'dim-presetError', role: error ? 'alert' : 'status' },
|
|
107
|
+
error ?? '当前 Agent Preset 已不可用,请选择其他 Preset 或跟随 Host 默认。',
|
|
108
|
+
) : null,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
|
2
|
+
|
|
1
3
|
export const DINGTALK_RPC_CHANNEL = '/dingtalk';
|
|
2
4
|
|
|
3
5
|
export const DINGTALK_ENDPOINTS = Object.freeze({
|
|
@@ -9,6 +11,7 @@ export const DINGTALK_ENDPOINTS = Object.freeze({
|
|
|
9
11
|
reconnectBot: 'bot.reconnect',
|
|
10
12
|
deleteBot: 'bot.delete',
|
|
11
13
|
setWorkspace: 'bot.workspace.set',
|
|
14
|
+
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
|
12
15
|
});
|
|
13
16
|
|
|
14
17
|
const ACCOUNT_STATES = new Set(['connected', 'connecting', 'offline', 'error']);
|
|
@@ -157,6 +160,7 @@ function normalizeBot(value) {
|
|
|
157
160
|
connected,
|
|
158
161
|
configured: value.configured !== false,
|
|
159
162
|
workspace: optionalString(value.workspace, 4_096) ?? '',
|
|
163
|
+
agentPreset: normalizeAgentPresetId(value.agentPreset),
|
|
160
164
|
bot: {
|
|
161
165
|
name: optionalString(bot.name, 100) ?? '钉钉机器人',
|
|
162
166
|
clientIdMasked: optionalString(bot.clientIdMasked, 140) ?? '已安全保存',
|
|
@@ -200,6 +204,7 @@ export function normalizeSnapshot(value) {
|
|
|
200
204
|
},
|
|
201
205
|
provisioning: source.provisioning ? normalizeProvisioning(source.provisioning) : null,
|
|
202
206
|
testMessage: normalizeTestMessage(source.testMessage),
|
|
207
|
+
agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog),
|
|
203
208
|
};
|
|
204
209
|
}
|
|
205
210
|
|
|
@@ -3,6 +3,11 @@ import * as React from 'react';
|
|
|
3
3
|
import { CredentialActionIcon, CredentialBindingPanel, QrActionIcon } from '../../credential-binding.js';
|
|
4
4
|
import { h } from '../../i18n.js';
|
|
5
5
|
import { WorkspaceEditor } from '../../workspace-editor.js';
|
|
6
|
+
import {
|
|
7
|
+
AgentPresetCatalogContext,
|
|
8
|
+
AgentPresetEditor,
|
|
9
|
+
EMPTY_AGENT_PRESET_CATALOG,
|
|
10
|
+
} from '../../agent-preset.js';
|
|
6
11
|
import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
|
|
7
12
|
import {
|
|
8
13
|
DINGTALK_ENDPOINTS,
|
|
@@ -213,6 +218,7 @@ export function AccountCard({
|
|
|
213
218
|
removing,
|
|
214
219
|
onReconnect,
|
|
215
220
|
onWorkspaceSave,
|
|
221
|
+
onAgentPresetSave,
|
|
216
222
|
onRequestRemove,
|
|
217
223
|
onConfirmRemove,
|
|
218
224
|
onCancelRemove,
|
|
@@ -241,6 +247,11 @@ export function AccountCard({
|
|
|
241
247
|
disabled: Boolean(busy),
|
|
242
248
|
onSave: onWorkspaceSave,
|
|
243
249
|
}),
|
|
250
|
+
h(AgentPresetEditor, {
|
|
251
|
+
agentPreset: account.agentPreset,
|
|
252
|
+
disabled: Boolean(busy),
|
|
253
|
+
onSave: onAgentPresetSave,
|
|
254
|
+
}),
|
|
244
255
|
h('div', { className: 'ddt-accountFooter dim-cardFooter' },
|
|
245
256
|
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
|
246
257
|
feedback ? h('div', {
|
|
@@ -272,6 +283,7 @@ function AccountList(props) {
|
|
|
272
283
|
removing: props.removeTarget === account.botId,
|
|
273
284
|
onReconnect: () => props.onReconnect(account),
|
|
274
285
|
onWorkspaceSave: (workspace) => props.onWorkspaceSave(account, workspace),
|
|
286
|
+
onAgentPresetSave: (agentPreset) => props.onAgentPresetSave(account, agentPreset),
|
|
275
287
|
onRequestRemove: () => props.onRequestRemove(account),
|
|
276
288
|
onConfirmRemove: () => props.onConfirmRemove(account),
|
|
277
289
|
onCancelRemove: props.onCancelRemove,
|
|
@@ -283,6 +295,7 @@ const EMPTY_TOTALS = Object.freeze({ configured: 0, connected: 0 });
|
|
|
283
295
|
export function DingtalkSettingsTab({ rpcCall }) {
|
|
284
296
|
const [model, setModel] = React.useState({
|
|
285
297
|
phase: 'loading', bots: [], totals: EMPTY_TOTALS, revision: 0, error: null,
|
|
298
|
+
agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
|
|
286
299
|
});
|
|
287
300
|
const [provision, setProvision] = React.useState(null);
|
|
288
301
|
const [busy, setBusy] = React.useState(false);
|
|
@@ -389,6 +402,7 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|
|
389
402
|
totals: snapshot.totals,
|
|
390
403
|
revision: snapshot.revision,
|
|
391
404
|
error: null,
|
|
405
|
+
agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
392
406
|
});
|
|
393
407
|
discardStaleFeedback(snapshot);
|
|
394
408
|
if (restoreProvisioning && snapshot.provisioning) {
|
|
@@ -504,6 +518,7 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|
|
504
518
|
totals: snapshot.totals,
|
|
505
519
|
revision: snapshot.revision,
|
|
506
520
|
error: null,
|
|
521
|
+
agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
507
522
|
});
|
|
508
523
|
discardStaleFeedback(snapshot);
|
|
509
524
|
}
|
|
@@ -638,6 +653,7 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|
|
638
653
|
totals: snapshot.totals,
|
|
639
654
|
revision: snapshot.revision,
|
|
640
655
|
error: null,
|
|
656
|
+
agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
641
657
|
});
|
|
642
658
|
discardStaleFeedback(snapshot);
|
|
643
659
|
}
|
|
@@ -700,6 +716,33 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|
|
700
716
|
totals: snapshot.totals,
|
|
701
717
|
revision: snapshot.revision,
|
|
702
718
|
error: null,
|
|
719
|
+
agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
720
|
+
});
|
|
721
|
+
discardStaleFeedback(snapshot);
|
|
722
|
+
}
|
|
723
|
+
} finally {
|
|
724
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
725
|
+
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
|
|
726
|
+
if (mountedRef.current) setBotBusy(account.botId, null);
|
|
727
|
+
}
|
|
728
|
+
}, [discardStaleFeedback, invoke, loadStatus, setBotBusy, workspaceFence]);
|
|
729
|
+
|
|
730
|
+
const saveAgentPreset = React.useCallback(async (account, agentPreset) => {
|
|
731
|
+
const snapshotVersion = workspaceFence.beginMutation();
|
|
732
|
+
setBotBusy(account.botId, 'preset');
|
|
733
|
+
try {
|
|
734
|
+
const snapshot = normalizeSnapshot(await invoke(
|
|
735
|
+
DINGTALK_ENDPOINTS.setAgentPreset,
|
|
736
|
+
{ botId: account.botId, agentPreset },
|
|
737
|
+
));
|
|
738
|
+
if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
739
|
+
setModel({
|
|
740
|
+
phase: 'ready',
|
|
741
|
+
bots: snapshot.bots,
|
|
742
|
+
totals: snapshot.totals,
|
|
743
|
+
revision: snapshot.revision,
|
|
744
|
+
error: null,
|
|
745
|
+
agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
703
746
|
});
|
|
704
747
|
discardStaleFeedback(snapshot);
|
|
705
748
|
}
|
|
@@ -762,7 +805,9 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|
|
762
805
|
})
|
|
763
806
|
: null;
|
|
764
807
|
|
|
765
|
-
return h(
|
|
808
|
+
return h(AgentPresetCatalogContext.Provider, {
|
|
809
|
+
value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
810
|
+
}, h('section', { className: 'ddt-page dim-channelPage', 'aria-label': '钉钉设置' },
|
|
766
811
|
h(Heading, {
|
|
767
812
|
totals: model.totals,
|
|
768
813
|
adding: Boolean(provision),
|
|
@@ -798,11 +843,12 @@ export function DingtalkSettingsTab({ rpcCall }) {
|
|
|
798
843
|
removeTarget,
|
|
799
844
|
onReconnect: (account) => void reconnect(account),
|
|
800
845
|
onWorkspaceSave: saveWorkspace,
|
|
846
|
+
onAgentPresetSave: saveAgentPreset,
|
|
801
847
|
onRequestRemove: (account) => setRemoveTarget(account.botId),
|
|
802
848
|
onConfirmRemove: (account) => void remove(account),
|
|
803
849
|
onCancelRemove: () => setRemoveTarget(null),
|
|
804
850
|
})
|
|
805
|
-
: null));
|
|
851
|
+
: null)));
|
|
806
852
|
}
|
|
807
853
|
|
|
808
854
|
export function apply(ctx) {
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* must never be returned by any endpoint on this channel.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { normalizeAgentPresetCatalog, normalizeAgentPresetId } from "../../agent-preset.js";
|
|
10
|
+
|
|
9
11
|
export const FEISHU_RPC_CHANNEL = "/feishu";
|
|
10
12
|
|
|
11
13
|
export const FEISHU_ENDPOINTS = Object.freeze({
|
|
@@ -19,6 +21,7 @@ export const FEISHU_ENDPOINTS = Object.freeze({
|
|
|
19
21
|
disconnectBot: "bot.disconnect",
|
|
20
22
|
deleteBot: "bot.delete",
|
|
21
23
|
setWorkspace: "bot.workspace.set",
|
|
24
|
+
setAgentPreset: "bot.preset.set",
|
|
22
25
|
// Kept for rolling upgrades. The multi-bot UI never calls these endpoints.
|
|
23
26
|
testConnection: "connection.test",
|
|
24
27
|
disconnect: "connection.disconnect",
|
|
@@ -182,6 +185,7 @@ export function normalizeBotConnection(value, fallbackBotId) {
|
|
|
182
185
|
connected,
|
|
183
186
|
configured: value.configured !== false,
|
|
184
187
|
workspace: optionalString(value.workspace)?.slice(0, 4_096) ?? "",
|
|
188
|
+
agentPreset: normalizeAgentPresetId(value.agentPreset),
|
|
185
189
|
bot: normalizeBot(value.bot),
|
|
186
190
|
health: normalizeHealth(value.health, connected),
|
|
187
191
|
error: normalizeError(value.error),
|
|
@@ -236,6 +240,7 @@ export function normalizeBotsSnapshot(value) {
|
|
|
236
240
|
? normalizeProvisioning(value.provisioning)
|
|
237
241
|
: undefined,
|
|
238
242
|
error: normalizeError(value.error),
|
|
243
|
+
agentPresetCatalog: normalizeAgentPresetCatalog(value.agentPresetCatalog),
|
|
239
244
|
};
|
|
240
245
|
}
|
|
241
246
|
|
|
@@ -16,6 +16,11 @@ import {
|
|
|
16
16
|
} from "./api.js";
|
|
17
17
|
import { useAnimationFrameScheduler } from "../../lifecycle.js";
|
|
18
18
|
import { WorkspaceEditor } from "../../workspace-editor.js";
|
|
19
|
+
import {
|
|
20
|
+
AgentPresetCatalogContext,
|
|
21
|
+
AgentPresetEditor,
|
|
22
|
+
EMPTY_AGENT_PRESET_CATALOG,
|
|
23
|
+
} from "../../agent-preset.js";
|
|
19
24
|
import { useWorkspaceSnapshotFence } from "../../workspace-snapshot-fence.js";
|
|
20
25
|
import { installFeishuStyles } from "./styles.js";
|
|
21
26
|
|
|
@@ -418,6 +423,7 @@ export function BotCard({
|
|
|
418
423
|
onReconnect,
|
|
419
424
|
onRepairCallback,
|
|
420
425
|
onWorkspaceSave,
|
|
426
|
+
onAgentPresetSave,
|
|
421
427
|
onRequestRemove,
|
|
422
428
|
onConfirmRemove,
|
|
423
429
|
onCancelRemove,
|
|
@@ -468,6 +474,11 @@ export function BotCard({
|
|
|
468
474
|
disabled: Boolean(busy),
|
|
469
475
|
onSave: onWorkspaceSave,
|
|
470
476
|
}),
|
|
477
|
+
h(AgentPresetEditor, {
|
|
478
|
+
agentPreset: connection.agentPreset,
|
|
479
|
+
disabled: Boolean(busy),
|
|
480
|
+
onSave: onAgentPresetSave,
|
|
481
|
+
}),
|
|
471
482
|
h("div", { className: "bxf-connectedFooter dim-cardFooter" },
|
|
472
483
|
summary ? h("div", { className: "bxf-healthSummary dim-cardSummary", "data-error": actionError || connection.error ? "true" : undefined },
|
|
473
484
|
summary) : null,
|
|
@@ -524,6 +535,7 @@ function BotList(props) {
|
|
|
524
535
|
onReconnect: () => props.onReconnect(bot),
|
|
525
536
|
onRepairCallback: () => props.onRepairCallback(bot),
|
|
526
537
|
onWorkspaceSave: (workspace) => props.onWorkspaceSave(bot, workspace),
|
|
538
|
+
onAgentPresetSave: (agentPreset) => props.onAgentPresetSave(bot, agentPreset),
|
|
527
539
|
onRequestRemove: () => props.onRequestRemove(bot),
|
|
528
540
|
onConfirmRemove: () => props.onConfirmRemove(bot),
|
|
529
541
|
onCancelRemove: props.onCancelRemove,
|
|
@@ -575,6 +587,7 @@ export function mergeFeishuSnapshotState(
|
|
|
575
587
|
provisioning,
|
|
576
588
|
pageError: null,
|
|
577
589
|
statusError: null,
|
|
590
|
+
agentPresetCatalog: snapshot.agentPresetCatalog ?? current.agentPresetCatalog,
|
|
578
591
|
};
|
|
579
592
|
}
|
|
580
593
|
|
|
@@ -587,6 +600,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
587
600
|
provisioning: null,
|
|
588
601
|
pageError: null,
|
|
589
602
|
statusError: null,
|
|
603
|
+
agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
|
|
590
604
|
});
|
|
591
605
|
const [pageBusy, setPageBusy] = React.useState(false);
|
|
592
606
|
const [provisionBusy, setProvisionBusy] = React.useState(false);
|
|
@@ -1067,6 +1081,26 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1067
1081
|
}
|
|
1068
1082
|
}, [invoke, loadStatus, mergeSnapshot, setBotBusy, setBotError, workspaceFence]);
|
|
1069
1083
|
|
|
1084
|
+
const saveAgentPreset = React.useCallback(async (connection, agentPreset) => {
|
|
1085
|
+
const { botId } = connection;
|
|
1086
|
+
const snapshotVersion = workspaceFence.beginMutation();
|
|
1087
|
+
setBotBusy(botId, "preset");
|
|
1088
|
+
setBotError(botId, null);
|
|
1089
|
+
try {
|
|
1090
|
+
const snapshot = normalizeBotsSnapshot(await invoke(
|
|
1091
|
+
FEISHU_ENDPOINTS.setAgentPreset,
|
|
1092
|
+
{ botId, agentPreset },
|
|
1093
|
+
));
|
|
1094
|
+
if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
1095
|
+
mergeSnapshot(snapshot);
|
|
1096
|
+
}
|
|
1097
|
+
} finally {
|
|
1098
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
1099
|
+
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
|
|
1100
|
+
if (mountedRef.current) setBotBusy(botId, null);
|
|
1101
|
+
}
|
|
1102
|
+
}, [invoke, loadStatus, mergeSnapshot, setBotBusy, setBotError, workspaceFence]);
|
|
1103
|
+
|
|
1070
1104
|
const requestRemove = React.useCallback((connection) => {
|
|
1071
1105
|
setRemoveTargetId(connection.botId);
|
|
1072
1106
|
}, []);
|
|
@@ -1173,7 +1207,9 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1173
1207
|
else removeButtonRefs.current.delete(botId);
|
|
1174
1208
|
}, []);
|
|
1175
1209
|
|
|
1176
|
-
return h(
|
|
1210
|
+
return h(AgentPresetCatalogContext.Provider, {
|
|
1211
|
+
value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
1212
|
+
}, h("section", { className: "bxf-page dim-channelPage", "aria-label": "飞书机器人设置" },
|
|
1177
1213
|
h(Heading, {
|
|
1178
1214
|
totals: model.totals,
|
|
1179
1215
|
onAdd: () => void startProvisioning(),
|
|
@@ -1217,6 +1253,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1217
1253
|
onReconnect: (bot) => void reconnectOneBot(bot),
|
|
1218
1254
|
onRepairCallback: repairCallback,
|
|
1219
1255
|
onWorkspaceSave: saveWorkspace,
|
|
1256
|
+
onAgentPresetSave: saveAgentPreset,
|
|
1220
1257
|
onRequestRemove: requestRemove,
|
|
1221
1258
|
onConfirmRemove: (bot) => void confirmRemove(bot),
|
|
1222
1259
|
onCancelRemove: cancelRemove,
|
|
@@ -1225,7 +1262,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1225
1262
|
})
|
|
1226
1263
|
: null,
|
|
1227
1264
|
),
|
|
1228
|
-
);
|
|
1265
|
+
));
|
|
1229
1266
|
}
|
|
1230
1267
|
|
|
1231
1268
|
export function apply(ctx) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
|
2
|
+
|
|
1
3
|
export const QQ_RPC_CHANNEL = '/qq';
|
|
2
4
|
|
|
3
5
|
export const QQ_ENDPOINTS = Object.freeze({
|
|
@@ -9,6 +11,7 @@ export const QQ_ENDPOINTS = Object.freeze({
|
|
|
9
11
|
reconnectBot: 'bot.reconnect',
|
|
10
12
|
deleteBot: 'bot.delete',
|
|
11
13
|
setWorkspace: 'bot.workspace.set',
|
|
14
|
+
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
|
12
15
|
});
|
|
13
16
|
|
|
14
17
|
const PROVISION_STATES = new Set(['starting', 'pending', 'refreshing', 'connecting', 'connected', 'failed', 'cancelled']);
|
|
@@ -82,6 +85,7 @@ function normalizeBot(value) {
|
|
|
82
85
|
connected,
|
|
83
86
|
state: connected ? 'connected' : state,
|
|
84
87
|
workspace: text(value.workspace, '', 4_096),
|
|
88
|
+
agentPreset: normalizeAgentPresetId(value.agentPreset),
|
|
85
89
|
bot: {
|
|
86
90
|
name: text(value.bot?.name, 'QQ机器人', 100),
|
|
87
91
|
appIdMasked: text(value.bot?.appIdMasked, '应用标识已安全保存', 140),
|
|
@@ -117,6 +121,7 @@ export function normalizeSnapshot(value) {
|
|
|
117
121
|
bots,
|
|
118
122
|
totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
|
|
119
123
|
provisioning: source.provisioning ? normalizeProvisioning(source.provisioning) : null,
|
|
124
|
+
agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog),
|
|
120
125
|
...(testMessage ? { testMessage } : {}),
|
|
121
126
|
};
|
|
122
127
|
}
|
|
@@ -4,6 +4,11 @@ import { QqLogoGlyph } from '../../channel-logos.js';
|
|
|
4
4
|
import { CredentialActionIcon, CredentialBindingPanel, QrActionIcon } from '../../credential-binding.js';
|
|
5
5
|
import { h } from '../../i18n.js';
|
|
6
6
|
import { WorkspaceEditor } from '../../workspace-editor.js';
|
|
7
|
+
import {
|
|
8
|
+
AgentPresetCatalogContext,
|
|
9
|
+
AgentPresetEditor,
|
|
10
|
+
EMPTY_AGENT_PRESET_CATALOG,
|
|
11
|
+
} from '../../agent-preset.js';
|
|
7
12
|
import { useWorkspaceSnapshotFence } from '../../workspace-snapshot-fence.js';
|
|
8
13
|
import { installDingtalkStyles } from '../dingtalk/styles.js';
|
|
9
14
|
import {
|
|
@@ -156,6 +161,7 @@ export function AccountCard({
|
|
|
156
161
|
removing,
|
|
157
162
|
onReconnect,
|
|
158
163
|
onWorkspaceSave,
|
|
164
|
+
onAgentPresetSave,
|
|
159
165
|
onRequestRemove,
|
|
160
166
|
onConfirmRemove,
|
|
161
167
|
onCancelRemove,
|
|
@@ -180,6 +186,11 @@ export function AccountCard({
|
|
|
180
186
|
disabled: Boolean(busy),
|
|
181
187
|
onSave: onWorkspaceSave,
|
|
182
188
|
}),
|
|
189
|
+
h(AgentPresetEditor, {
|
|
190
|
+
agentPreset: account.agentPreset,
|
|
191
|
+
disabled: Boolean(busy),
|
|
192
|
+
onSave: onAgentPresetSave,
|
|
193
|
+
}),
|
|
183
194
|
h('div', { className: 'ddt-accountFooter dim-cardFooter' },
|
|
184
195
|
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
|
185
196
|
feedback ? h('div', {
|
|
@@ -196,7 +207,10 @@ export function AccountCard({
|
|
|
196
207
|
}
|
|
197
208
|
|
|
198
209
|
export function QqSettingsTab({ rpcCall }) {
|
|
199
|
-
const [model, setModel] = React.useState({
|
|
210
|
+
const [model, setModel] = React.useState({
|
|
211
|
+
phase: 'loading', bots: [], totals: { configured: 0, connected: 0 }, error: null,
|
|
212
|
+
agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
|
|
213
|
+
});
|
|
200
214
|
const [provision, setProvision] = React.useState(null);
|
|
201
215
|
const [busy, setBusy] = React.useState(false);
|
|
202
216
|
const [busyByBot, setBusyByBot] = React.useState({});
|
|
@@ -233,7 +247,10 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
233
247
|
const snapshot = normalizeSnapshot(await invoke(QQ_ENDPOINTS.status, {}, signal));
|
|
234
248
|
if (!mounted.current || signal?.aborted
|
|
235
249
|
|| !workspaceFence.canCommitStatus(workspaceVersion)) return undefined;
|
|
236
|
-
setModel({
|
|
250
|
+
setModel({
|
|
251
|
+
phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
|
|
252
|
+
agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
253
|
+
});
|
|
237
254
|
if (restore && snapshot.provisioning) setProvision({
|
|
238
255
|
...snapshot.provisioning,
|
|
239
256
|
durationMs: Math.max(1, snapshot.provisioning.expiresAt - Date.now()),
|
|
@@ -297,7 +314,10 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
297
314
|
));
|
|
298
315
|
if (!mounted.current) return;
|
|
299
316
|
if (workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
300
|
-
setModel({
|
|
317
|
+
setModel({
|
|
318
|
+
phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
|
|
319
|
+
agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
320
|
+
});
|
|
301
321
|
}
|
|
302
322
|
setCredentialOpen(false);
|
|
303
323
|
} catch (error) {
|
|
@@ -357,7 +377,10 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
357
377
|
try {
|
|
358
378
|
const snapshot = normalizeSnapshot(await invoke(endpoint, payload));
|
|
359
379
|
if (mounted.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
360
|
-
setModel({
|
|
380
|
+
setModel({
|
|
381
|
+
phase: 'ready', bots: snapshot.bots, totals: snapshot.totals, error: null,
|
|
382
|
+
agentPresetCatalog: snapshot.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
383
|
+
});
|
|
361
384
|
}
|
|
362
385
|
return snapshot;
|
|
363
386
|
} finally {
|
|
@@ -424,6 +447,12 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
424
447
|
QQ_ENDPOINTS.setWorkspace,
|
|
425
448
|
{ botId: account.botId, workspace },
|
|
426
449
|
),
|
|
450
|
+
onAgentPresetSave: (agentPreset) => botAction(
|
|
451
|
+
account,
|
|
452
|
+
'preset',
|
|
453
|
+
QQ_ENDPOINTS.setAgentPreset,
|
|
454
|
+
{ botId: account.botId, agentPreset },
|
|
455
|
+
),
|
|
427
456
|
onRequestRemove: () => setRemoveTarget(account.botId),
|
|
428
457
|
onCancelRemove: () => setRemoveTarget(null),
|
|
429
458
|
onConfirmRemove: async () => {
|
|
@@ -447,7 +476,9 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
447
476
|
})
|
|
448
477
|
: null;
|
|
449
478
|
|
|
450
|
-
return h(
|
|
479
|
+
return h(AgentPresetCatalogContext.Provider, {
|
|
480
|
+
value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
481
|
+
}, h('section', { className: 'ddt-page dqq-page dim-channelPage', 'aria-label': 'QQ 设置' },
|
|
451
482
|
h(Heading, {
|
|
452
483
|
totals: model.totals,
|
|
453
484
|
adding: Boolean(provision),
|
|
@@ -465,7 +496,7 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
465
496
|
provisionView,
|
|
466
497
|
model.bots.length === 0 && !provision && !credentialOpen
|
|
467
498
|
? h(EmptyView, { busy, onStart: () => void startProvisioning() }) : null,
|
|
468
|
-
botList));
|
|
499
|
+
botList)));
|
|
469
500
|
}
|
|
470
501
|
|
|
471
502
|
export function apply(ctx) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
|
2
|
+
|
|
1
3
|
const ACCOUNT_STATES = new Set(['connected', 'connecting', 'offline', 'error']);
|
|
2
4
|
|
|
3
5
|
function isRecord(value) {
|
|
@@ -25,6 +27,7 @@ export const TOKEN_BOT_ENDPOINTS = Object.freeze({
|
|
|
25
27
|
reconnectBot: 'bot.reconnect',
|
|
26
28
|
deleteBot: 'bot.delete',
|
|
27
29
|
setWorkspace: 'bot.workspace.set',
|
|
30
|
+
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
|
28
31
|
});
|
|
29
32
|
|
|
30
33
|
export function createTokenChannelApi(channel, connectionSummary, {
|
|
@@ -52,6 +55,7 @@ export function createTokenChannelApi(channel, connectionSummary, {
|
|
|
52
55
|
connected,
|
|
53
56
|
state: connected ? 'connected' : state,
|
|
54
57
|
workspace: text(value.workspace, '', 4_096),
|
|
58
|
+
agentPreset: normalizeAgentPresetId(value.agentPreset),
|
|
55
59
|
bot: {
|
|
56
60
|
name: text(value.bot?.name, `${channel}机器人`, 100),
|
|
57
61
|
username: text(value.bot?.username, '', 100),
|
|
@@ -82,6 +86,7 @@ export function createTokenChannelApi(channel, connectionSummary, {
|
|
|
82
86
|
revision: Number.isSafeInteger(source.revision) ? source.revision : 0,
|
|
83
87
|
bots,
|
|
84
88
|
totals: { configured: bots.length, connected: bots.filter((bot) => bot.connected).length },
|
|
89
|
+
agentPresetCatalog: normalizeAgentPresetCatalog(source.agentPresetCatalog),
|
|
85
90
|
};
|
|
86
91
|
};
|
|
87
92
|
|