@xmanrui/dsh-im 4.22.0 → 4.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/README.en.md +5 -4
  2. package/README.md +5 -4
  3. package/lib/client.js +2584 -1153
  4. package/lib/index.js +290 -298
  5. package/package.json +14 -2
  6. package/plugin-src/client/channel-card-meta.js +2 -0
  7. package/plugin-src/client/channel-logos.js +11 -0
  8. package/plugin-src/client/channels/dingtalk/api.js +5 -1
  9. package/plugin-src/client/channels/dingtalk/index.js +15 -13
  10. package/plugin-src/client/channels/email/api.js +52 -0
  11. package/plugin-src/client/channels/email/index.js +728 -0
  12. package/plugin-src/client/channels/email/styles.js +51 -0
  13. package/plugin-src/client/channels/feishu/api.js +4 -2
  14. package/plugin-src/client/channels/feishu/index.js +23 -7
  15. package/plugin-src/client/channels/imessage/index.js +2 -1
  16. package/plugin-src/client/channels/office/api.js +2 -0
  17. package/plugin-src/client/channels/office/index.js +6 -5
  18. package/plugin-src/client/channels/qq/api.js +7 -0
  19. package/plugin-src/client/channels/qq/index.js +16 -3
  20. package/plugin-src/client/channels/shared/token-api.js +8 -1
  21. package/plugin-src/client/channels/shared/token-channel.js +28 -3
  22. package/plugin-src/client/channels/slack/index.js +2 -1
  23. package/plugin-src/client/channels/telegram/index.js +3 -0
  24. package/plugin-src/client/channels/telegram/styles.js +12 -0
  25. package/plugin-src/client/channels/telegram/thinking-traces.js +25 -0
  26. package/plugin-src/client/channels/wecom/api.js +7 -1
  27. package/plugin-src/client/channels/wecom/index.js +16 -3
  28. package/plugin-src/client/channels/wecom-app/api.js +6 -1
  29. package/plugin-src/client/channels/wecom-app/index.js +16 -3
  30. package/plugin-src/client/channels/weixin/api.js +2 -1
  31. package/plugin-src/client/channels/weixin/connection-error.js +1 -71
  32. package/plugin-src/client/channels/whatsapp/api.js +5 -0
  33. package/plugin-src/client/channels/whatsapp/index.js +16 -3
  34. package/plugin-src/client/connection-error.js +87 -0
  35. package/plugin-src/client/global-settings.js +81 -1
  36. package/plugin-src/client/i18n.js +69 -1
  37. package/plugin-src/client/index.js +40 -4
  38. package/plugin-src/client/last-message-error.js +2 -1
  39. package/plugin-src/client/styles.js +8 -2
  40. package/plugin-src/host/build.mjs +4 -0
  41. package/plugin-src/host/channels/dingtalk/production.mjs +2 -0
  42. package/plugin-src/host/channels/dingtalk/rpc.mjs +7 -4
  43. package/plugin-src/host/channels/email/availability.mjs +60 -0
  44. package/plugin-src/host/channels/email/index.mjs +36 -0
  45. package/plugin-src/host/channels/email/production.mjs +58 -0
  46. package/plugin-src/host/channels/email/rpc.mjs +163 -0
  47. package/plugin-src/host/channels/feishu/production.mjs +3 -0
  48. package/plugin-src/host/channels/feishu/rpc.mjs +7 -4
  49. package/plugin-src/host/channels/imessage/rpc.mjs +13 -6
  50. package/plugin-src/host/channels/office/rpc.mjs +3 -1
  51. package/plugin-src/host/channels/qq/production.mjs +2 -0
  52. package/plugin-src/host/channels/qq/rpc.mjs +5 -3
  53. package/plugin-src/host/channels/shared/production.mjs +52 -5
  54. package/plugin-src/host/channels/shared/rpc.mjs +14 -3
  55. package/plugin-src/host/channels/shared/startup-error.mjs +4 -3
  56. package/plugin-src/host/channels/shared/startup.mjs +11 -6
  57. package/plugin-src/host/channels/shared/thinking-traces-rpc.mjs +11 -0
  58. package/plugin-src/host/channels/slack/production.mjs +2 -0
  59. package/plugin-src/host/channels/slack/rpc.mjs +5 -3
  60. package/plugin-src/host/channels/wecom/production.mjs +2 -0
  61. package/plugin-src/host/channels/wecom/rpc.mjs +5 -3
  62. package/plugin-src/host/channels/wecom-app/production.mjs +2 -0
  63. package/plugin-src/host/channels/wecom-app/rpc.mjs +5 -3
  64. package/plugin-src/host/channels/weixin/production.mjs +2 -0
  65. package/plugin-src/host/channels/whatsapp/production.mjs +2 -0
  66. package/plugin-src/host/channels/whatsapp/rpc.mjs +5 -3
  67. package/plugin-src/host/delivery-adapter.mjs +11 -0
  68. package/plugin-src/host/image-input-rpc.mjs +24 -0
  69. package/plugin-src/host/inbound-ttl-rpc.mjs +6 -1
  70. package/plugin-src/host/index.mjs +3 -0
  71. package/plugin-src/host/modern-harness-api.mjs +7 -2
  72. package/plugin-src/management-rpc.mjs +12 -2
  73. package/scripts/verify-package.mjs +13 -5
  74. package/src/channels/dingtalk/connection-error.mjs +5 -12
  75. package/src/channels/dingtalk/device-auth.mjs +4 -1
  76. package/src/channels/dingtalk/dingtalk-bridge.mjs +6 -4
  77. package/src/channels/dingtalk/dingtalk-controller.mjs +60 -45
  78. package/src/channels/dingtalk/dingtalk-runtime.mjs +20 -4
  79. package/src/channels/discord/discord-api.mjs +3 -3
  80. package/src/channels/discord/discord-runtime.mjs +18 -12
  81. package/src/channels/email/config-store.mjs +194 -0
  82. package/src/channels/email/email-api.mjs +20 -0
  83. package/src/channels/email/email-bridge.mjs +17 -0
  84. package/src/channels/email/email-controller.mjs +824 -0
  85. package/src/channels/email/email-runtime.mjs +699 -0
  86. package/src/channels/email/harness-client.mjs +7 -0
  87. package/src/channels/email/mail-format.mjs +91 -0
  88. package/src/channels/email/state-store.mjs +176 -0
  89. package/src/channels/email/transport.mjs +64 -0
  90. package/src/channels/email/transports/agent-mail.mjs +604 -0
  91. package/src/channels/email/transports/agently-cli.mjs +288 -0
  92. package/src/channels/email/transports/imap-smtp.mjs +165 -0
  93. package/src/channels/feishu/bridge.mjs +39 -11
  94. package/src/channels/feishu/feishu-channel.mjs +35 -0
  95. package/src/channels/feishu/feishu-runtime.mjs +15 -8
  96. package/src/channels/feishu/group-message-permission-manager.mjs +2 -1
  97. package/src/channels/feishu/live-cot.mjs +260 -0
  98. package/src/channels/feishu/message-utils.mjs +3 -2
  99. package/src/channels/feishu/multi-bot-controller.mjs +56 -47
  100. package/src/channels/feishu/registration-manager.mjs +8 -4
  101. package/src/channels/feishu/repair-manager.mjs +2 -1
  102. package/src/channels/feishu/slash-command-registry.mjs +17 -0
  103. package/src/channels/feishu/step-push-mode.mjs +10 -4
  104. package/src/channels/imessage/runtime.mjs +9 -5
  105. package/src/channels/office/office-controller.mjs +23 -12
  106. package/src/channels/office/office-runtime.mjs +6 -3
  107. package/src/channels/office/office-transport.mjs +1 -0
  108. package/src/channels/qq/qq-bridge.mjs +7 -5
  109. package/src/channels/qq/qq-controller.mjs +56 -42
  110. package/src/channels/qq/qq-runtime.mjs +16 -11
  111. package/src/channels/shared/bot-workspace-store.mjs +29 -10
  112. package/src/channels/shared/connection-error.mjs +191 -0
  113. package/src/channels/shared/connection-test.mjs +3 -1
  114. package/src/channels/shared/conversation-state-store.mjs +46 -1
  115. package/src/channels/shared/diagnostic-details.mjs +95 -0
  116. package/src/channels/shared/harness-client.mjs +287 -57
  117. package/src/channels/shared/i18n-en/diagnostics.mjs +55 -0
  118. package/src/channels/shared/i18n-en/email.mjs +25 -0
  119. package/src/channels/shared/i18n-en/image-input.mjs +17 -0
  120. package/src/channels/shared/i18n-en/shared-a.mjs +1 -0
  121. package/src/channels/shared/i18n-en/shared-c.mjs +1 -1
  122. package/src/channels/shared/i18n-en.mjs +6 -0
  123. package/src/channels/shared/image-input-policy.mjs +35 -0
  124. package/src/channels/shared/image-input-settings-store.mjs +60 -0
  125. package/src/channels/shared/image-input.mjs +124 -0
  126. package/src/channels/shared/image-prompt.mjs +15 -7
  127. package/src/channels/shared/message-failure.mjs +12 -0
  128. package/src/channels/shared/semantic/reply-reference.mjs +3 -3
  129. package/src/channels/shared/text-harness-bridge.mjs +150 -25
  130. package/src/channels/shared/token-bot-controller.mjs +52 -47
  131. package/src/channels/slack/slack-api.mjs +3 -3
  132. package/src/channels/slack/slack-controller.mjs +59 -51
  133. package/src/channels/slack/slack-runtime.mjs +18 -12
  134. package/src/channels/telegram/config-store.mjs +4 -1
  135. package/src/channels/telegram/telegram-api.mjs +2 -2
  136. package/src/channels/telegram/telegram-controller.mjs +13 -1
  137. package/src/channels/telegram/telegram-runtime.mjs +215 -14
  138. package/src/channels/wecom/wecom-bridge.mjs +15 -6
  139. package/src/channels/wecom/wecom-controller.mjs +51 -37
  140. package/src/channels/wecom/wecom-runtime.mjs +10 -6
  141. package/src/channels/wecom-app/wecom-app-bridge.mjs +7 -5
  142. package/src/channels/wecom-app/wecom-app-controller.mjs +50 -37
  143. package/src/channels/wecom-app/wecom-app-runtime.mjs +2 -1
  144. package/src/channels/weixin/connection-error.mjs +26 -92
  145. package/src/channels/weixin/diagnostic-details.mjs +1 -63
  146. package/src/channels/weixin/weixin-bridge.mjs +7 -5
  147. package/src/channels/whatsapp/whatsapp-controller.mjs +34 -17
  148. package/src/channels/whatsapp/whatsapp-runtime.mjs +35 -8
@@ -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('p', null, error.message),
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
- return unwrapRpcResult(await rpcCall(endpoint, payload, signal));
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('p', null, model.error?.message), h(Button, { onClick: () => void loadStatus() }, '重新读取')))
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
  };
@@ -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
 
@@ -159,7 +160,7 @@ function BindForm({ busy, error, onSubmit, onCancel }) {
159
160
  disabled: busy,
160
161
  }, streamEnabled ? '流式回复:开' : '流式回复:关'),
161
162
  h('span', { className: 'dim-switchHint' }, '企业微信客户端实时出字;微信端不支持时自动改为整段发送'))),
162
- error ? h('div', { className: 'ddt-inlineError dim-inlineError', role: 'alert' }, h('p', null, error.message)) : null,
163
+ error ? h('div', { className: 'ddt-inlineError dim-inlineError', role: 'alert' }, h(ConnectionError, { error: error })) : null,
163
164
  h('div', { className: 'ddt-actions dim-viewActions' },
164
165
  h(Button, { kind: 'primary', onClick: () => formRef.current?.requestSubmit(), disabled: busy }, busy ? '正在绑定…' : '保存并连接'),
165
166
  h(Button, { kind: 'quiet', onClick: onCancel, disabled: busy }, '取消'))));
@@ -327,6 +328,7 @@ export function AccountCard({
327
328
  h(Button, { className: 'dim-cardAction', onClick: onReconnect, disabled: Boolean(busy) }, busy === 'reconnect' ? '检查中…' : account.connected ? '检查连接' : '重试连接'),
328
329
  h(Button, { className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy) }, '移除接入')),
329
330
  summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
331
+ account.error ? h(ConnectionError, { error: account.error, showMessage: false }) : null,
330
332
  account.lastMessageError ? h(LastMessageErrorSummary, {
331
333
  className: 'ddt-summary',
332
334
  error: account.lastMessageError,
@@ -344,6 +346,7 @@ export function AccountCard({
344
346
  }
345
347
 
346
348
  export function WecomAppSettingsTab({ rpcCall }) {
349
+ const [operationError, setOperationError] = React.useState(null);
347
350
  const [model, setModel] = React.useState({
348
351
  phase: 'loading', bots: [], totals: { configured: 0, connected: 0 }, error: null,
349
352
  agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
@@ -393,7 +396,16 @@ export function WecomAppSettingsTab({ rpcCall }) {
393
396
 
394
397
  const invoke = React.useCallback(async (endpoint, payload = {}, signal) => {
395
398
  if (typeof rpcCall !== 'function') throw new TypeError('企业微信应用设置页缺少 RPC 连接');
396
- return unwrapRpcResult(await rpcCall(endpoint, payload, signal));
399
+ const operation = !['connection.status', 'provision.poll', 'provision.begin', 'provision.cancel'].includes(endpoint);
400
+ if (operation && mounted.current) setOperationError(null);
401
+ try {
402
+ const value = unwrapRpcResult(await rpcCall(endpoint, payload, signal));
403
+ if (operation && mounted.current) setOperationError(value?.testMessage?.error ?? value?.warnings?.[0] ?? null);
404
+ return value;
405
+ } catch (error) {
406
+ if (operation && mounted.current && !signal?.aborted && error?.name !== 'AbortError') setOperationError(normalizeConnectionError(error));
407
+ throw error;
408
+ }
397
409
  }, [rpcCall]);
398
410
 
399
411
  const loadStatus = React.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
@@ -601,6 +613,7 @@ export function WecomAppSettingsTab({ rpcCall }) {
601
613
  }, h(AgentPresetCatalogContext.Provider, {
602
614
  value: model.agentPresetCatalog ?? EMPTY_AGENT_PRESET_CATALOG,
603
615
  }, h('section', { className: 'ddt-page dwecomapp-page dim-channelPage', 'aria-label': '企业微信应用设置' },
616
+ operationError ? h(ConnectionError, { error: operationError }) : null,
604
617
  h(Heading, {
605
618
  totals: model.totals,
606
619
  adding: bindOpen,
@@ -611,7 +624,7 @@ export function WecomAppSettingsTab({ rpcCall }) {
611
624
  h('div', { className: 'ddt-visuallyHidden', role: 'status', 'aria-live': 'polite' }, notice),
612
625
  model.phase === 'loading' ? h(LoadingView)
613
626
  : model.phase === 'error'
614
- ? h('div', { className: 'ddt-card dim-surfaceCard' }, h('div', { className: 'ddt-inlineError dim-inlineError' }, h('h3', null, '无法读取企业微信应用状态'), h('p', null, model.error?.message), h(Button, { onClick: () => void loadStatus() }, '重新读取')))
627
+ ? 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() }, '重新读取')))
615
628
  : h(React.Fragment, null,
616
629
  bindView,
617
630
  model.bots.length === 0 && !bindOpen
@@ -1,3 +1,4 @@
1
+ import { normalizeConnectionError as normalizeTestError } from '../../connection-error.js';
1
2
  import { normalizeWeixinDiagnosticDetails } from '../../../../src/channels/weixin/diagnostic-details.mjs';
2
3
  import { normalizeBotAlias } from '../../../../src/channels/shared/bot-alias.mjs';
3
4
  import { normalizeAgentPresetCatalog, normalizeAgentPresetId, SET_AGENT_PRESET_ENDPOINT } from '../../agent-preset.js';
@@ -55,7 +56,7 @@ function normalizeTestMessage(value) {
55
56
  const code = value.code === 'test-target-unavailable'
56
57
  ? 'test-target-unavailable'
57
58
  : 'test-message-failed';
58
- return { sent: false, code };
59
+ return { sent: false, code, ...(value.error ? { error: normalizeTestError(value.error) } : {}) };
59
60
  }
60
61
 
61
62
  export function normalizeConnectionError(value, fallbackCode = 'WEIXIN_ERROR', fallbackMessage = '微信操作失败,请稍后重试') {
@@ -1,71 +1 @@
1
- import * as React from 'react';
2
- import { h, localizeText } from '../../i18n.js';
3
- import { normalizeConnectionError } from './api.js';
4
- import { CONFIG_ISSUE_LABELS } from '../../../../src/channels/weixin/diagnostic-details.mjs';
5
-
6
- const STAGE_LABELS = {
7
- 'startup.load': '加载微信配置', 'qr.begin': '申请二维码', 'qr.encode': '生成二维码图片', 'qr.poll': '查询扫码状态',
8
- 'qr.verify': '提交配对码', 'qr.cancel': '取消绑定', 'credential.read': '读取登录凭据', 'credential.save': '保存登录凭据',
9
- 'credential.remove': '移除登录凭据', 'account.save': '保存账号配置', 'account.remove': '移除账号配置',
10
- 'state.load': '读取账号状态', 'state.write': '保存账号状态', 'state.cleanup': '清理账号状态',
11
- 'workspace.write': '保存工作区设置', 'workspace.cleanup': '清理工作区设置', 'runtime.prepare': '准备消息连接',
12
- activation: '激活微信账号', 'harness.check': '检查 DSH 宿主', 'connection.start': '启动微信连接',
13
- 'connection.poll': '同步微信消息', 'connection.stop': '停止微信连接', 'status.read': '读取连接状态',
14
- rollback: '恢复原账号状态', 'management.request': '访问 DSH 管理接口',
15
- };
16
-
17
- export function provisioningErrorTitle(error) {
18
- const stage = error?.details?.stage;
19
- if (stage === 'qr.begin' || stage === 'qr.encode') return '无法生成微信二维码';
20
- if (stage === 'qr.poll') return '查询微信扫码状态失败';
21
- if (stage === 'management.request') return '无法完成微信管理请求';
22
- return '微信没有绑定完成';
23
- }
24
-
25
- export function formatWeixinDiagnostic(value) {
26
- const error = normalizeConnectionError(value);
27
- const details = error.details ?? {};
28
- return [
29
- localizeText(error.message), details.hint ? localizeText(details.hint) : null,
30
- localizeText('错误码') + ': ' + error.code,
31
- ...['operation', 'stage', 'reason', 'httpStatus', 'providerCode', 'resource', 'file', 'field', 'issue', 'referenceId', 'occurredAt', 'rollback', 'pluginVersion']
32
- .filter(field => details[field] !== undefined)
33
- .map(field => `${field}: ${details[field]}`),
34
- ].filter(Boolean).join('\n');
35
- }
36
-
37
- export function WeixinConnectionError({ error: value, warning = false }) {
38
- const error = normalizeConnectionError(value);
39
- const details = error.details ?? {};
40
- const [copyState, setCopyState] = React.useState(null);
41
- React.useEffect(() => setCopyState(null), [details.referenceId, error.code, error.message]);
42
- const copy = async () => {
43
- try {
44
- if (typeof globalThis.navigator?.clipboard?.writeText !== 'function') throw new Error('clipboard unavailable');
45
- await globalThis.navigator.clipboard.writeText(formatWeixinDiagnostic(error));
46
- setCopyState('copied');
47
- } catch { setCopyState('manual'); }
48
- };
49
- const fields = [
50
- ['错误码', error.code], ['失败阶段', localizeText(STAGE_LABELS[details.stage] ?? details.stage ?? '')],
51
- ['底层原因', details.reason], ['HTTP 状态', details.httpStatus], ['微信返回码', details.providerCode],
52
- ['配置文件', details.file], ['配置字段', details.field],
53
- ['校验原因', details.issue ? localizeText(CONFIG_ISSUE_LABELS[details.issue]) : undefined],
54
- ['参考号', details.referenceId], ['发生时间', details.occurredAt], ['插件版本', details.pluginVersion],
55
- ].filter(([, text]) => text !== undefined && text !== '');
56
- return h('div', { className: 'dxw-summary dim-cardSummary', 'data-weixin-diagnostic': true, role: warning ? 'status' : undefined },
57
- h('p', null, error.message),
58
- details.hint ? h('p', null, details.hint) : null,
59
- h('details', { style: { marginTop: 8 } },
60
- h('summary', { style: { cursor: 'pointer' } }, '诊断详情'),
61
- h('dl', { style: { display: 'grid', gridTemplateColumns: 'auto minmax(0, 1fr)', gap: '4px 12px', margin: '10px 0' } },
62
- ...fields.flatMap(([label, text]) => [h('dt', { key: `${label}-label` }, label),
63
- React.createElement('dd', { key: label, style: { margin: 0, overflowWrap: 'anywhere' } }, String(text))])),
64
- !details.referenceId ? h('p', null, '未取得 Host 诊断参考号。') : null,
65
- h('div', { className: 'dim-viewActions' },
66
- h('button', { type: 'button', className: 'dxw-button', onClick: copy }, copyState === 'copied' ? '诊断信息已复制' : '复制诊断信息')),
67
- copyState === 'manual' ? h('div', null,
68
- h('p', null, '无法访问剪贴板,请选择并复制以下诊断信息。'),
69
- React.createElement('textarea', { readOnly: true, value: formatWeixinDiagnostic(error), rows: 7,
70
- 'aria-label': localizeText('诊断信息'), style: { width: '100%', boxSizing: 'border-box', marginTop: 8 } })) : null));
71
- }
1
+ export { ConnectionError as WeixinConnectionError, formatConnectionDiagnostic as formatWeixinDiagnostic, provisioningErrorTitle } from '../../connection-error.js';
@@ -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';
@@ -52,6 +53,7 @@ export function unwrapRpcResult(result) {
52
53
  if (!result.ok) {
53
54
  const error = new Error(text(result.error?.message, 'WhatsApp 操作失败'));
54
55
  error.code = text(result.error?.code, 'WHATSAPP_RPC_ERROR', 80);
56
+ Object.assign(error, diagnosticFields(result.error));
55
57
  throw error;
56
58
  }
57
59
  return result.value;
@@ -79,6 +81,7 @@ export function normalizeProvisioning(value, now = Date.now()) {
79
81
  if (qrCodeDataUrl) result.qrCodeDataUrl = qrCodeDataUrl;
80
82
  if (id(source.botId)) result.botId = id(source.botId);
81
83
  if (isRecord(source.error)) result.error = {
84
+ ...diagnosticFields(source.error),
82
85
  code: text(source.error.code, 'WHATSAPP_PROVISION_FAILED', 80),
83
86
  message: text(source.error.message, 'WhatsApp 没有接入完成'),
84
87
  };
@@ -112,6 +115,7 @@ function normalizeBot(value) {
112
115
  },
113
116
  lastMessageError: normalizeLastMessageError(value.lastMessageError),
114
117
  error: isRecord(value.error) ? {
118
+ ...diagnosticFields(value.error),
115
119
  code: text(value.error.code, 'WHATSAPP_ACCOUNT_ERROR', 80),
116
120
  message: text(value.error.message, 'WhatsApp 连接尚未就绪'),
117
121
  } : null,
@@ -136,6 +140,7 @@ export function normalizeSnapshot(value) {
136
140
 
137
141
  export function presentError(error) {
138
142
  return {
143
+ ...diagnosticFields(error),
139
144
  code: text(error?.code, 'WHATSAPP_ERROR', 80),
140
145
  message: text(error?.message, 'WhatsApp 操作失败,请稍后重试'),
141
146
  };
@@ -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
 
@@ -168,7 +169,7 @@ export function ProvisionView({ provision, busy, onRetry, onClose }) {
168
169
  return h('div', { className: 'ddt-card dim-surfaceCard' },
169
170
  h('div', { className: 'ddt-inlineError dim-inlineError', role: 'alert' },
170
171
  h('h3', null, 'WhatsApp 没有接入完成'),
171
- h('p', null, error.message),
172
+ h(ConnectionError, { error: error }),
172
173
  h('span', { className: 'ddt-errorCode' }, error.code),
173
174
  h('div', { className: 'ddt-actions dim-viewActions' },
174
175
  h(Button, { kind: 'primary', onClick: onRetry, disabled: busy }, '重新生成二维码'),
@@ -268,6 +269,7 @@ export function WhatsappAccountCard({
268
269
  className: 'dim-cardAction', kind: 'danger', onClick: onRequestRemove, disabled: Boolean(busy),
269
270
  }, '移除接入')),
270
271
  summary ? h('div', { className: 'ddt-summary dim-cardSummary' }, summary) : null,
272
+ account.error ? h(ConnectionError, { error: account.error, showMessage: false }) : null,
271
273
  account.lastMessageError ? h(LastMessageErrorSummary, {
272
274
  className: 'ddt-summary',
273
275
  error: account.lastMessageError,
@@ -287,6 +289,7 @@ export function WhatsappAccountCard({
287
289
  }
288
290
 
289
291
  export function WhatsappSettingsTab({ rpcCall }) {
292
+ const [operationError, setOperationError] = React.useState(null);
290
293
  const [model, setModel] = React.useState({
291
294
  phase: 'loading', bots: [], totals: { configured: 0, connected: 0 }, error: null,
292
295
  agentPresetCatalog: EMPTY_AGENT_PRESET_CATALOG,
@@ -315,7 +318,16 @@ export function WhatsappSettingsTab({ rpcCall }) {
315
318
 
316
319
  const invoke = React.useCallback(async (endpoint, payload = {}, signal) => {
317
320
  if (typeof rpcCall !== 'function') throw new TypeError('WhatsApp 设置页缺少 RPC 连接');
318
- return unwrapRpcResult(await rpcCall(endpoint, payload, signal));
321
+ const operation = !['connection.status', 'provision.poll', 'provision.begin', 'provision.cancel'].includes(endpoint);
322
+ if (operation && mounted.current) setOperationError(null);
323
+ try {
324
+ const value = unwrapRpcResult(await rpcCall(endpoint, payload, signal));
325
+ if (operation && mounted.current) setOperationError(value?.testMessage?.error ?? value?.warnings?.[0] ?? null);
326
+ return value;
327
+ } catch (error) {
328
+ if (operation && mounted.current && !signal?.aborted && error?.name !== 'AbortError') setOperationError(normalizeConnectionError(error));
329
+ throw error;
330
+ }
319
331
  }, [rpcCall]);
320
332
 
321
333
  const loadStatus = React.useCallback(async ({ signal, silent = false, restore = false } = {}) => {
@@ -558,6 +570,7 @@ export function WhatsappSettingsTab({ rpcCall }) {
558
570
  className: 'ddt-page dwa-page dim-channelPage',
559
571
  'aria-label': 'WhatsApp 设置',
560
572
  },
573
+ operationError ? h(ConnectionError, { error: operationError }) : null,
561
574
  h(Heading, {
562
575
  totals: model.totals,
563
576
  busy,
@@ -570,7 +583,7 @@ export function WhatsappSettingsTab({ rpcCall }) {
570
583
  ? h('div', { className: 'ddt-card dim-surfaceCard' },
571
584
  h('div', { className: 'ddt-inlineError dim-inlineError' },
572
585
  h('h3', null, '无法读取 WhatsApp 机器人状态'),
573
- h('p', null, model.error?.message),
586
+ h(ConnectionError, { error: model.error }),
574
587
  h(Button, { onClick: () => void loadStatus() }, '重新读取')))
575
588
  : h(React.Fragment, null,
576
589
  provision?.status === 'pending'
@@ -0,0 +1,87 @@
1
+ import * as React from 'react';
2
+ import { h, localizeText } from './i18n.js';
3
+ import { normalizeDiagnosticDetails } from '../../src/channels/shared/diagnostic-details.mjs';
4
+ import { CONFIG_ISSUE_LABELS } from '../../src/channels/shared/diagnostic-details.mjs';
5
+
6
+ const STAGE_LABELS = {
7
+ 'credential.verify': '验证凭据', 'permission.check': '检查权限', 'sdk.load': '加载渠道组件',
8
+ operation: '执行渠道操作', 'message.receive': '接收消息', 'message.send': '发送消息', 'connection.test': '发送测试消息',
9
+ 'startup.load': '加载渠道配置', 'qr.begin': '申请二维码', 'qr.encode': '生成二维码图片', 'qr.poll': '查询扫码状态',
10
+ 'qr.verify': '提交配对码', 'qr.cancel': '取消绑定', 'credential.read': '读取登录凭据', 'credential.save': '保存登录凭据',
11
+ 'credential.remove': '移除登录凭据', 'account.save': '保存账号配置', 'account.remove': '移除账号配置',
12
+ 'state.load': '读取账号状态', 'state.write': '保存账号状态', 'state.cleanup': '清理账号状态',
13
+ 'workspace.write': '保存工作区设置', 'workspace.cleanup': '清理工作区设置', 'runtime.prepare': '准备消息连接',
14
+ activation: '激活账号', 'harness.check': '检查 DSH 宿主', 'connection.start': '启动连接',
15
+ 'connection.poll': '监测连接与消息', 'connection.stop': '停止连接', 'status.read': '读取连接状态',
16
+ rollback: '恢复原账号状态', 'management.request': '访问 DSH 管理接口',
17
+ };
18
+
19
+ export function provisioningErrorTitle(error) {
20
+ const stage = error?.details?.stage;
21
+ if (stage === 'qr.begin' || stage === 'qr.encode') return '无法生成微信二维码';
22
+ if (stage === 'qr.poll') return '查询微信扫码状态失败';
23
+ if (stage === 'management.request') return '无法完成微信管理请求';
24
+ return '微信没有绑定完成';
25
+ }
26
+
27
+ export function formatConnectionDiagnostic(value) {
28
+ const error = normalizeConnectionError(value);
29
+ const details = error.details ?? {};
30
+ return [
31
+ localizeText(error.message), details.hint ? localizeText(details.hint) : null,
32
+ localizeText('错误码') + ': ' + error.code,
33
+ ...['channel', 'operation', 'stage', 'reason', 'reasons', 'host', 'durationMs', 'timeoutMs', 'nodeVersion', 'dshVersion', 'platform', 'dependencies', 'proxyConfigured', 'truncated', 'httpStatus', 'providerCode', 'resource', 'file', 'field', 'issue', 'referenceId', 'occurredAt', 'rollback', 'pluginVersion']
34
+ .filter(field => details[field] !== undefined)
35
+ .map(field => `${field}: ${typeof details[field] === 'object' ? JSON.stringify(details[field]) : details[field]}`),
36
+ ].filter(Boolean).join('\n');
37
+ }
38
+
39
+ export function ConnectionError({ error: value, warning = false, showMessage = true }) {
40
+ const error = normalizeConnectionError(value);
41
+ const details = error.details ?? {};
42
+ const isWarning = warning || error.code.endsWith('cleanup-failed');
43
+ const [copyState, setCopyState] = React.useState(null);
44
+ React.useEffect(() => setCopyState(null), [details.referenceId, error.code, error.message]);
45
+ const copy = async () => {
46
+ try {
47
+ if (typeof globalThis.navigator?.clipboard?.writeText !== 'function') throw new Error('clipboard unavailable');
48
+ await globalThis.navigator.clipboard.writeText(formatConnectionDiagnostic(error));
49
+ setCopyState('copied');
50
+ } catch { setCopyState('manual'); }
51
+ };
52
+ const fields = [
53
+ ['错误码', error.code], ['失败阶段', localizeText(STAGE_LABELS[details.stage] ?? details.stage ?? '')],
54
+ ['底层原因', details.reason === 'unknown' ? localizeText('暂未识别') : details.reason], ['多个原因', details.reasons?.join(', ')], ['服务域名', details.host], ['请求耗时(毫秒)', details.durationMs], ['超时阈值(毫秒)', details.timeoutMs], ['Node 版本', details.nodeVersion], ['DSH 版本', details.dshVersion], ['依赖版本', details.dependencies ? Object.entries(details.dependencies).map(([name, version]) => `${name}: ${version}`).join(', ') : undefined], ['代理已配置', details.proxyConfigured], ['原因链已截断', details.truncated], ['运行平台', details.platform], ['HTTP 状态', details.httpStatus], ['服务返回码', details.providerCode],
55
+ ['配置文件', details.file], ['配置字段', details.field],
56
+ ['校验原因', details.issue ? localizeText(CONFIG_ISSUE_LABELS[details.issue]) : undefined],
57
+ ['参考号', details.referenceId], ['发生时间', details.occurredAt], ['插件版本', details.pluginVersion],
58
+ ].filter(([, text]) => text !== undefined && text !== '');
59
+ return h('div', { className: 'dim-connectionDiagnostic', 'data-connection-diagnostic': true, 'data-weixin-diagnostic': details.channel === 'weixin' || /^WX-CONN/.test(details.referenceId ?? '') || undefined, role: isWarning ? 'status' : undefined, 'data-warning': isWarning || undefined },
60
+ showMessage ? h('p', null, error.message) : null,
61
+ details.hint ? h('p', null, details.hint) : null,
62
+ h('details', { style: { marginTop: 8 } },
63
+ h('summary', { style: { cursor: 'pointer' } }, '诊断详情'),
64
+ h('dl', { style: { display: 'grid', gridTemplateColumns: 'auto minmax(0, 1fr)', gap: '4px 12px', margin: '10px 0' } },
65
+ ...fields.flatMap(([label, text]) => [h('dt', { key: `${label}-label` }, label),
66
+ React.createElement('dd', { key: label, style: { margin: 0, overflowWrap: 'anywhere' } }, String(text))])),
67
+ !details.referenceId ? h('p', null, '未取得 Host 诊断参考号。') : null,
68
+ h('div', { className: 'dim-viewActions' },
69
+ h('button', { type: 'button', className: 'dxw-button', onClick: copy }, copyState === 'copied' ? '诊断信息已复制' : '复制诊断信息')),
70
+ copyState === 'manual' ? h('div', null,
71
+ h('p', null, '无法访问剪贴板,请选择并复制以下诊断信息。'),
72
+ React.createElement('textarea', { readOnly: true, value: formatConnectionDiagnostic(error), rows: 7,
73
+ 'aria-label': localizeText('诊断信息'), style: { width: '100%', boxSizing: 'border-box', marginTop: 8 } })) : null));
74
+ }
75
+
76
+ export function normalizeConnectionError(value, fallbackCode = 'operation-failed', fallbackMessage = '操作失败,请查看原因和诊断详情。') {
77
+ const source = value && typeof value === 'object' ? value : {};
78
+ const text = (v, fallback, n) => typeof v === 'string' && v.trim() ? v.trim().slice(0, n) : fallback;
79
+ const details = normalizeDiagnosticDetails({ ...source.details,
80
+ referenceId: source.details?.referenceId ?? source.referenceId,
81
+ hint: source.details?.hint ?? source.hint,
82
+ });
83
+ return { code: text(source.code, fallbackCode, 100), message: text(source.message, fallbackMessage, 500),
84
+ ...(Object.keys(details).length ? { details } : {}),
85
+ ...(source.hint ? { hint: details.hint } : {}), ...(source.referenceId ? { referenceId: details.referenceId } : {}),
86
+ };
87
+ }
@@ -6,6 +6,7 @@ import {
6
6
  normalizeInboundTtlHours,
7
7
  } from '../../src/channels/shared/inbound-ttl.mjs';
8
8
  import { h } from './i18n.js';
9
+ import { DEFAULT_IMAGE_INPUT_SETTINGS, normalizeImageInputSettings } from '../../src/channels/shared/image-input-policy.mjs';
9
10
 
10
11
  export const GLOBAL_SETTINGS_RPC_CHANNEL = '/dsh-im-settings';
11
12
 
@@ -18,6 +19,8 @@ export const GLOBAL_SETTINGS_ENDPOINTS = Object.freeze({
18
19
  getTtl: 'settings.inbound-ttl.get',
19
20
  setTtl: 'settings.inbound-ttl.set',
20
21
  sweep: 'settings.inbound-ttl.sweep',
22
+ getImages: 'settings.image-input.get',
23
+ setImages: 'settings.image-input.set',
21
24
  });
22
25
 
23
26
  function presentError(error, fallback) {
@@ -56,6 +59,82 @@ function GlobalButton({ children, kind = 'secondary', className = '', ...props }
56
59
  }, children);
57
60
  }
58
61
 
62
+ export function ImageInputSettings({ rpcCall }) {
63
+ const [values, setValues] = React.useState(DEFAULT_IMAGE_INPUT_SETTINGS);
64
+ const [phase, setPhase] = React.useState('loading');
65
+ const [error, setError] = React.useState(null);
66
+ const [saved, setSaved] = React.useState(false);
67
+ const mounted = React.useRef(false);
68
+ const saving = React.useRef(false);
69
+ const id = React.useId();
70
+ const load = React.useCallback(async (signal) => {
71
+ setPhase('loading');
72
+ setError(null);
73
+ try {
74
+ const result = unwrapRpcResult(await rpcCall(GLOBAL_SETTINGS_ENDPOINTS.getImages, {}, signal));
75
+ if (signal?.aborted || !mounted.current) return;
76
+ const settings = normalizeImageInputSettings(result);
77
+ if (!settings) throw new Error('通用设置返回了无法识别的响应。');
78
+ setValues(settings);
79
+ setPhase('ready');
80
+ } catch (caught) {
81
+ if (signal?.aborted || !mounted.current) return;
82
+ setError(presentError(caught, '无法读取或保存图片设置,请稍后重试。'));
83
+ setPhase('error');
84
+ }
85
+ }, [rpcCall]);
86
+ React.useEffect(() => {
87
+ mounted.current = true;
88
+ const controller = new AbortController();
89
+ void load(controller.signal);
90
+ return () => { mounted.current = false; controller.abort(); };
91
+ }, [load]);
92
+ const save = async (event) => {
93
+ event.preventDefault();
94
+ if (saving.current || phase !== 'ready') return;
95
+ const settings = normalizeImageInputSettings(values);
96
+ setSaved(false);
97
+ if (!settings) {
98
+ setError('图片限制无效:请输入正整数,原图接收上限和总量上限不能小于单图上限。');
99
+ return;
100
+ }
101
+ saving.current = true;
102
+ setPhase('saving');
103
+ setError(null);
104
+ try {
105
+ const result = unwrapRpcResult(await rpcCall(GLOBAL_SETTINGS_ENDPOINTS.setImages, settings));
106
+ if (!normalizeImageInputSettings(result)) throw new Error('通用设置返回了无法识别的响应。');
107
+ if (mounted.current) { setValues(result); setSaved(true); }
108
+ } catch (caught) {
109
+ if (mounted.current) setError(presentError(caught, '无法读取或保存图片设置,请稍后重试。'));
110
+ } finally {
111
+ saving.current = false;
112
+ if (mounted.current) setPhase('ready');
113
+ }
114
+ };
115
+ const fields = [
116
+ ['maxDownloadMb', '原图接收上限 (MB)'],
117
+ ['maxImageMb', '模型单图上限 (MB)'],
118
+ ['maxTotalMb', '模型图片总量上限 (MB)'],
119
+ ['maxImages', '每条消息最多图片数'],
120
+ ];
121
+ return h('section', { className: 'dim-globalSection', 'aria-label': '图片输入', 'aria-busy': phase === 'loading' || phase === 'saving' },
122
+ h('h3', null, '图片输入'),
123
+ h('p', null, '适用于支持图片的聊天渠道。原图按附件保留时长保存,发送给模型的副本会自动缩放或压缩;无法直接发送时交给模型按文件处理。'),
124
+ h('form', { onSubmit: save },
125
+ ...fields.map(([key, label]) => h('div', { className: 'dim-globalTtlRow', key },
126
+ h('label', { htmlFor: `${id}-${key}` }, label),
127
+ h('input', { id: `${id}-${key}`, className: 'dim-globalTtlInput', type: 'number', min: 1, step: 1,
128
+ value: values[key], disabled: phase !== 'ready',
129
+ onChange: (event) => { setValues((current) => ({ ...current, [key]: event.target.value })); setSaved(false); setError(null); },
130
+ }))),
131
+ h(GlobalButton, { type: 'submit', kind: 'primary', disabled: phase !== 'ready' }, phase === 'saving' ? '保存中…' : '保存图片设置'),
132
+ phase === 'error' ? h(GlobalButton, { onClick: () => void load() }, '重试') : null),
133
+ error ? h('p', { role: 'alert' }, error) : null,
134
+ saved ? h('p', { role: 'status' }, '已保存') : null,
135
+ phase === 'loading' ? h('p', { role: 'status' }, '正在读取图片设置…') : null);
136
+ }
137
+
59
138
  export function GlobalSettingsPanel({ rpcCall }) {
60
139
  const [phase, setPhase] = React.useState('loading');
61
140
  const [loadError, setLoadError] = React.useState(null);
@@ -337,5 +416,6 @@ export function GlobalSettingsPanel({ rpcCall }) {
337
416
  role: inlineStatus.role,
338
417
  'aria-live': inlineStatus.role === 'status' ? 'polite' : undefined,
339
418
  }, inlineStatus.message)
340
- : null))));
419
+ : null)),
420
+ h(ImageInputSettings, { rpcCall })));
341
421
  }