@xmanrui/dsh-im 4.22.0 → 4.23.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 -3
- package/README.md +3 -3
- package/lib/client.js +2505 -1152
- package/lib/index.js +290 -299
- package/package.json +8 -1
- package/plugin-src/client/channel-card-meta.js +2 -0
- package/plugin-src/client/channel-logos.js +11 -0
- package/plugin-src/client/channels/dingtalk/api.js +5 -1
- package/plugin-src/client/channels/dingtalk/index.js +15 -13
- package/plugin-src/client/channels/email/api.js +52 -0
- package/plugin-src/client/channels/email/index.js +728 -0
- package/plugin-src/client/channels/email/styles.js +51 -0
- package/plugin-src/client/channels/feishu/api.js +4 -2
- package/plugin-src/client/channels/feishu/index.js +16 -3
- package/plugin-src/client/channels/imessage/index.js +2 -1
- package/plugin-src/client/channels/office/api.js +2 -0
- package/plugin-src/client/channels/office/index.js +6 -5
- package/plugin-src/client/channels/qq/api.js +7 -0
- package/plugin-src/client/channels/qq/index.js +16 -3
- package/plugin-src/client/channels/shared/token-api.js +5 -1
- package/plugin-src/client/channels/shared/token-channel.js +28 -3
- package/plugin-src/client/channels/slack/index.js +2 -1
- package/plugin-src/client/channels/wecom/api.js +7 -1
- package/plugin-src/client/channels/wecom/index.js +16 -3
- package/plugin-src/client/channels/wecom-app/api.js +6 -1
- package/plugin-src/client/channels/wecom-app/index.js +16 -3
- package/plugin-src/client/channels/weixin/api.js +2 -1
- package/plugin-src/client/channels/weixin/connection-error.js +1 -71
- package/plugin-src/client/channels/whatsapp/api.js +5 -0
- package/plugin-src/client/channels/whatsapp/index.js +16 -3
- package/plugin-src/client/connection-error.js +87 -0
- package/plugin-src/client/global-settings.js +81 -1
- package/plugin-src/client/i18n.js +61 -0
- package/plugin-src/client/index.js +30 -2
- package/plugin-src/client/last-message-error.js +2 -1
- package/plugin-src/client/styles.js +6 -0
- package/plugin-src/host/build.mjs +4 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +2 -0
- package/plugin-src/host/channels/dingtalk/rpc.mjs +7 -4
- package/plugin-src/host/channels/email/availability.mjs +60 -0
- package/plugin-src/host/channels/email/index.mjs +36 -0
- package/plugin-src/host/channels/email/production.mjs +58 -0
- package/plugin-src/host/channels/email/rpc.mjs +163 -0
- package/plugin-src/host/channels/feishu/production.mjs +3 -0
- package/plugin-src/host/channels/feishu/rpc.mjs +7 -4
- package/plugin-src/host/channels/imessage/rpc.mjs +13 -6
- package/plugin-src/host/channels/office/rpc.mjs +3 -1
- package/plugin-src/host/channels/qq/production.mjs +2 -0
- package/plugin-src/host/channels/qq/rpc.mjs +5 -3
- package/plugin-src/host/channels/shared/production.mjs +52 -5
- package/plugin-src/host/channels/shared/rpc.mjs +5 -3
- package/plugin-src/host/channels/shared/startup-error.mjs +4 -3
- package/plugin-src/host/channels/shared/startup.mjs +11 -6
- package/plugin-src/host/channels/slack/production.mjs +2 -0
- package/plugin-src/host/channels/slack/rpc.mjs +5 -3
- package/plugin-src/host/channels/wecom/production.mjs +2 -0
- package/plugin-src/host/channels/wecom/rpc.mjs +5 -3
- package/plugin-src/host/channels/wecom-app/production.mjs +2 -0
- package/plugin-src/host/channels/wecom-app/rpc.mjs +5 -3
- package/plugin-src/host/channels/weixin/production.mjs +2 -0
- package/plugin-src/host/channels/whatsapp/production.mjs +2 -0
- package/plugin-src/host/channels/whatsapp/rpc.mjs +5 -3
- package/plugin-src/host/delivery-adapter.mjs +11 -0
- package/plugin-src/host/image-input-rpc.mjs +24 -0
- package/plugin-src/host/inbound-ttl-rpc.mjs +6 -1
- package/plugin-src/host/index.mjs +3 -0
- package/plugin-src/management-rpc.mjs +12 -2
- package/scripts/verify-package.mjs +3 -1
- package/src/channels/dingtalk/connection-error.mjs +5 -12
- package/src/channels/dingtalk/device-auth.mjs +4 -1
- package/src/channels/dingtalk/dingtalk-bridge.mjs +6 -4
- package/src/channels/dingtalk/dingtalk-controller.mjs +60 -45
- package/src/channels/dingtalk/dingtalk-runtime.mjs +20 -4
- package/src/channels/discord/discord-api.mjs +3 -3
- package/src/channels/discord/discord-runtime.mjs +18 -12
- package/src/channels/email/config-store.mjs +194 -0
- package/src/channels/email/email-api.mjs +20 -0
- package/src/channels/email/email-bridge.mjs +17 -0
- package/src/channels/email/email-controller.mjs +824 -0
- package/src/channels/email/email-runtime.mjs +692 -0
- package/src/channels/email/harness-client.mjs +7 -0
- package/src/channels/email/mail-format.mjs +91 -0
- package/src/channels/email/state-store.mjs +176 -0
- package/src/channels/email/transport.mjs +64 -0
- package/src/channels/email/transports/agent-mail.mjs +593 -0
- package/src/channels/email/transports/agently-cli.mjs +288 -0
- package/src/channels/email/transports/imap-smtp.mjs +165 -0
- package/src/channels/feishu/bridge.mjs +8 -7
- package/src/channels/feishu/feishu-runtime.mjs +15 -8
- package/src/channels/feishu/group-message-permission-manager.mjs +2 -1
- package/src/channels/feishu/message-utils.mjs +3 -2
- package/src/channels/feishu/multi-bot-controller.mjs +56 -47
- package/src/channels/feishu/registration-manager.mjs +8 -4
- package/src/channels/feishu/repair-manager.mjs +2 -1
- package/src/channels/imessage/runtime.mjs +9 -5
- package/src/channels/office/office-controller.mjs +23 -12
- package/src/channels/office/office-runtime.mjs +6 -3
- package/src/channels/office/office-transport.mjs +1 -0
- package/src/channels/qq/qq-bridge.mjs +7 -5
- package/src/channels/qq/qq-controller.mjs +56 -42
- package/src/channels/qq/qq-runtime.mjs +16 -11
- package/src/channels/shared/bot-workspace-store.mjs +29 -10
- package/src/channels/shared/connection-error.mjs +191 -0
- package/src/channels/shared/connection-test.mjs +3 -1
- package/src/channels/shared/conversation-state-store.mjs +46 -1
- package/src/channels/shared/diagnostic-details.mjs +95 -0
- package/src/channels/shared/harness-client.mjs +63 -18
- package/src/channels/shared/i18n-en/diagnostics.mjs +55 -0
- package/src/channels/shared/i18n-en/email.mjs +25 -0
- package/src/channels/shared/i18n-en/image-input.mjs +17 -0
- package/src/channels/shared/i18n-en/shared-a.mjs +1 -0
- package/src/channels/shared/i18n-en/shared-c.mjs +1 -1
- package/src/channels/shared/i18n-en.mjs +6 -0
- package/src/channels/shared/image-input-policy.mjs +35 -0
- package/src/channels/shared/image-input-settings-store.mjs +60 -0
- package/src/channels/shared/image-input.mjs +124 -0
- package/src/channels/shared/image-prompt.mjs +15 -7
- package/src/channels/shared/message-failure.mjs +12 -0
- package/src/channels/shared/semantic/reply-reference.mjs +3 -3
- package/src/channels/shared/text-harness-bridge.mjs +90 -11
- package/src/channels/shared/token-bot-controller.mjs +52 -47
- package/src/channels/slack/slack-api.mjs +3 -3
- package/src/channels/slack/slack-controller.mjs +59 -51
- package/src/channels/slack/slack-runtime.mjs +18 -12
- package/src/channels/telegram/telegram-api.mjs +2 -2
- package/src/channels/telegram/telegram-runtime.mjs +17 -13
- package/src/channels/wecom/wecom-bridge.mjs +15 -6
- package/src/channels/wecom/wecom-controller.mjs +51 -37
- package/src/channels/wecom/wecom-runtime.mjs +10 -6
- package/src/channels/wecom-app/wecom-app-bridge.mjs +7 -5
- package/src/channels/wecom-app/wecom-app-controller.mjs +50 -37
- package/src/channels/wecom-app/wecom-app-runtime.mjs +2 -1
- package/src/channels/weixin/connection-error.mjs +26 -92
- package/src/channels/weixin/diagnostic-details.mjs +1 -63
- package/src/channels/weixin/weixin-bridge.mjs +7 -5
- package/src/channels/whatsapp/whatsapp-controller.mjs +34 -17
- package/src/channels/whatsapp/whatsapp-runtime.mjs +35 -8
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const EMAIL_STYLE_ID = 'xmanrui-dsh-im-email-settings';
|
|
2
|
+
const CSS = String.raw`
|
|
3
|
+
.dim-pageEmail { --ddt-accent: #2f6fd0; --ddt-accent-deep: #24559f; --ddt-accent-wash: #eef4fd; }
|
|
4
|
+
.dim-avatarEmail { color: #fff; background: linear-gradient(180deg, #6ea8f5 0%, #3d7fe0 50%, #24559f 100%); box-shadow: inset 0 1px 1px rgb(255 255 255 / 45%), 0 1px 3px rgb(31 35 41 / 12%); }
|
|
5
|
+
.dim-avatarEmail svg { display: block; }
|
|
6
|
+
.dim-emailPanel { padding: 20px; }
|
|
7
|
+
.dim-emailPanel p { color: var(--dsw-alias-label-secondary, #646a73); }
|
|
8
|
+
.dim-emailFields { display: grid; gap: 12px; margin: 16px 0; }
|
|
9
|
+
.dim-emailField { display: grid; gap: 6px; }
|
|
10
|
+
.dim-emailField > span { font-size: 13px; color: var(--dsw-alias-label-secondary, #646a73); }
|
|
11
|
+
.dim-emailField input, .dim-emailField select, .dim-emailField textarea {
|
|
12
|
+
padding: 8px 10px; border: 1px solid var(--dsw-alias-border, #dee0e3); border-radius: 6px;
|
|
13
|
+
background: var(--dsw-alias-bg-base, #fff); color: inherit; font: inherit;
|
|
14
|
+
}
|
|
15
|
+
.dim-emailField textarea { min-height: 76px; resize: vertical; }
|
|
16
|
+
.dim-emailHint { font-size: 12px; color: var(--dsw-alias-label-tertiary, #8f959e); }
|
|
17
|
+
.dim-emailGrid { display: grid; grid-template-columns: 1fr 120px; gap: 12px; }
|
|
18
|
+
.dim-emailBinding { margin-top: 20px; border-top: 1px solid var(--dsw-alias-border, #dee0e3); padding-top: 16px; }
|
|
19
|
+
.dim-emailBinding h4 { margin: 0 0 4px; font-size: 14px; }
|
|
20
|
+
.dim-emailBindingHead { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
21
|
+
/* The sender address wraps above its picker instead of squeezing the select
|
|
22
|
+
into a narrow column, so a long session title stays fully readable. */
|
|
23
|
+
.dim-emailBindingRow {
|
|
24
|
+
display: grid;
|
|
25
|
+
grid-template-columns: minmax(0, 1fr);
|
|
26
|
+
gap: 4px;
|
|
27
|
+
align-items: center;
|
|
28
|
+
padding: 8px 0;
|
|
29
|
+
border-top: 1px dashed var(--dsw-alias-border, #eef0f2);
|
|
30
|
+
}
|
|
31
|
+
.dim-emailBindingRow:first-of-type { border-top: 0; }
|
|
32
|
+
.dim-emailBindingSender { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
|
|
33
|
+
.dim-emailBindingRow select { width: 100%; }
|
|
34
|
+
/* Selects clip their own text; a minimum width keeps the chosen title legible. */
|
|
35
|
+
.dim-emailBinding select { min-width: 0; }
|
|
36
|
+
/* QR authorization block for the Agent mailbox. */
|
|
37
|
+
.dim-emailAuth { display: grid; gap: 8px; padding: 12px; border: 1px solid var(--dsw-alias-border, #dee0e3); border-radius: 8px; }
|
|
38
|
+
.dim-emailAuthPanel { display: grid; gap: 6px; }
|
|
39
|
+
.dim-emailAuthRow { display: grid; grid-template-columns: 72px minmax(0, 1fr); gap: 8px; align-items: center; font-size: 13px; }
|
|
40
|
+
.dim-emailAuthLink { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
41
|
+
/* The full title of the current choice, wrapping instead of clipping. */
|
|
42
|
+
.dim-emailBindingSelected { display: block; white-space: normal; overflow-wrap: anywhere; }
|
|
43
|
+
`;
|
|
44
|
+
export function installEmailStyles() {
|
|
45
|
+
if (typeof document === 'undefined') return () => {};
|
|
46
|
+
const existing = document.querySelector(`style[data-plugin-css="${EMAIL_STYLE_ID}"]`);
|
|
47
|
+
if (existing) return () => {};
|
|
48
|
+
const style = document.createElement('style'); style.dataset.plugin = '@xmanrui/dsh-im';
|
|
49
|
+
style.dataset.pluginCss = EMAIL_STYLE_ID; style.textContent = CSS; document.head.appendChild(style);
|
|
50
|
+
return () => style.remove();
|
|
51
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { diagnosticFields } from '../../../../src/channels/shared/diagnostic-details.mjs';
|
|
1
2
|
import { normalizeBotAlias } from '../../../../src/channels/shared/bot-alias.mjs';
|
|
2
3
|
/**
|
|
3
4
|
* Browser-safe contract for the Feishu Host plugin.
|
|
@@ -119,6 +120,7 @@ export function unwrapRpcResult(result) {
|
|
|
119
120
|
const message = optionalString(result.error?.message) ?? "飞书服务请求失败";
|
|
120
121
|
const error = new Error(message);
|
|
121
122
|
error.code = optionalString(result.error?.code) ?? "FEISHU_RPC_ERROR";
|
|
123
|
+
Object.assign(error, diagnosticFields(result.error));
|
|
122
124
|
throw error;
|
|
123
125
|
}
|
|
124
126
|
return result.value;
|
|
@@ -190,7 +192,7 @@ function normalizeError(value) {
|
|
|
190
192
|
if (!isRecord(value)) return undefined;
|
|
191
193
|
const message = optionalString(value.message);
|
|
192
194
|
if (!message) return undefined;
|
|
193
|
-
return { message, code: optionalString(value.code) };
|
|
195
|
+
return { ...diagnosticFields(value), message, code: optionalString(value.code) };
|
|
194
196
|
}
|
|
195
197
|
|
|
196
198
|
function authoritativeState(value, connected) {
|
|
@@ -386,7 +388,7 @@ export function presentError(error) {
|
|
|
386
388
|
const message = raw
|
|
387
389
|
.replace(/(client[_-]?secret|app[_-]?secret|secret|token)\s*[:=]\s*[^\s,;]+/gi, "$1=••••••")
|
|
388
390
|
.slice(0, 240);
|
|
389
|
-
return { message, code: optionalString(error?.code) };
|
|
391
|
+
return { message, code: optionalString(error?.code), ...diagnosticFields(error) };
|
|
390
392
|
}
|
|
391
393
|
|
|
392
394
|
export function formatRemaining(milliseconds) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConnectionError, normalizeConnectionError } from '../../connection-error.js';
|
|
1
2
|
import { BotName } from '../../bot-alias.js';
|
|
2
3
|
import * as React from "react";
|
|
3
4
|
|
|
@@ -396,7 +397,7 @@ function ProvisionError({ error, provision, onRetry, onCancel, busy }) {
|
|
|
396
397
|
h("h3", null, repairing
|
|
397
398
|
? "权限与回调没有补全完成"
|
|
398
399
|
: grantingGroupMessages ? "群消息权限没有开通完成" : "新机器人没有添加完成"),
|
|
399
|
-
h(
|
|
400
|
+
h(ConnectionError, { error: error }),
|
|
400
401
|
error.code ? h("span", { className: "bxf-errorCode" }, error.code) : null,
|
|
401
402
|
h("div", { className: "bxf-actions dim-viewActions" },
|
|
402
403
|
h(Button, { kind: "primary", onClick: onRetry, disabled: busy },
|
|
@@ -700,6 +701,7 @@ export function BotCard({
|
|
|
700
701
|
}, "移除接入")),
|
|
701
702
|
summary ? h("div", { className: "bxf-healthSummary dim-cardSummary", "data-error": actionError || connection.error ? "true" : undefined },
|
|
702
703
|
summary) : null,
|
|
704
|
+
(actionError || connection.error) ? h(ConnectionError, { error: actionError || connection.error, showMessage: false }) : null,
|
|
703
705
|
connection.lastMessageError ? h(LastMessageErrorSummary, {
|
|
704
706
|
className: "bxf-healthSummary",
|
|
705
707
|
error: connection.lastMessageError,
|
|
@@ -770,7 +772,7 @@ function PageError({ error, onRetry, busy }) {
|
|
|
770
772
|
h("div", { className: "bxf-error dim-inlineError", role: "alert" },
|
|
771
773
|
h("div", null,
|
|
772
774
|
h("h3", null, "无法读取飞书机器人"),
|
|
773
|
-
h(
|
|
775
|
+
h(ConnectionError, { error: error }),
|
|
774
776
|
error.code ? h("span", { className: "bxf-errorCode" }, error.code) : null,
|
|
775
777
|
h("div", { className: "bxf-actions dim-viewActions" },
|
|
776
778
|
h(Button, { kind: "primary", onClick: onRetry, disabled: busy },
|
|
@@ -812,6 +814,7 @@ export function mergeFeishuSnapshotState(
|
|
|
812
814
|
}
|
|
813
815
|
|
|
814
816
|
export function FeishuSettingsTab({ rpcCall }) {
|
|
817
|
+
const [operationError, setOperationError] = React.useState(null);
|
|
815
818
|
const [model, setModel] = React.useState({
|
|
816
819
|
phase: "loading",
|
|
817
820
|
revision: 0,
|
|
@@ -857,7 +860,16 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
857
860
|
}, [scheduleAnimationFrame]);
|
|
858
861
|
|
|
859
862
|
const invoke = React.useCallback(async (endpoint, payload = {}, signal) => {
|
|
860
|
-
|
|
863
|
+
const operation = !['connection.status', 'provision.poll', 'provision.begin', 'provision.cancel'].includes(endpoint);
|
|
864
|
+
if (operation && mountedRef.current) setOperationError(null);
|
|
865
|
+
try {
|
|
866
|
+
const value = unwrapRpcResult(await rpcCall(endpoint, payload, signal));
|
|
867
|
+
if (operation && mountedRef.current) setOperationError(value?.testMessage?.error ?? value?.warnings?.[0] ?? null);
|
|
868
|
+
return value;
|
|
869
|
+
} catch (error) {
|
|
870
|
+
if (endpoint === 'bot.delete' && mountedRef.current && !signal?.aborted && error?.name !== 'AbortError') setOperationError(normalizeConnectionError(error));
|
|
871
|
+
throw error;
|
|
872
|
+
}
|
|
861
873
|
}, [rpcCall]);
|
|
862
874
|
|
|
863
875
|
const mergeSnapshot = React.useCallback((snapshot, { restoreProvisioning = false } = {}) => {
|
|
@@ -1490,6 +1502,7 @@ export function FeishuSettingsTab({ rpcCall }) {
|
|
|
1490
1502
|
}, h(AgentPresetCatalogContext.Provider, {
|
|
1491
1503
|
value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
1492
1504
|
}, h("section", { className: "bxf-page dim-channelPage", "aria-label": "飞书机器人设置" },
|
|
1505
|
+
operationError ? h(ConnectionError, { error: operationError }) : null,
|
|
1493
1506
|
h(Heading, {
|
|
1494
1507
|
totals: model.totals,
|
|
1495
1508
|
onAdd: () => void startProvisioning(),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConnectionError } from '../../connection-error.js';
|
|
1
2
|
import * as React from 'react';
|
|
2
3
|
import { IMessageLogoGlyph } from '../../channel-logos.js';
|
|
3
4
|
import { createTokenChannelSettings } from '../shared/token-channel.js';
|
|
@@ -21,7 +22,7 @@ function PermissionPanel({ permissions, busy, error, onSubmit, onCancel }) {
|
|
|
21
22
|
h('strong', null, automationReady ? '✓ Messages 自动化权限已授予' : '2. 允许自动化控制 Messages'),
|
|
22
23
|
automationReady ? null : h('p', null, '在“系统设置 → 隐私与安全性 → 自动化”中,允许运行 DeepSeek Harness 的应用控制 Messages。'),
|
|
23
24
|
automationReady ? null : h('a', { href: 'x-apple.systempreferences:com.apple.preference.security?Privacy_Automation' }, '打开自动化设置'))),
|
|
24
|
-
error ? h(
|
|
25
|
+
error ? h(ConnectionError, { error: error }) : null,
|
|
25
26
|
h('div', { className: 'ddt-actions dim-viewActions' },
|
|
26
27
|
h('button', { type: 'button', className: 'ddt-button', onClick: onCancel, disabled: busy }, '取消'),
|
|
27
28
|
h('button', { type: 'button', className: 'ddt-button', 'data-kind': 'primary', onClick: () => onSubmit({}), disabled: busy },
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { diagnosticFields } from '../../../../src/channels/shared/diagnostic-details.mjs';
|
|
1
2
|
import {
|
|
2
3
|
OFFICE_PROTOCOL_VERSION,
|
|
3
4
|
OFFICE_RPC_CHANNEL,
|
|
@@ -12,6 +13,7 @@ export function unwrapOfficeRpc(result) {
|
|
|
12
13
|
if (!result.ok) {
|
|
13
14
|
const error = new Error(typeof result.error?.message === 'string' ? result.error.message : 'AI Office 操作失败');
|
|
14
15
|
error.code = typeof result.error?.code === 'string' ? result.error.code : 'office-rpc-error';
|
|
16
|
+
Object.assign(error, diagnosticFields(result.error));
|
|
15
17
|
throw error;
|
|
16
18
|
}
|
|
17
19
|
return result.value;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConnectionError } from '../../connection-error.js';
|
|
1
2
|
import * as React from 'react';
|
|
2
3
|
|
|
3
4
|
import { h } from '../../i18n.js';
|
|
@@ -76,15 +77,15 @@ export function OfficeSettingsTab({ rpcCall, initialStatus }) {
|
|
|
76
77
|
|
|
77
78
|
const load = React.useCallback(async () => {
|
|
78
79
|
try { adopt(await invoke(OFFICE_RPC_ENDPOINTS.status)); setPhase('ready'); setError(''); }
|
|
79
|
-
catch (caught) { setPhase('error'); setError(caught
|
|
80
|
+
catch (caught) { setPhase('error'); setError(caught); }
|
|
80
81
|
}, [adopt, invoke]);
|
|
81
82
|
|
|
82
83
|
React.useEffect(() => { void load(); }, [load]);
|
|
83
84
|
|
|
84
85
|
const run = async (name, operation) => {
|
|
85
86
|
setBusy(name); setError(''); setNotice('');
|
|
86
|
-
try { const value = await operation(); adopt(value); setNotice(name === 'test' ? '连接测试通过。' : '配置已保存。'); }
|
|
87
|
-
catch (caught) { setError(caught
|
|
87
|
+
try { const value = await operation(); adopt(value); if (value?.warnings?.length) setError(value.warnings[0]); setNotice(name === 'test' ? '连接测试通过。' : '配置已保存。'); }
|
|
88
|
+
catch (caught) { setError(caught); }
|
|
88
89
|
finally { setBusy(''); }
|
|
89
90
|
};
|
|
90
91
|
|
|
@@ -128,9 +129,9 @@ export function OfficeSettingsTab({ rpcCall, initialStatus }) {
|
|
|
128
129
|
h('label', { className: 'dof-field', 'data-wide': 'true' }, 'Instruction Preset 映射',
|
|
129
130
|
h('textarea', { value: form.instructionPresets, placeholder: 'action-items=转换为负责人、截止和验收明确的工单', onChange: (event) => setForm({ ...form, instructionPresets: event.target.value }) }),
|
|
130
131
|
h('small', null, '每行 alias=指令;新增 preset 不需要改 Office 代码。'))),
|
|
131
|
-
error ? h(
|
|
132
|
+
error ? h(ConnectionError, { error }) : null,
|
|
132
133
|
notice ? h('p', { className: 'dof-notice', role: 'status' }, notice) : null,
|
|
133
|
-
health.error?.message ? h(
|
|
134
|
+
health.error?.message ? h(ConnectionError, { error: health.error }) : null,
|
|
134
135
|
h('div', { className: 'dof-actions' },
|
|
135
136
|
h(Button, { kind: 'primary', disabled: Boolean(busy), onClick: () => void run('save', () => invoke(OFFICE_RPC_ENDPOINTS.configure, {
|
|
136
137
|
baseUrl: form.baseUrl,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { normalizeConnectionError as normalizeTestError } from '../../connection-error.js';
|
|
2
|
+
import { diagnosticFields } from '../../../../src/channels/shared/diagnostic-details.mjs';
|
|
1
3
|
import { normalizeBotAlias } from '../../../../src/channels/shared/bot-alias.mjs';
|
|
2
4
|
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
|
3
5
|
import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
|
|
@@ -52,6 +54,7 @@ export function unwrapRpcResult(result) {
|
|
|
52
54
|
if (!result.ok) {
|
|
53
55
|
const error = new Error(text(result.error?.message, 'QQ 操作失败'));
|
|
54
56
|
error.code = text(result.error?.code, 'QQ_RPC_ERROR', 80);
|
|
57
|
+
Object.assign(error, diagnosticFields(result.error));
|
|
55
58
|
throw error;
|
|
56
59
|
}
|
|
57
60
|
return result.value;
|
|
@@ -79,6 +82,7 @@ export function normalizeProvisioning(value, now = Date.now()) {
|
|
|
79
82
|
if (qrCodeDataUrl) result.qrCodeDataUrl = qrCodeDataUrl;
|
|
80
83
|
if (id(source.botId)) result.botId = id(source.botId);
|
|
81
84
|
if (isRecord(source.error)) result.error = {
|
|
85
|
+
...diagnosticFields(source.error),
|
|
82
86
|
code: text(source.error.code, 'QQ_PROVISION_FAILED', 80),
|
|
83
87
|
message: text(source.error.message, 'QQ 机器人没有接入完成'),
|
|
84
88
|
};
|
|
@@ -111,6 +115,7 @@ function normalizeBot(value) {
|
|
|
111
115
|
},
|
|
112
116
|
lastMessageError: normalizeLastMessageError(value.lastMessageError),
|
|
113
117
|
error: isRecord(value.error) ? {
|
|
118
|
+
...diagnosticFields(value.error),
|
|
114
119
|
code: text(value.error.code, 'QQ_ACCOUNT_ERROR', 80),
|
|
115
120
|
message: text(value.error.message, 'QQ 连接尚未就绪'),
|
|
116
121
|
} : null,
|
|
@@ -123,6 +128,7 @@ function normalizeTestMessage(value) {
|
|
|
123
128
|
const code = text(value.code, 'test-message-failed', 80);
|
|
124
129
|
return {
|
|
125
130
|
sent: false,
|
|
131
|
+
...(value.error ? { error: normalizeTestError(value.error) } : {}),
|
|
126
132
|
code: TEST_MESSAGE_CODES.has(code) ? code : 'test-message-failed',
|
|
127
133
|
};
|
|
128
134
|
}
|
|
@@ -153,6 +159,7 @@ export function connectionTestFeedback(result) {
|
|
|
153
159
|
|
|
154
160
|
export function presentError(error) {
|
|
155
161
|
return {
|
|
162
|
+
...diagnosticFields(error),
|
|
156
163
|
code: text(error?.code, 'QQ_ERROR', 80),
|
|
157
164
|
message: text(error?.message, 'QQ 操作失败,请稍后重试'),
|
|
158
165
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConnectionError, normalizeConnectionError } from '../../connection-error.js';
|
|
1
2
|
import { BotName } from '../../bot-alias.js';
|
|
2
3
|
import * as React from 'react';
|
|
3
4
|
|
|
@@ -151,7 +152,7 @@ function ProvisionView({ provision, busy, onRetry, onClose }) {
|
|
|
151
152
|
return h('div', { className: 'ddt-card dim-surfaceCard' },
|
|
152
153
|
h('div', { className: 'ddt-inlineError dim-inlineError', role: 'alert' },
|
|
153
154
|
h('h3', null, 'QQ 机器人没有绑定完成'),
|
|
154
|
-
h(
|
|
155
|
+
h(ConnectionError, { error: error }),
|
|
155
156
|
h('span', { className: 'ddt-errorCode' }, error.code),
|
|
156
157
|
h('div', { className: 'ddt-actions dim-viewActions' },
|
|
157
158
|
h(Button, { kind: 'primary', onClick: onRetry, disabled: busy }, '重新生成二维码'),
|
|
@@ -242,6 +243,7 @@ export function AccountCard({
|
|
|
242
243
|
h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
|
|
243
244
|
h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')),
|
|
244
245
|
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
|
246
|
+
account.error ? h(ConnectionError, { error: account.error, showMessage: false }) : null,
|
|
245
247
|
account.lastMessageError ? h(LastMessageErrorSummary, {
|
|
246
248
|
className: 'ddt-summary',
|
|
247
249
|
error: account.lastMessageError,
|
|
@@ -259,6 +261,7 @@ export function AccountCard({
|
|
|
259
261
|
}
|
|
260
262
|
|
|
261
263
|
export function QqSettingsTab({ rpcCall }) {
|
|
264
|
+
const [operationError, setOperationError] = React.useState(null);
|
|
262
265
|
const [model, setModel] = React.useState({
|
|
263
266
|
phase: 'loading', bots: [], totals: { configured: 0, connected: 0 }, error: null,
|
|
264
267
|
agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
|
|
@@ -289,7 +292,16 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
289
292
|
|
|
290
293
|
const invoke = React.useCallback(async (endpoint, payload = {}, signal) => {
|
|
291
294
|
if (typeof rpcCall !== 'function') throw new TypeError('QQ 设置页缺少 RPC 连接');
|
|
292
|
-
|
|
295
|
+
const operation = !['connection.status', 'provision.poll', 'provision.begin', 'provision.cancel'].includes(endpoint);
|
|
296
|
+
if (operation && mounted.current) setOperationError(null);
|
|
297
|
+
try {
|
|
298
|
+
const value = unwrapRpcResult(await rpcCall(endpoint, payload, signal));
|
|
299
|
+
if (operation && mounted.current) setOperationError(value?.testMessage?.error ?? value?.warnings?.[0] ?? null);
|
|
300
|
+
return value;
|
|
301
|
+
} catch (error) {
|
|
302
|
+
if (operation && mounted.current && !signal?.aborted && error?.name !== 'AbortError') setOperationError(normalizeConnectionError(error));
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
293
305
|
}, [rpcCall]);
|
|
294
306
|
|
|
295
307
|
const loadStatus = React.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
|
|
@@ -558,6 +570,7 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
558
570
|
}, h(AgentPresetCatalogContext.Provider, {
|
|
559
571
|
value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
560
572
|
}, h('section', { className: 'ddt-page dqq-page dim-channelPage', 'aria-label': 'QQ 设置' },
|
|
573
|
+
operationError ? h(ConnectionError, { error: operationError }) : null,
|
|
561
574
|
h(Heading, {
|
|
562
575
|
totals: model.totals,
|
|
563
576
|
adding: Boolean(provision),
|
|
@@ -569,7 +582,7 @@ export function QqSettingsTab({ rpcCall }) {
|
|
|
569
582
|
}),
|
|
570
583
|
model.phase === 'loading' ? h(LoadingView)
|
|
571
584
|
: model.phase === 'error'
|
|
572
|
-
? h('div', { className: 'ddt-card dim-surfaceCard' }, h('div', { className: 'ddt-inlineError dim-inlineError' }, h('h3', null, '无法读取 QQ 机器人状态'), h(
|
|
585
|
+
? h('div', { className: 'ddt-card dim-surfaceCard' }, h('div', { className: 'ddt-inlineError dim-inlineError' }, h('h3', null, '无法读取 QQ 机器人状态'), h(ConnectionError, { error: model.error }), h(Button, { onClick: () => void loadStatus() }, '重新读取')))
|
|
573
586
|
: h(React.Fragment, null,
|
|
574
587
|
credentialView,
|
|
575
588
|
provisionView,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { diagnosticFields } from '../../../../src/channels/shared/diagnostic-details.mjs';
|
|
1
2
|
import { normalizeBotAlias } from '../../../../src/channels/shared/bot-alias.mjs';
|
|
2
3
|
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
|
3
4
|
import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
|
|
@@ -49,7 +50,8 @@ export function createTokenChannelApi(channel, connectionSummary, {
|
|
|
49
50
|
if (!result.ok) {
|
|
50
51
|
const error = new Error(text(result.error?.message, `${channel} 操作失败`));
|
|
51
52
|
error.code = text(result.error?.code, `${channel.toUpperCase()}_RPC_ERROR`, 80);
|
|
52
|
-
|
|
53
|
+
Object.assign(error, diagnosticFields(result.error));
|
|
54
|
+
throw error;
|
|
53
55
|
}
|
|
54
56
|
return result.value;
|
|
55
57
|
};
|
|
@@ -85,6 +87,7 @@ export function createTokenChannelApi(channel, connectionSummary, {
|
|
|
85
87
|
},
|
|
86
88
|
lastMessageError: normalizeLastMessageError(value.lastMessageError),
|
|
87
89
|
error: isRecord(value.error) ? {
|
|
90
|
+
...diagnosticFields(value.error),
|
|
88
91
|
code: text(value.error.code, `${channel.toUpperCase()}_ACCOUNT_ERROR`, 80),
|
|
89
92
|
message: text(value.error.message, `${channel}连接尚未就绪`),
|
|
90
93
|
} : null,
|
|
@@ -109,6 +112,7 @@ export function createTokenChannelApi(channel, connectionSummary, {
|
|
|
109
112
|
};
|
|
110
113
|
|
|
111
114
|
const presentError = (error) => ({
|
|
115
|
+
...diagnosticFields(error),
|
|
112
116
|
code: text(error?.code, `${channel.toUpperCase()}_ERROR`, 80),
|
|
113
117
|
message: text(error?.message, `${channel}操作失败,请稍后重试`),
|
|
114
118
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConnectionError, normalizeConnectionError } from '../../connection-error.js';
|
|
1
2
|
import { BotName } from '../../bot-alias.js';
|
|
2
3
|
import * as React from 'react';
|
|
3
4
|
|
|
@@ -82,7 +83,7 @@ export function createTokenChannelSettings(definition) {
|
|
|
82
83
|
accountSettingsEndpoint = null,
|
|
83
84
|
} = definition;
|
|
84
85
|
|
|
85
|
-
function AccountCard({ account, busy, testNotice, removing, onReconnect, onWorkspaceSave, onAliasSave, onModelSave, onAgentPresetSave, onContextEnhancementSave, onAccountSettingsSave, onRequestRemove, onConfirmRemove, onCancelRemove }) {
|
|
86
|
+
function AccountCard({ account, busy, testNotice, removing, onReconnect, onWorkspaceSave, onAliasSave, onModelSave, onAgentPresetSave, onContextEnhancementSave, onAccountSettingsSave, onRequestRemove, onConfirmRemove, onCancelRemove, rpcCall, reload }) {
|
|
86
87
|
const state = busy === 'reconnect' ? 'connecting' : account.state;
|
|
87
88
|
const tone = account.connected ? 'success' : state === 'error' ? 'error' : 'warning';
|
|
88
89
|
const stateLabel = account.connected ? '运行正常' : state === 'connecting' ? '正在连接' : '连接未就绪';
|
|
@@ -144,6 +145,11 @@ export function createTokenChannelSettings(definition) {
|
|
|
144
145
|
account,
|
|
145
146
|
busy: Boolean(busy),
|
|
146
147
|
onSave: onAccountSettingsSave,
|
|
148
|
+
// Channels with extra settings panels (email session binding) call
|
|
149
|
+
// their own endpoints through the same RPC bridge.
|
|
150
|
+
rpcCall,
|
|
151
|
+
endpoints,
|
|
152
|
+
onChanged: reload,
|
|
147
153
|
}) : null,
|
|
148
154
|
h('div', { className: 'ddt-accountFooter dim-cardFooter' },
|
|
149
155
|
h('div', { className: 'dim-cardFooterLayout' },
|
|
@@ -160,6 +166,7 @@ export function createTokenChannelSettings(definition) {
|
|
|
160
166
|
disabled: Boolean(busy),
|
|
161
167
|
}, '移除接入')),
|
|
162
168
|
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
|
169
|
+
account.error ? h(ConnectionError, { error: account.error, showMessage: false }) : null,
|
|
163
170
|
account.lastMessageError ? h(LastMessageErrorSummary, {
|
|
164
171
|
className: 'ddt-summary',
|
|
165
172
|
error: account.lastMessageError,
|
|
@@ -180,6 +187,7 @@ export function createTokenChannelSettings(definition) {
|
|
|
180
187
|
}
|
|
181
188
|
|
|
182
189
|
function SettingsTab({ rpcCall }) {
|
|
190
|
+
const [operationError, setOperationError] = React.useState(null);
|
|
183
191
|
const [model, setModel] = React.useState({
|
|
184
192
|
phase: 'loading', bots: [], totals: { configured: 0, connected: 0 }, error: null,
|
|
185
193
|
agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
|
|
@@ -207,7 +215,16 @@ export function createTokenChannelSettings(definition) {
|
|
|
207
215
|
|
|
208
216
|
const invoke = React.useCallback(async (endpoint, payload = {}, signal) => {
|
|
209
217
|
if (typeof rpcCall !== 'function') throw new TypeError(`${channel} 设置页缺少 RPC 连接`);
|
|
210
|
-
|
|
218
|
+
const operation = !['connection.status', 'provision.poll', 'provision.begin', 'provision.cancel'].includes(endpoint);
|
|
219
|
+
if (operation && mounted.current) setOperationError(null);
|
|
220
|
+
try {
|
|
221
|
+
const value = api.unwrapRpcResult(await rpcCall(endpoint, payload, signal));
|
|
222
|
+
if (operation && mounted.current) setOperationError(value?.testMessage?.error ?? value?.warnings?.[0] ?? null);
|
|
223
|
+
return value;
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (operation && mounted.current && !signal?.aborted && error?.name !== 'AbortError') setOperationError(normalizeConnectionError(error));
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
211
228
|
}, [rpcCall]);
|
|
212
229
|
|
|
213
230
|
const loadStatus = React.useCallback(async ({ signal, silent = false } = {}) => {
|
|
@@ -331,6 +348,8 @@ export function createTokenChannelSettings(definition) {
|
|
|
331
348
|
h('ul', { className: 'ddt-list dim-botList' }, model.bots.map((account) =>
|
|
332
349
|
h('li', { key: account.botId }, h(AccountCard, {
|
|
333
350
|
account,
|
|
351
|
+
rpcCall,
|
|
352
|
+
reload: loadStatus,
|
|
334
353
|
busy: busyByBot[account.botId],
|
|
335
354
|
testNotice: testNoticeByBot[account.botId],
|
|
336
355
|
removing: removeTarget === account.botId,
|
|
@@ -398,6 +417,7 @@ export function createTokenChannelSettings(definition) {
|
|
|
398
417
|
className: `ddt-page ${pageClass} dim-channelPage`,
|
|
399
418
|
'aria-label': `${channel} 设置`,
|
|
400
419
|
},
|
|
420
|
+
operationError ? h(ConnectionError, { error: operationError }) : null,
|
|
401
421
|
h('div', { className: 'ddt-heading' },
|
|
402
422
|
h('div', { className: 'ddt-tools' },
|
|
403
423
|
h('div', { className: 'dim-bindActions' },
|
|
@@ -422,7 +442,7 @@ export function createTokenChannelSettings(definition) {
|
|
|
422
442
|
? h('div', { className: 'ddt-card dim-surfaceCard' },
|
|
423
443
|
h('div', { className: 'ddt-inlineError dim-inlineError' },
|
|
424
444
|
h('h3', null, `无法读取 ${channel} 机器人状态`),
|
|
425
|
-
h(
|
|
445
|
+
h(ConnectionError, { error: model.error }),
|
|
426
446
|
h(Button, { onClick: () => void loadStatus() }, '重新读取')))
|
|
427
447
|
: h(React.Fragment, null,
|
|
428
448
|
credentialOpen ? (CredentialPanel
|
|
@@ -433,6 +453,11 @@ export function createTokenChannelSettings(definition) {
|
|
|
433
453
|
error: credentialError,
|
|
434
454
|
onSubmit: bindCredentials,
|
|
435
455
|
onCancel: () => { setCredentialOpen(false); setCredentialError(null); },
|
|
456
|
+
// A transport that authorizes out of band (the Agent mailbox
|
|
457
|
+
// shows a QR code) drives its own endpoints through the bridge.
|
|
458
|
+
rpcCall,
|
|
459
|
+
endpoints,
|
|
460
|
+
onAuthorized: bindCredentials,
|
|
436
461
|
})
|
|
437
462
|
: h(CredentialBindingPanel, {
|
|
438
463
|
channel,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConnectionError } from '../../connection-error.js';
|
|
1
2
|
import * as React from 'react';
|
|
2
3
|
|
|
3
4
|
import { SlackLogoGlyph } from '../../channel-logos.js';
|
|
@@ -89,7 +90,7 @@ export function SlackCredentialPanel({ busy, error, onSubmit, onCancel }) {
|
|
|
89
90
|
required: true,
|
|
90
91
|
})),
|
|
91
92
|
h('p', { className: 'dsl-tokenHint' }, 'Bot Token 来自 OAuth & Permissions;App Token 来自 Basic Information,并且必须包含 connections:write。')),
|
|
92
|
-
error ? h(
|
|
93
|
+
error ? h(ConnectionError, { error: error }) : null,
|
|
93
94
|
h('div', { className: 'ddt-actions dim-viewActions dim-credentialActions' },
|
|
94
95
|
h('button', {
|
|
95
96
|
type: 'submit',
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { normalizeConnectionError as normalizeTestError } from '../../connection-error.js';
|
|
2
|
+
import { diagnosticFields } from '../../../../src/channels/shared/diagnostic-details.mjs';
|
|
1
3
|
import { normalizeBotAlias } from '../../../../src/channels/shared/bot-alias.mjs';
|
|
2
4
|
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
|
3
5
|
import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
|
|
@@ -53,7 +55,7 @@ function normalizeTestMessage(value) {
|
|
|
53
55
|
const code = value.code === 'test-target-unavailable'
|
|
54
56
|
? 'test-target-unavailable'
|
|
55
57
|
: 'test-message-failed';
|
|
56
|
-
return { sent: false, code };
|
|
58
|
+
return { sent: false, code, ...(value.error ? { error: normalizeTestError(value.error) } : {}) };
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
export function unwrapRpcResult(result) {
|
|
@@ -61,6 +63,7 @@ export function unwrapRpcResult(result) {
|
|
|
61
63
|
if (!result.ok) {
|
|
62
64
|
const error = new Error(text(result.error?.message, '企业微信操作失败'));
|
|
63
65
|
error.code = text(result.error?.code, 'WECOM_RPC_ERROR', 80);
|
|
66
|
+
Object.assign(error, diagnosticFields(result.error));
|
|
64
67
|
throw error;
|
|
65
68
|
}
|
|
66
69
|
return result.value;
|
|
@@ -88,6 +91,7 @@ export function normalizeProvisioning(value, now = Date.now()) {
|
|
|
88
91
|
if (qrCodeDataUrl) result.qrCodeDataUrl = qrCodeDataUrl;
|
|
89
92
|
if (id(source.botId)) result.botId = id(source.botId);
|
|
90
93
|
if (isRecord(source.error)) result.error = {
|
|
94
|
+
...diagnosticFields(source.error),
|
|
91
95
|
code: text(source.error.code, 'WECOM_PROVISION_FAILED', 80),
|
|
92
96
|
message: text(source.error.message, '企业微信机器人没有接入完成'),
|
|
93
97
|
};
|
|
@@ -120,6 +124,7 @@ function normalizeBot(value) {
|
|
|
120
124
|
},
|
|
121
125
|
lastMessageError: normalizeLastMessageError(value.lastMessageError),
|
|
122
126
|
error: isRecord(value.error) ? {
|
|
127
|
+
...diagnosticFields(value.error),
|
|
123
128
|
code: text(value.error.code, 'WECOM_ACCOUNT_ERROR', 80),
|
|
124
129
|
message: text(value.error.message, '企业微信连接尚未就绪'),
|
|
125
130
|
} : null,
|
|
@@ -143,6 +148,7 @@ export function normalizeSnapshot(value) {
|
|
|
143
148
|
|
|
144
149
|
export function presentError(error) {
|
|
145
150
|
return {
|
|
151
|
+
...diagnosticFields(error),
|
|
146
152
|
code: text(error?.code, 'WECOM_ERROR', 80),
|
|
147
153
|
message: text(error?.message, '企业微信操作失败,请稍后重试'),
|
|
148
154
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConnectionError, normalizeConnectionError } from '../../connection-error.js';
|
|
1
2
|
import { BotName } from '../../bot-alias.js';
|
|
2
3
|
import * as React from 'react';
|
|
3
4
|
|
|
@@ -150,7 +151,7 @@ function ProvisionView({ provision, busy, onRetry, onClose }) {
|
|
|
150
151
|
return h('div', { className: 'ddt-card dim-surfaceCard' },
|
|
151
152
|
h('div', { className: 'ddt-inlineError dim-inlineError', role: 'alert' },
|
|
152
153
|
h('h3', null, '企业微信机器人没有绑定完成'),
|
|
153
|
-
h(
|
|
154
|
+
h(ConnectionError, { error: error }),
|
|
154
155
|
h('span', { className: 'ddt-errorCode' }, error.code),
|
|
155
156
|
h('div', { className: 'ddt-actions dim-viewActions' },
|
|
156
157
|
h(Button, { kind: 'primary', onClick: onRetry, disabled: busy }, '重新生成二维码'),
|
|
@@ -241,6 +242,7 @@ export function AccountCard({
|
|
|
241
242
|
h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
|
|
242
243
|
h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')),
|
|
243
244
|
summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
|
|
245
|
+
account.error ? h(ConnectionError, { error: account.error, showMessage: false }) : null,
|
|
244
246
|
account.lastMessageError ? h(LastMessageErrorSummary, {
|
|
245
247
|
className: 'ddt-summary',
|
|
246
248
|
error: account.lastMessageError,
|
|
@@ -258,6 +260,7 @@ export function AccountCard({
|
|
|
258
260
|
}
|
|
259
261
|
|
|
260
262
|
export function WecomSettingsTab({ rpcCall }) {
|
|
263
|
+
const [operationError, setOperationError] = React.useState(null);
|
|
261
264
|
const [model, setModel] = React.useState({
|
|
262
265
|
phase: 'loading', bots: [], totals: { configured: 0, connected: 0 }, error: null,
|
|
263
266
|
agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
|
|
@@ -309,7 +312,16 @@ export function WecomSettingsTab({ rpcCall }) {
|
|
|
309
312
|
|
|
310
313
|
const invoke = React.useCallback(async (endpoint, payload = {}, signal) => {
|
|
311
314
|
if (typeof rpcCall !== 'function') throw new TypeError('企业微信设置页缺少 RPC 连接');
|
|
312
|
-
|
|
315
|
+
const operation = !['connection.status', 'provision.poll', 'provision.begin', 'provision.cancel'].includes(endpoint);
|
|
316
|
+
if (operation && mounted.current) setOperationError(null);
|
|
317
|
+
try {
|
|
318
|
+
const value = unwrapRpcResult(await rpcCall(endpoint, payload, signal));
|
|
319
|
+
if (operation && mounted.current) setOperationError(value?.testMessage?.error ?? value?.warnings?.[0] ?? null);
|
|
320
|
+
return value;
|
|
321
|
+
} catch (error) {
|
|
322
|
+
if (operation && mounted.current && !signal?.aborted && error?.name !== 'AbortError') setOperationError(normalizeConnectionError(error));
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
313
325
|
}, [rpcCall]);
|
|
314
326
|
|
|
315
327
|
const loadStatus = React.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
|
|
@@ -589,6 +601,7 @@ export function WecomSettingsTab({ rpcCall }) {
|
|
|
589
601
|
}, h(AgentPresetCatalogContext.Provider, {
|
|
590
602
|
value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
|
|
591
603
|
}, h('section', { className: 'ddt-page dwecom-page dim-channelPage', 'aria-label': '企业微信设置' },
|
|
604
|
+
operationError ? h(ConnectionError, { error: operationError }) : null,
|
|
592
605
|
h(Heading, {
|
|
593
606
|
totals: model.totals,
|
|
594
607
|
adding: Boolean(provision),
|
|
@@ -601,7 +614,7 @@ export function WecomSettingsTab({ rpcCall }) {
|
|
|
601
614
|
h('div', { className: 'ddt-visuallyHidden', role: 'status', 'aria-live': 'polite' }, notice),
|
|
602
615
|
model.phase === 'loading' ? h(LoadingView)
|
|
603
616
|
: model.phase === 'error'
|
|
604
|
-
? h('div', { className: 'ddt-card dim-surfaceCard' }, h('div', { className: 'ddt-inlineError dim-inlineError' }, h('h3', null, '无法读取企业微信机器人状态'), h(
|
|
617
|
+
? h('div', { className: 'ddt-card dim-surfaceCard' }, h('div', { className: 'ddt-inlineError dim-inlineError' }, h('h3', null, '无法读取企业微信机器人状态'), h(ConnectionError, { error: model.error }), h(Button, { onClick: () => void loadStatus() }, '重新读取')))
|
|
605
618
|
: h(React.Fragment, null,
|
|
606
619
|
credentialView,
|
|
607
620
|
provisionView,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { normalizeConnectionError as normalizeTestError } from '../../connection-error.js';
|
|
2
|
+
import { diagnosticFields } from '../../../../src/channels/shared/diagnostic-details.mjs';
|
|
1
3
|
import { normalizeBotAlias } from '../../../../src/channels/shared/bot-alias.mjs';
|
|
2
4
|
import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
|
|
3
5
|
import { normalizeModelCatalog, normalizeModelSelection, SET_MODEL_ENDPOINT } from '../../model-setting.js';
|
|
@@ -55,7 +57,7 @@ function normalizeTestMessage(value) {
|
|
|
55
57
|
const code = value.code === 'test-target-unavailable'
|
|
56
58
|
? 'test-target-unavailable'
|
|
57
59
|
: 'test-message-failed';
|
|
58
|
-
return { sent: false, code };
|
|
60
|
+
return { sent: false, code, ...(value.error ? { error: normalizeTestError(value.error) } : {}) };
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
export function unwrapRpcResult(result) {
|
|
@@ -63,6 +65,7 @@ export function unwrapRpcResult(result) {
|
|
|
63
65
|
if (!result.ok) {
|
|
64
66
|
const error = new Error(text(result.error?.message, '企业微信应用操作失败'));
|
|
65
67
|
error.code = text(result.error?.code, 'WECOM_APP_RPC_ERROR', 80);
|
|
68
|
+
Object.assign(error, diagnosticFields(result.error));
|
|
66
69
|
throw error;
|
|
67
70
|
}
|
|
68
71
|
return result.value;
|
|
@@ -99,6 +102,7 @@ function normalizeBot(value) {
|
|
|
99
102
|
},
|
|
100
103
|
lastMessageError: normalizeLastMessageError(value.lastMessageError),
|
|
101
104
|
error: isRecord(value.error) ? {
|
|
105
|
+
...diagnosticFields(value.error),
|
|
102
106
|
code: text(value.error.code, 'WECOM_APP_ACCOUNT_ERROR', 80),
|
|
103
107
|
message: text(value.error.message, '企业微信应用连接尚未就绪'),
|
|
104
108
|
} : null,
|
|
@@ -121,6 +125,7 @@ export function normalizeSnapshot(value) {
|
|
|
121
125
|
|
|
122
126
|
export function presentError(error) {
|
|
123
127
|
return {
|
|
128
|
+
...diagnosticFields(error),
|
|
124
129
|
code: text(error?.code, 'WECOM_APP_ERROR', 80),
|
|
125
130
|
message: text(error?.message, '企业微信应用操作失败,请稍后重试'),
|
|
126
131
|
};
|