@xmanrui/dsh-im 0.17.1 → 0.19.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 +1297 -600
- package/lib/index.js +190 -145
- 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 +28 -5
- package/plugin-src/client/channels/feishu/index.js +330 -41
- package/plugin-src/client/channels/feishu/styles.js +46 -0
- 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 +5 -0
- package/plugin-src/client/channels/weixin/index.js +50 -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 +59 -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 +8 -3
- package/plugin-src/host/channels/feishu/rpc.mjs +78 -7
- 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/feishu/bridge.mjs +29 -4
- package/src/channels/feishu/feishu-runtime.mjs +14 -0
- package/src/channels/feishu/group-message-permission-manager.mjs +71 -0
- package/src/channels/feishu/group-response-mode.mjs +15 -0
- package/src/channels/feishu/multi-bot-controller.mjs +258 -9
- package/src/channels/feishu/plugin-config-store.mjs +3 -0
- package/src/channels/feishu/repair-manager.mjs +17 -3
- 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/telegram/telegram-api.mjs +31 -0
- package/src/channels/telegram/telegram-runtime.mjs +25 -1
- package/src/channels/weixin/weixin-api.mjs +1 -1
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
FEISHU_RPC_CHANNEL,
|
|
10
10
|
formatRemaining,
|
|
11
11
|
normalizeBotsSnapshot,
|
|
12
|
+
normalizeGroupResponseMode,
|
|
12
13
|
normalizePollResult,
|
|
13
14
|
normalizeProvisioning,
|
|
14
15
|
presentError,
|
|
@@ -16,6 +17,11 @@ import {
|
|
|
16
17
|
} from "./api.js";
|
|
17
18
|
import { useAnimationFrameScheduler } from "../../lifecycle.js";
|
|
18
19
|
import { WorkspaceEditor } from "../../workspace-editor.js";
|
|
20
|
+
import {
|
|
21
|
+
AgentPresetCatalogContext,
|
|
22
|
+
AgentPresetEditor,
|
|
23
|
+
EMPTY_AGENT_PRESET_CATALOG,
|
|
24
|
+
} from "../../agent-preset.js";
|
|
19
25
|
import { useWorkspaceSnapshotFence } from "../../workspace-snapshot-fence.js";
|
|
20
26
|
import { installFeishuStyles } from "./styles.js";
|
|
21
27
|
|
|
@@ -23,11 +29,20 @@ export const name = "feishu-settings";
|
|
|
23
29
|
export const inject = ["slots", "connection"];
|
|
24
30
|
|
|
25
31
|
const CALLBACK_REPAIR_OPERATION = FEISHU_REGISTRATION_OPERATIONS.CALLBACK_REPAIR;
|
|
32
|
+
const GROUP_MESSAGE_PERMISSION_OPERATION = FEISHU_REGISTRATION_OPERATIONS.GROUP_MESSAGE_PERMISSION;
|
|
26
33
|
|
|
27
34
|
function isCallbackRepair(value) {
|
|
28
35
|
return value?.operation === CALLBACK_REPAIR_OPERATION;
|
|
29
36
|
}
|
|
30
37
|
|
|
38
|
+
function isGroupMessagePermission(value) {
|
|
39
|
+
return value?.operation === GROUP_MESSAGE_PERMISSION_OPERATION;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isTargetedAppUpdate(value) {
|
|
43
|
+
return isCallbackRepair(value) || isGroupMessagePermission(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
31
46
|
function SvgIcon({ children, size = 18, className, viewBox = "0 0 24 24" }) {
|
|
32
47
|
return h("svg", {
|
|
33
48
|
width: size,
|
|
@@ -236,6 +251,7 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
|
|
|
236
251
|
const expired = provision.expired === true || remaining === 0;
|
|
237
252
|
const progress = Math.min(1, remaining / Math.max(1, provision.durationMs ?? remaining));
|
|
238
253
|
const repairing = isCallbackRepair(provision);
|
|
254
|
+
const grantingGroupMessages = isGroupMessagePermission(provision);
|
|
239
255
|
const botName = provision.botName ?? "此机器人";
|
|
240
256
|
|
|
241
257
|
React.useEffect(() => setImageFailed(false), [qrSource]);
|
|
@@ -249,7 +265,9 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
|
|
|
249
265
|
src: qrSource,
|
|
250
266
|
alt: repairing
|
|
251
267
|
? `用于修复${botName}卡片按钮的一次性授权二维码`
|
|
252
|
-
:
|
|
268
|
+
: grantingGroupMessages
|
|
269
|
+
? `用于为${botName}开通群消息权限的一次性授权二维码`
|
|
270
|
+
: "用于新增 DeepSeek Harness 飞书机器人的一次性授权二维码",
|
|
253
271
|
onError: () => setImageFailed(true),
|
|
254
272
|
})
|
|
255
273
|
: h("div", { className: "bxf-qrFallback dim-qrFallback" },
|
|
@@ -273,21 +291,35 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
|
|
|
273
291
|
h("div", { className: "bxf-qrCopy dim-qrCopy" },
|
|
274
292
|
h("div", { className: "bxf-stateLabel dim-stateLabel" },
|
|
275
293
|
h("span", { className: "bxf-dot dim-stateDot", "data-tone": "warning" }),
|
|
276
|
-
h("span", null, repairing
|
|
294
|
+
h("span", null, repairing
|
|
295
|
+
? `正在修复「${botName}」`
|
|
296
|
+
: grantingGroupMessages
|
|
297
|
+
? `正在为「${botName}」开通群消息权限`
|
|
298
|
+
: "正在添加新机器人")),
|
|
277
299
|
h("h3", null, expired
|
|
278
300
|
? "刷新二维码后继续"
|
|
279
|
-
: repairing
|
|
301
|
+
: repairing
|
|
302
|
+
? "使用飞书扫码修复卡片按钮"
|
|
303
|
+
: grantingGroupMessages
|
|
304
|
+
? "使用飞书确认群消息权限"
|
|
305
|
+
: "使用飞书扫码创建机器人"),
|
|
280
306
|
h("p", null, repairing
|
|
281
307
|
? "扫码会更新现有飞书应用,只增量补充卡片按钮回调;不会创建新应用。确认后此机器人会短暂重连,其他机器人不受影响。"
|
|
282
|
-
:
|
|
308
|
+
: grantingGroupMessages
|
|
309
|
+
? "扫码会更新现有飞书应用,只增量开通“获取群组中所有消息”权限;不会创建新应用。确认后会自动启用“响应所有群消息”,其他机器人不受影响。"
|
|
310
|
+
: "扫码只会新增一个机器人,已接入的机器人会继续正常收发消息。"),
|
|
283
311
|
h("ol", { className: "bxf-steps dim-steps" },
|
|
284
312
|
h("li", null, "打开飞书移动端,使用扫一扫读取二维码"),
|
|
285
313
|
h("li", null, repairing
|
|
286
314
|
? "核对现有应用名称,并确认只新增卡片回调"
|
|
287
|
-
:
|
|
315
|
+
: grantingGroupMessages
|
|
316
|
+
? "核对现有应用,并确认“获取群组中所有消息”权限"
|
|
317
|
+
: "核对应用名称与权限范围,并确认创建"),
|
|
288
318
|
h("li", null, repairing
|
|
289
319
|
? "保持本页打开,等待卡片按钮修复完成"
|
|
290
|
-
:
|
|
320
|
+
: grantingGroupMessages
|
|
321
|
+
? "保持本页打开,等待权限生效并自动切换响应方式"
|
|
322
|
+
: "保持本页打开,等待新机器人的长连接就绪")),
|
|
291
323
|
h("div", { className: "bxf-actions dim-viewActions" },
|
|
292
324
|
expired
|
|
293
325
|
? h(Button, {
|
|
@@ -302,7 +334,9 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
|
|
|
302
334
|
!expired
|
|
303
335
|
? h(Button, { onClick: onRefresh, disabled: busy }, "换一个二维码")
|
|
304
336
|
: null,
|
|
305
|
-
h(Button, { onClick: onCancel, disabled: busy }, repairing
|
|
337
|
+
h(Button, { onClick: onCancel, disabled: busy }, repairing
|
|
338
|
+
? "取消修复"
|
|
339
|
+
: grantingGroupMessages ? "取消授权" : "取消添加")),
|
|
306
340
|
),
|
|
307
341
|
),
|
|
308
342
|
);
|
|
@@ -311,34 +345,50 @@ function QrPane({ provision, now, onRefresh, onCancel, busy }) {
|
|
|
311
345
|
function ProvisionProgress({ phase, provision, onCancel, busy }) {
|
|
312
346
|
const connecting = phase === "connecting";
|
|
313
347
|
const repairing = isCallbackRepair(provision);
|
|
348
|
+
const grantingGroupMessages = isGroupMessagePermission(provision);
|
|
314
349
|
return h("div", {
|
|
315
350
|
className: "bxf-card bxf-provisionCard dim-surfaceCard dim-loadingView",
|
|
316
351
|
"aria-busy": "true",
|
|
317
352
|
},
|
|
318
353
|
h("div", { className: "dim-spinner", "aria-hidden": "true" }),
|
|
319
354
|
h("h3", null, connecting
|
|
320
|
-
? repairing
|
|
321
|
-
|
|
355
|
+
? repairing
|
|
356
|
+
? "已确认,正在完成卡片按钮修复"
|
|
357
|
+
: grantingGroupMessages
|
|
358
|
+
? "已确认,正在启用全部消息模式"
|
|
359
|
+
: "已确认,正在连接新机器人"
|
|
360
|
+
: repairing
|
|
361
|
+
? "正在准备修复二维码"
|
|
362
|
+
: grantingGroupMessages ? "正在准备权限授权二维码" : "正在准备授权二维码"),
|
|
322
363
|
h("p", null, connecting
|
|
323
364
|
? repairing
|
|
324
365
|
? "配置已提交,正在验证卡片按钮回调并重连此机器人;此阶段无法取消,其他机器人不会中断。"
|
|
325
|
-
:
|
|
366
|
+
: grantingGroupMessages
|
|
367
|
+
? "权限配置已提交,正在保存设置并重连此机器人;此阶段无法取消,其他机器人不会中断。"
|
|
368
|
+
: "正在安全保存凭据并检查新机器人的消息通道,其他机器人不会中断。"
|
|
326
369
|
: repairing
|
|
327
370
|
? "正在为现有飞书应用申请一次性更新二维码,请稍候。"
|
|
328
|
-
:
|
|
371
|
+
: grantingGroupMessages
|
|
372
|
+
? "正在为现有飞书应用申请群消息权限二维码,请稍候。"
|
|
373
|
+
: "正在向飞书申请一次性授权二维码,请稍候。"),
|
|
329
374
|
connecting && onCancel
|
|
330
375
|
? h("div", { className: "bxf-actions dim-viewActions", style: { justifyContent: "center" } },
|
|
331
|
-
h(Button, { onClick: onCancel, disabled: busy }, repairing
|
|
376
|
+
h(Button, { onClick: onCancel, disabled: busy }, repairing
|
|
377
|
+
? "取消修复"
|
|
378
|
+
: grantingGroupMessages ? "取消授权" : "取消添加"))
|
|
332
379
|
: null,
|
|
333
380
|
);
|
|
334
381
|
}
|
|
335
382
|
|
|
336
383
|
function ProvisionError({ error, provision, onRetry, onCancel, busy }) {
|
|
337
384
|
const repairing = isCallbackRepair(provision);
|
|
385
|
+
const grantingGroupMessages = isGroupMessagePermission(provision);
|
|
338
386
|
return h("div", { className: "bxf-card bxf-provisionCard dim-surfaceCard" },
|
|
339
387
|
h("div", { className: "bxf-inlineError dim-inlineError", role: "alert" },
|
|
340
388
|
h("div", null,
|
|
341
|
-
h("h3", null, repairing
|
|
389
|
+
h("h3", null, repairing
|
|
390
|
+
? "卡片按钮没有修复完成"
|
|
391
|
+
: grantingGroupMessages ? "群消息权限没有开通完成" : "新机器人没有添加完成"),
|
|
342
392
|
h("p", null, error.message),
|
|
343
393
|
error.code ? h("span", { className: "bxf-errorCode" }, error.code) : null,
|
|
344
394
|
h("div", { className: "bxf-actions dim-viewActions" },
|
|
@@ -408,16 +458,104 @@ function RemoveConfirmation({ bot, busy, onConfirm, onCancel }) {
|
|
|
408
458
|
);
|
|
409
459
|
}
|
|
410
460
|
|
|
461
|
+
function GroupResponseModeEditor({
|
|
462
|
+
value,
|
|
463
|
+
permissionGranted = false,
|
|
464
|
+
disabled = false,
|
|
465
|
+
authorizationDisabled = false,
|
|
466
|
+
onSave,
|
|
467
|
+
onAuthorize,
|
|
468
|
+
}) {
|
|
469
|
+
const current = normalizeGroupResponseMode(value);
|
|
470
|
+
const [saving, setSaving] = React.useState(false);
|
|
471
|
+
const [authorizing, setAuthorizing] = React.useState(false);
|
|
472
|
+
const [error, setError] = React.useState(null);
|
|
473
|
+
|
|
474
|
+
const change = async (event) => {
|
|
475
|
+
const next = normalizeGroupResponseMode(event.target.value);
|
|
476
|
+
if (next === current || saving || disabled) return;
|
|
477
|
+
setSaving(true);
|
|
478
|
+
setError(null);
|
|
479
|
+
try {
|
|
480
|
+
await onSave?.(next);
|
|
481
|
+
} catch (cause) {
|
|
482
|
+
setError(cause?.message ?? "群聊响应方式修改失败,请重试。");
|
|
483
|
+
} finally {
|
|
484
|
+
setSaving(false);
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
const authorize = async () => {
|
|
489
|
+
if (current !== "all" || saving || authorizing || disabled || authorizationDisabled) return;
|
|
490
|
+
setAuthorizing(true);
|
|
491
|
+
setError(null);
|
|
492
|
+
try {
|
|
493
|
+
await onAuthorize?.();
|
|
494
|
+
} catch (cause) {
|
|
495
|
+
setError(cause?.message ?? "群消息权限授权失败,请重试。");
|
|
496
|
+
} finally {
|
|
497
|
+
setAuthorizing(false);
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
return h("div", { className: "bxf-responseMode dim-responseMode" },
|
|
502
|
+
h("div", { className: "bxf-responseModeHeader dim-responseModeHeader" },
|
|
503
|
+
h("span", null, "群聊响应方式"),
|
|
504
|
+
saving || authorizing
|
|
505
|
+
? h("span", { className: "bxf-responseModeStatus dim-responseModeStatus" },
|
|
506
|
+
saving ? "保存中…" : "正在准备授权…")
|
|
507
|
+
: null),
|
|
508
|
+
h("select", {
|
|
509
|
+
className: "bxf-responseModeSelect dim-responseModeSelect",
|
|
510
|
+
value: current,
|
|
511
|
+
disabled: disabled || saving,
|
|
512
|
+
"aria-label": "群聊响应方式",
|
|
513
|
+
onChange: (event) => { void change(event); },
|
|
514
|
+
},
|
|
515
|
+
h("option", { value: "mention" }, "仅在 @机器人时响应(推荐)"),
|
|
516
|
+
h("option", { value: "all" }, "响应所有群消息"),
|
|
517
|
+
),
|
|
518
|
+
h("small", { className: "bxf-responseModeHelp dim-responseModeHelp" },
|
|
519
|
+
current === "mention"
|
|
520
|
+
? permissionGranted
|
|
521
|
+
? "私聊始终响应;群聊仅处理明确 @当前机器人的消息。群消息权限已开通,再次切换无需授权。"
|
|
522
|
+
: "私聊始终响应;群聊仅处理明确 @当前机器人的消息。选择全部消息后会打开飞书官方授权流程。"
|
|
523
|
+
: permissionGranted
|
|
524
|
+
? "已开通“获取群组中所有消息”权限(im:message.group_msg);机器人会处理群聊中的所有可见消息。"
|
|
525
|
+
: "尚未确认“获取群组中所有消息”权限,请完成飞书授权。"),
|
|
526
|
+
current === "all"
|
|
527
|
+
? h("div", { className: "bxf-responseModePermissionAction dim-responseModePermissionAction" },
|
|
528
|
+
h(Button, {
|
|
529
|
+
className: "bxf-responseModePermissionButton",
|
|
530
|
+
size: "small",
|
|
531
|
+
disabled: disabled || authorizationDisabled || saving || authorizing,
|
|
532
|
+
"aria-busy": authorizing ? "true" : undefined,
|
|
533
|
+
"aria-label": permissionGranted ? "重新授权群消息权限" : "授权群消息权限",
|
|
534
|
+
onClick: () => { void authorize(); },
|
|
535
|
+
}, authorizing ? "正在准备…" : permissionGranted ? "重新授权" : "去授权"))
|
|
536
|
+
: null,
|
|
537
|
+
error ? h("p", {
|
|
538
|
+
className: "bxf-responseModeError dim-responseModeError",
|
|
539
|
+
role: "alert",
|
|
540
|
+
}, error) : null,
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
|
|
411
544
|
export function BotCard({
|
|
412
545
|
connection,
|
|
413
546
|
busy,
|
|
414
547
|
repairDisabled,
|
|
548
|
+
provisionContent,
|
|
549
|
+
provisionRef,
|
|
415
550
|
actionError,
|
|
416
551
|
testNotice,
|
|
417
552
|
removing,
|
|
418
553
|
onReconnect,
|
|
419
554
|
onRepairCallback,
|
|
420
555
|
onWorkspaceSave,
|
|
556
|
+
onAgentPresetSave,
|
|
557
|
+
onGroupResponseModeSave,
|
|
558
|
+
onGroupMessagePermissionAuthorize,
|
|
421
559
|
onRequestRemove,
|
|
422
560
|
onConfirmRemove,
|
|
423
561
|
onCancelRemove,
|
|
@@ -468,6 +606,28 @@ export function BotCard({
|
|
|
468
606
|
disabled: Boolean(busy),
|
|
469
607
|
onSave: onWorkspaceSave,
|
|
470
608
|
}),
|
|
609
|
+
h(AgentPresetEditor, {
|
|
610
|
+
agentPreset: connection.agentPreset,
|
|
611
|
+
disabled: Boolean(busy),
|
|
612
|
+
onSave: onAgentPresetSave,
|
|
613
|
+
}),
|
|
614
|
+
h(GroupResponseModeEditor, {
|
|
615
|
+
value: connection.groupResponseMode,
|
|
616
|
+
permissionGranted: connection.groupMessagePermissionGranted,
|
|
617
|
+
disabled: Boolean(busy),
|
|
618
|
+
authorizationDisabled: repairDisabled,
|
|
619
|
+
onSave: onGroupResponseModeSave,
|
|
620
|
+
onAuthorize: onGroupMessagePermissionAuthorize,
|
|
621
|
+
}),
|
|
622
|
+
provisionContent
|
|
623
|
+
? h("section", {
|
|
624
|
+
className: "bxf-botProvision dim-botProvision",
|
|
625
|
+
"aria-label": `${bot.name}的飞书授权流程`,
|
|
626
|
+
"data-provision-for": connection.botId,
|
|
627
|
+
ref: provisionRef,
|
|
628
|
+
tabIndex: -1,
|
|
629
|
+
}, provisionContent)
|
|
630
|
+
: null,
|
|
471
631
|
h("div", { className: "bxf-connectedFooter dim-cardFooter" },
|
|
472
632
|
summary ? h("div", { className: "bxf-healthSummary dim-cardSummary", "data-error": actionError || connection.error ? "true" : undefined },
|
|
473
633
|
summary) : null,
|
|
@@ -515,15 +675,23 @@ function BotList(props) {
|
|
|
515
675
|
h(BotCard, {
|
|
516
676
|
connection: bot,
|
|
517
677
|
busy: props.busyByBot[bot.botId]
|
|
518
|
-
?? (
|
|
519
|
-
&& props.provisioning.botId === bot.botId ?
|
|
678
|
+
?? (isTargetedAppUpdate(props.provisioning)
|
|
679
|
+
&& props.provisioning.botId === bot.botId ? props.provisioning.operation : undefined),
|
|
520
680
|
repairDisabled: Boolean(props.provisioning),
|
|
681
|
+
provisionContent: isTargetedAppUpdate(props.provisioning)
|
|
682
|
+
&& props.provisioning.botId === bot.botId
|
|
683
|
+
? props.provisionContent
|
|
684
|
+
: null,
|
|
685
|
+
provisionRef: props.provisionRef,
|
|
521
686
|
actionError: props.errorsByBot[bot.botId],
|
|
522
687
|
testNotice: props.testNoticesByBot[bot.botId],
|
|
523
688
|
removing: props.removeTargetId === bot.botId,
|
|
524
689
|
onReconnect: () => props.onReconnect(bot),
|
|
525
690
|
onRepairCallback: () => props.onRepairCallback(bot),
|
|
526
691
|
onWorkspaceSave: (workspace) => props.onWorkspaceSave(bot, workspace),
|
|
692
|
+
onAgentPresetSave: (agentPreset) => props.onAgentPresetSave(bot, agentPreset),
|
|
693
|
+
onGroupResponseModeSave: (groupResponseMode) => props.onGroupResponseModeSave(bot, groupResponseMode),
|
|
694
|
+
onGroupMessagePermissionAuthorize: () => props.onGroupMessagePermissionAuthorize(bot),
|
|
527
695
|
onRequestRemove: () => props.onRequestRemove(bot),
|
|
528
696
|
onConfirmRemove: () => props.onConfirmRemove(bot),
|
|
529
697
|
onCancelRemove: props.onCancelRemove,
|
|
@@ -575,6 +743,7 @@ export function mergeFeishuSnapshotState(
|
|
|
575
743
|
provisioning,
|
|
576
744
|
pageError: null,
|
|
577
745
|
statusError: null,
|
|
746
|
+
agentPresetCatalog: snapshot.agentPresetCatalog ?? current.agentPresetCatalog,
|
|
578
747
|
};
|
|
579
748
|
}
|
|
580
749
|
|
|
@@ -587,6 +756,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
587
756
|
provisioning: null,
|
|
588
757
|
pageError: null,
|
|
589
758
|
statusError: null,
|
|
759
|
+
agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
|
|
590
760
|
});
|
|
591
761
|
const [pageBusy, setPageBusy] = React.useState(false);
|
|
592
762
|
const [provisionBusy, setProvisionBusy] = React.useState(false);
|
|
@@ -602,6 +772,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
602
772
|
const [focusBotId, setFocusBotId] = React.useState(null);
|
|
603
773
|
const cardRefs = React.useRef(new Map());
|
|
604
774
|
const removeButtonRefs = React.useRef(new Map());
|
|
775
|
+
const targetedProvisionRef = React.useRef(null);
|
|
605
776
|
const addButtonRef = React.useRef(null);
|
|
606
777
|
const mountedRef = React.useRef(true);
|
|
607
778
|
const workspaceFence = useWorkspaceSnapshotFence();
|
|
@@ -692,15 +863,30 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
692
863
|
setFocusBotId(null);
|
|
693
864
|
}, [focusBotId, model.bots]);
|
|
694
865
|
|
|
866
|
+
const targetedProvisionFocusKey = isTargetedAppUpdate(model.provisioning)
|
|
867
|
+
? `${model.provisioning.botId}:${model.provisioning.attemptId ?? "preparing"}:${model.provisioning.phase}`
|
|
868
|
+
: null;
|
|
869
|
+
React.useEffect(() => {
|
|
870
|
+
if (!targetedProvisionFocusKey) return;
|
|
871
|
+
scheduleAnimationFrame(() => {
|
|
872
|
+
const node = targetedProvisionRef.current;
|
|
873
|
+
if (!node) return;
|
|
874
|
+
node.scrollIntoView?.({ block: "nearest", behavior: "smooth" });
|
|
875
|
+
node.focus?.({ preventScroll: true });
|
|
876
|
+
}, "targeted-provision-focus");
|
|
877
|
+
}, [scheduleAnimationFrame, targetedProvisionFocusKey]);
|
|
878
|
+
|
|
695
879
|
const startProvisioning = React.useCallback(async ({
|
|
696
880
|
replace = false,
|
|
697
881
|
operation = FEISHU_REGISTRATION_OPERATIONS.PROVISION,
|
|
698
882
|
bot,
|
|
699
883
|
} = {}) => {
|
|
700
884
|
const repairing = operation === CALLBACK_REPAIR_OPERATION;
|
|
701
|
-
const
|
|
702
|
-
const
|
|
703
|
-
|
|
885
|
+
const grantingGroupMessages = operation === GROUP_MESSAGE_PERMISSION_OPERATION;
|
|
886
|
+
const targetedUpdate = repairing || grantingGroupMessages;
|
|
887
|
+
const botId = targetedUpdate ? bot?.botId ?? model.provisioning?.botId : undefined;
|
|
888
|
+
const botName = targetedUpdate ? bot?.bot?.name ?? model.provisioning?.botName : undefined;
|
|
889
|
+
if (targetedUpdate && !botId) return;
|
|
704
890
|
setCredentialOpen(false);
|
|
705
891
|
setCredentialError(null);
|
|
706
892
|
setProvisionBusy(true);
|
|
@@ -728,13 +914,20 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
728
914
|
// Continue with begin. It is the source of truth for the new attempt.
|
|
729
915
|
}
|
|
730
916
|
}
|
|
917
|
+
const endpoint = repairing
|
|
918
|
+
? FEISHU_ENDPOINTS.beginCallbackRepair
|
|
919
|
+
: grantingGroupMessages
|
|
920
|
+
? FEISHU_ENDPOINTS.beginGroupMessagePermission
|
|
921
|
+
: FEISHU_ENDPOINTS.beginProvisioning;
|
|
731
922
|
const provision = normalizeProvisioning(await invoke(
|
|
732
|
-
|
|
733
|
-
|
|
923
|
+
endpoint,
|
|
924
|
+
targetedUpdate ? { botId } : { locale: "zh-CN" },
|
|
734
925
|
));
|
|
735
|
-
if (
|
|
736
|
-
&& (provision.operation !==
|
|
737
|
-
throw new Error(
|
|
926
|
+
if (targetedUpdate
|
|
927
|
+
&& (provision.operation !== operation || provision.botId !== botId)) {
|
|
928
|
+
throw new Error(grantingGroupMessages
|
|
929
|
+
? "飞书服务返回了不匹配的群消息权限二维码"
|
|
930
|
+
: "飞书服务返回了不匹配的卡片修复二维码");
|
|
738
931
|
}
|
|
739
932
|
const timestamp = Date.now();
|
|
740
933
|
setNow(timestamp);
|
|
@@ -750,7 +943,9 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
750
943
|
}));
|
|
751
944
|
announce(repairing
|
|
752
945
|
? `${botName ?? "机器人"}的修复二维码已生成,请使用飞书扫码。`
|
|
753
|
-
:
|
|
946
|
+
: grantingGroupMessages
|
|
947
|
+
? `${botName ?? "机器人"}的群消息权限二维码已生成,请使用飞书确认。`
|
|
948
|
+
: "授权二维码已生成,请使用飞书扫码。");
|
|
754
949
|
} catch (error) {
|
|
755
950
|
setModel((current) => ({
|
|
756
951
|
...current,
|
|
@@ -801,7 +996,9 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
801
996
|
const activeProvision = model.provisioning;
|
|
802
997
|
const attemptId = activeProvision?.attemptId;
|
|
803
998
|
const repairing = isCallbackRepair(activeProvision);
|
|
804
|
-
const
|
|
999
|
+
const grantingGroupMessages = isGroupMessagePermission(activeProvision);
|
|
1000
|
+
const targetedUpdate = isTargetedAppUpdate(activeProvision);
|
|
1001
|
+
const targetBot = targetedUpdate
|
|
805
1002
|
? model.bots.find((bot) => bot.botId === activeProvision?.botId)
|
|
806
1003
|
: undefined;
|
|
807
1004
|
setProvisionBusy(true);
|
|
@@ -809,8 +1006,8 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
809
1006
|
const result = attemptId
|
|
810
1007
|
? normalizePollResult(await invoke(FEISHU_ENDPOINTS.cancelProvisioning, { attemptId }))
|
|
811
1008
|
: null;
|
|
812
|
-
if (
|
|
813
|
-
if (result.operation !==
|
|
1009
|
+
if (targetedUpdate && result) {
|
|
1010
|
+
if (result.operation !== activeProvision.operation
|
|
814
1011
|
|| result.botId !== activeProvision.botId) {
|
|
815
1012
|
throw new Error("飞书服务返回了不匹配的注册进度");
|
|
816
1013
|
}
|
|
@@ -827,23 +1024,29 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
827
1024
|
},
|
|
828
1025
|
}
|
|
829
1026
|
: current);
|
|
830
|
-
announce(
|
|
1027
|
+
announce(grantingGroupMessages
|
|
1028
|
+
? "权限配置已提交,正在启用全部消息模式并重连此机器人;此阶段无法取消,其他机器人不会中断。"
|
|
1029
|
+
: "配置已提交,正在验证卡片按钮回调并重连此机器人;此阶段无法取消,其他机器人不会中断。");
|
|
831
1030
|
return;
|
|
832
1031
|
}
|
|
833
1032
|
if (result.status === "connected") {
|
|
834
1033
|
const targetBotName = targetBot?.bot.name ?? activeProvision.botName ?? "机器人";
|
|
835
1034
|
setModel((current) => ({ ...current, provisioning: null }));
|
|
836
|
-
announce(
|
|
1035
|
+
announce(grantingGroupMessages
|
|
1036
|
+
? `${targetBotName}已开通群消息权限,并启用“响应所有群消息”。`
|
|
1037
|
+
: `${targetBotName}的卡片按钮已修复。`);
|
|
837
1038
|
if (activeProvision.botId) setFocusBotId(activeProvision.botId);
|
|
838
1039
|
await loadStatus({ silent: true, restoreProvisioning: false });
|
|
839
1040
|
return;
|
|
840
1041
|
}
|
|
841
1042
|
}
|
|
842
1043
|
setModel((current) => ({ ...current, provisioning: null }));
|
|
843
|
-
announce(repairing
|
|
1044
|
+
announce(repairing
|
|
1045
|
+
? "已取消卡片按钮修复。"
|
|
1046
|
+
: grantingGroupMessages ? "已取消群消息权限授权。" : "已取消添加机器人。");
|
|
844
1047
|
await loadStatus({ silent: true, restoreProvisioning: false });
|
|
845
1048
|
scheduleAnimationFrame(() => {
|
|
846
|
-
if (
|
|
1049
|
+
if (targetedUpdate && activeProvision.botId) {
|
|
847
1050
|
cardRefs.current.get(activeProvision.botId)?.focus();
|
|
848
1051
|
} else {
|
|
849
1052
|
addButtonRef.current?.focus();
|
|
@@ -900,7 +1103,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
900
1103
|
controller.signal,
|
|
901
1104
|
));
|
|
902
1105
|
if (result.operation !== provision.operation
|
|
903
|
-
|| (
|
|
1106
|
+
|| (isTargetedAppUpdate(provision) && result.botId !== provision.botId)) {
|
|
904
1107
|
throw new Error("飞书服务返回了不匹配的注册进度");
|
|
905
1108
|
}
|
|
906
1109
|
if (result.status === "connected") {
|
|
@@ -909,7 +1112,9 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
909
1112
|
if (!snapshot) {
|
|
910
1113
|
throw new Error(isCallbackRepair(provision)
|
|
911
1114
|
? "卡片按钮已更新,但暂时无法确认机器人连接状态"
|
|
912
|
-
:
|
|
1115
|
+
: isGroupMessagePermission(provision)
|
|
1116
|
+
? "群消息权限已更新,但暂时无法确认机器人连接状态"
|
|
1117
|
+
: "机器人已经创建,但暂时无法确认连接状态");
|
|
913
1118
|
}
|
|
914
1119
|
if (!targetBot?.connected) {
|
|
915
1120
|
setModel((current) => current.provisioning?.attemptId === provision.attemptId
|
|
@@ -920,15 +1125,21 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
920
1125
|
setModel((current) => ({ ...current, provisioning: null }));
|
|
921
1126
|
announce(isCallbackRepair(provision)
|
|
922
1127
|
? `${targetBot.bot.name}的卡片按钮已修复。`
|
|
923
|
-
:
|
|
924
|
-
? `${targetBot.bot.name}
|
|
925
|
-
:
|
|
1128
|
+
: isGroupMessagePermission(provision)
|
|
1129
|
+
? `${targetBot.bot.name}已开通群消息权限,并启用“响应所有群消息”。`
|
|
1130
|
+
: targetBot
|
|
1131
|
+
? `${targetBot.bot.name}已连接,可以在飞书中开始聊天。`
|
|
1132
|
+
: "新飞书机器人已连接,可以开始聊天。");
|
|
926
1133
|
if (result.botId) setFocusBotId(result.botId);
|
|
927
1134
|
return;
|
|
928
1135
|
}
|
|
929
1136
|
if (result.status === "failed") {
|
|
930
1137
|
const error = new Error(result.message
|
|
931
|
-
?? (isCallbackRepair(provision)
|
|
1138
|
+
?? (isCallbackRepair(provision)
|
|
1139
|
+
? "飞书卡片按钮修复失败"
|
|
1140
|
+
: isGroupMessagePermission(provision)
|
|
1141
|
+
? "飞书群消息权限开通失败"
|
|
1142
|
+
: "飞书应用创建失败"));
|
|
932
1143
|
error.code = "FEISHU_PROVISION_FAILED";
|
|
933
1144
|
throw error;
|
|
934
1145
|
}
|
|
@@ -1067,6 +1278,76 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1067
1278
|
}
|
|
1068
1279
|
}, [invoke, loadStatus, mergeSnapshot, setBotBusy, setBotError, workspaceFence]);
|
|
1069
1280
|
|
|
1281
|
+
const saveAgentPreset = React.useCallback(async (connection, agentPreset) => {
|
|
1282
|
+
const { botId } = connection;
|
|
1283
|
+
const snapshotVersion = workspaceFence.beginMutation();
|
|
1284
|
+
setBotBusy(botId, "preset");
|
|
1285
|
+
setBotError(botId, null);
|
|
1286
|
+
try {
|
|
1287
|
+
const snapshot = normalizeBotsSnapshot(await invoke(
|
|
1288
|
+
FEISHU_ENDPOINTS.setAgentPreset,
|
|
1289
|
+
{ botId, agentPreset },
|
|
1290
|
+
));
|
|
1291
|
+
if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
1292
|
+
mergeSnapshot(snapshot);
|
|
1293
|
+
}
|
|
1294
|
+
} finally {
|
|
1295
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
1296
|
+
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
|
|
1297
|
+
if (mountedRef.current) setBotBusy(botId, null);
|
|
1298
|
+
}
|
|
1299
|
+
}, [invoke, loadStatus, mergeSnapshot, setBotBusy, setBotError, workspaceFence]);
|
|
1300
|
+
|
|
1301
|
+
const authorizeGroupMessages = React.useCallback(async (connection) => {
|
|
1302
|
+
const { botId } = connection;
|
|
1303
|
+
if (model.provisioning) {
|
|
1304
|
+
throw new Error("请先完成当前飞书授权操作,再开通群消息权限。");
|
|
1305
|
+
}
|
|
1306
|
+
setRemoveTargetId(null);
|
|
1307
|
+
setBotError(botId, null);
|
|
1308
|
+
setTestNoticesByBot((current) => {
|
|
1309
|
+
const next = { ...current };
|
|
1310
|
+
delete next[botId];
|
|
1311
|
+
return next;
|
|
1312
|
+
});
|
|
1313
|
+
await startProvisioning({
|
|
1314
|
+
operation: GROUP_MESSAGE_PERMISSION_OPERATION,
|
|
1315
|
+
bot: connection,
|
|
1316
|
+
});
|
|
1317
|
+
}, [model.provisioning, setBotError, startProvisioning]);
|
|
1318
|
+
|
|
1319
|
+
const saveGroupResponseMode = React.useCallback(async (connection, groupResponseMode) => {
|
|
1320
|
+
const { botId } = connection;
|
|
1321
|
+
if (groupResponseMode === "all" && connection.groupMessagePermissionGranted !== true) {
|
|
1322
|
+
await authorizeGroupMessages(connection);
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
const snapshotVersion = workspaceFence.beginMutation();
|
|
1326
|
+
setBotBusy(botId, "group-response-mode");
|
|
1327
|
+
setBotError(botId, null);
|
|
1328
|
+
try {
|
|
1329
|
+
const snapshot = normalizeBotsSnapshot(await invoke(
|
|
1330
|
+
FEISHU_ENDPOINTS.setGroupResponseMode,
|
|
1331
|
+
{ botId, groupResponseMode },
|
|
1332
|
+
));
|
|
1333
|
+
if (mountedRef.current && workspaceFence.canCommitMutation(snapshotVersion)) {
|
|
1334
|
+
mergeSnapshot(snapshot);
|
|
1335
|
+
}
|
|
1336
|
+
} finally {
|
|
1337
|
+
const shouldRefresh = workspaceFence.endMutation();
|
|
1338
|
+
if (shouldRefresh && mountedRef.current) void loadStatus({ silent: true });
|
|
1339
|
+
if (mountedRef.current) setBotBusy(botId, null);
|
|
1340
|
+
}
|
|
1341
|
+
}, [
|
|
1342
|
+
invoke,
|
|
1343
|
+
authorizeGroupMessages,
|
|
1344
|
+
loadStatus,
|
|
1345
|
+
mergeSnapshot,
|
|
1346
|
+
setBotBusy,
|
|
1347
|
+
setBotError,
|
|
1348
|
+
workspaceFence,
|
|
1349
|
+
]);
|
|
1350
|
+
|
|
1070
1351
|
const requestRemove = React.useCallback((connection) => {
|
|
1071
1352
|
setRemoveTargetId(connection.botId);
|
|
1072
1353
|
}, []);
|
|
@@ -1104,6 +1385,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1104
1385
|
}, [announce, invoke, loadStatus, mergeSnapshot, scheduleAnimationFrame, setBotBusy, setBotError, workspaceFence]);
|
|
1105
1386
|
|
|
1106
1387
|
const provision = model.provisioning;
|
|
1388
|
+
const targetedProvisioning = isTargetedAppUpdate(provision);
|
|
1107
1389
|
const provisionBot = provision?.botId
|
|
1108
1390
|
? model.bots.find((bot) => bot.botId === provision.botId)
|
|
1109
1391
|
?? { botId: provision.botId, bot: { name: provision.botName ?? "此机器人" } }
|
|
@@ -1129,7 +1411,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1129
1411
|
provisionContent = h(ProvisionProgress, {
|
|
1130
1412
|
phase: "connecting",
|
|
1131
1413
|
provision,
|
|
1132
|
-
onCancel:
|
|
1414
|
+
onCancel: isTargetedAppUpdate(provision) ? undefined : () => void cancelProvisioning(),
|
|
1133
1415
|
busy: provisionBusy,
|
|
1134
1416
|
});
|
|
1135
1417
|
} else if (provision?.phase === "error") {
|
|
@@ -1173,7 +1455,9 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1173
1455
|
else removeButtonRefs.current.delete(botId);
|
|
1174
1456
|
}, []);
|
|
1175
1457
|
|
|
1176
|
-
return h(
|
|
1458
|
+
return h(AgentPresetCatalogContext.Provider, {
|
|
1459
|
+
value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
1460
|
+
}, h("section", { className: "bxf-page dim-channelPage", "aria-label": "飞书机器人设置" },
|
|
1177
1461
|
h(Heading, {
|
|
1178
1462
|
totals: model.totals,
|
|
1179
1463
|
onAdd: () => void startProvisioning(),
|
|
@@ -1202,7 +1486,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1202
1486
|
})
|
|
1203
1487
|
: h(React.Fragment, null,
|
|
1204
1488
|
credentialContent,
|
|
1205
|
-
provisionContent,
|
|
1489
|
+
targetedProvisioning ? null : provisionContent,
|
|
1206
1490
|
model.bots.length === 0 && !provision && !credentialOpen
|
|
1207
1491
|
? h(EmptyView, { onStart: () => void startProvisioning(), busy: provisionBusy })
|
|
1208
1492
|
: null,
|
|
@@ -1214,9 +1498,14 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1214
1498
|
testNoticesByBot,
|
|
1215
1499
|
removeTargetId,
|
|
1216
1500
|
provisioning: provision,
|
|
1501
|
+
provisionContent,
|
|
1502
|
+
provisionRef: targetedProvisionRef,
|
|
1217
1503
|
onReconnect: (bot) => void reconnectOneBot(bot),
|
|
1218
1504
|
onRepairCallback: repairCallback,
|
|
1219
1505
|
onWorkspaceSave: saveWorkspace,
|
|
1506
|
+
onAgentPresetSave: saveAgentPreset,
|
|
1507
|
+
onGroupResponseModeSave: saveGroupResponseMode,
|
|
1508
|
+
onGroupMessagePermissionAuthorize: authorizeGroupMessages,
|
|
1220
1509
|
onRequestRemove: requestRemove,
|
|
1221
1510
|
onConfirmRemove: (bot) => void confirmRemove(bot),
|
|
1222
1511
|
onCancelRemove: cancelRemove,
|
|
@@ -1225,7 +1514,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1225
1514
|
})
|
|
1226
1515
|
: null,
|
|
1227
1516
|
),
|
|
1228
|
-
);
|
|
1517
|
+
));
|
|
1229
1518
|
}
|
|
1230
1519
|
|
|
1231
1520
|
export function apply(ctx) {
|