@xmanrui/dsh-im 0.2.1 → 0.3.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 (39) hide show
  1. package/README.md +19 -11
  2. package/lib/client.js +1847 -1145
  3. package/lib/index.js +116 -115
  4. package/package.json +2 -2
  5. package/plugin-src/client/channel-logos.js +13 -0
  6. package/plugin-src/client/channels/dingtalk/index.js +1 -1
  7. package/plugin-src/client/channels/dingtalk/styles.js +8 -8
  8. package/plugin-src/client/channels/feishu/index.js +1 -2
  9. package/plugin-src/client/channels/feishu/styles.js +3 -3
  10. package/plugin-src/client/channels/qq/index.js +1 -1
  11. package/plugin-src/client/channels/shared/token-channel.js +1 -2
  12. package/plugin-src/client/channels/slack/api.js +11 -0
  13. package/plugin-src/client/channels/slack/index.js +130 -0
  14. package/plugin-src/client/channels/slack/styles.js +34 -0
  15. package/plugin-src/client/channels/wecom/index.js +1 -1
  16. package/plugin-src/client/channels/wecom/styles.js +1 -1
  17. package/plugin-src/client/channels/weixin/index.js +1 -2
  18. package/plugin-src/client/channels/weixin/styles.js +9 -9
  19. package/plugin-src/client/channels/whatsapp/index.js +1 -1
  20. package/plugin-src/client/credential-binding.js +1 -2
  21. package/plugin-src/client/i18n.js +443 -0
  22. package/plugin-src/client/index.js +29 -4
  23. package/plugin-src/client/styles.js +46 -38
  24. package/plugin-src/host/channels/shared/production.mjs +2 -2
  25. package/plugin-src/host/channels/slack/index.mjs +28 -0
  26. package/plugin-src/host/channels/slack/production.mjs +91 -0
  27. package/plugin-src/host/channels/slack/rpc.mjs +127 -0
  28. package/plugin-src/host/index.mjs +3 -0
  29. package/scripts/verify-package.mjs +10 -3
  30. package/src/channels/discord/discord-api.mjs +1 -1
  31. package/src/channels/slack/config-store.mjs +173 -0
  32. package/src/channels/slack/harness-client.mjs +3 -0
  33. package/src/channels/slack/manifest.mjs +31 -0
  34. package/src/channels/slack/slack-api.mjs +259 -0
  35. package/src/channels/slack/slack-bridge.mjs +15 -0
  36. package/src/channels/slack/slack-controller.mjs +318 -0
  37. package/src/channels/slack/slack-runtime.mjs +462 -0
  38. package/src/channels/slack/state-store.mjs +3 -0
  39. package/src/channels/weixin/weixin-api.mjs +1 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xmanrui/dsh-im",
3
- "version": "0.2.1",
4
- "description": "通过扫码或机器人凭据把IM机器人接入DeepSeek Harness(支持飞书、微信、钉钉、企业微信、QQ、Telegram、Discord和WhatsApp)。",
3
+ "version": "0.3.0",
4
+ "description": "通过扫码、App Manifest或机器人凭据把IM机器人接入DeepSeek Harness(支持飞书、微信、钉钉、企业微信、QQ、Slack、Telegram、Discord和WhatsApp)。",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "repository": {
@@ -106,6 +106,19 @@ export function DiscordLogoGlyph({ size } = {}) {
106
106
  }));
107
107
  }
108
108
 
109
+ export function SlackLogoGlyph({ size } = {}) {
110
+ return h('svg', {
111
+ ...dimensions(size),
112
+ viewBox: '0 0 24 24',
113
+ focusable: 'false',
114
+ 'aria-hidden': 'true',
115
+ 'data-im-channel-logo': 'slack',
116
+ }, h('path', {
117
+ fill: 'currentColor',
118
+ d: 'M6 15a2 2 0 1 1-2-2h2v2Zm1 0a2 2 0 1 1 4 0v5a2 2 0 1 1-4 0v-5Zm2-8a2 2 0 1 1 2-2v2H9Zm0 1a2 2 0 1 1 0 4H4a2 2 0 1 1 0-4h5Zm8 2a2 2 0 1 1 2 2h-2v-2Zm-1 0a2 2 0 1 1-4 0V5a2 2 0 1 1 4 0v5Zm-2 8a2 2 0 1 1-2 2v-2h2Zm0-1a2 2 0 1 1 0-4h5a2 2 0 1 1 0 4h-5Z',
119
+ }));
120
+ }
121
+
109
122
  export function WhatsappLogoGlyph({ size } = {}) {
110
123
  return h('svg', {
111
124
  ...dimensions(size),
@@ -1,6 +1,7 @@
1
1
  import * as React from 'react';
2
2
 
3
3
  import { CredentialActionIcon, CredentialBindingPanel, QrActionIcon } from '../../credential-binding.js';
4
+ import { h } from '../../i18n.js';
4
5
  import {
5
6
  DINGTALK_ENDPOINTS,
6
7
  DINGTALK_RPC_CHANNEL,
@@ -13,7 +14,6 @@ import {
13
14
  } from './api.js';
14
15
  import { installDingtalkStyles } from './styles.js';
15
16
 
16
- const h = React.createElement;
17
17
  const ACTIVE_PROVISION_STATES = new Set(['pending', 'scanned', 'authorizing', 'creating', 'connecting']);
18
18
 
19
19
  export const name = 'dingtalk-settings';
@@ -6,7 +6,7 @@ const CSS = String.raw`
6
6
  --ddt-accent-deep: #0958d9;
7
7
  --ddt-accent-wash: #eaf3ff;
8
8
  --ddt-success: var(--dsw-alias-state-success-primary, #20a162);
9
- --ddt-warning: var(--dsw-alias-state-warning-primary, #d97706);
9
+ --ddt-warning: var(--dsw-alias-state-warn-primary, #d97706);
10
10
  --ddt-error: var(--dsw-alias-state-error-primary, #d54941);
11
11
  width: 100%;
12
12
  max-width: 880px;
@@ -27,13 +27,13 @@ const CSS = String.raw`
27
27
  .ddt-heading p { margin-top: 5px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 20px; white-space: nowrap; }
28
28
  .ddt-tools, .ddt-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }
29
29
  .ddt-tools { width: 100%; justify-content: space-between; flex-wrap: nowrap; }
30
- .ddt-badge { min-height: 30px; display: inline-flex; align-items: center; gap: 7px; padding: 0 11px; border-radius: 999px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-fill-secondary, #f2f3f5); font-size: 12px; white-space: nowrap; }
30
+ .ddt-badge { min-height: 30px; display: inline-flex; align-items: center; gap: 7px; padding: 0 11px; border-radius: 999px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-module-platform, #f2f3f5); font-size: 12px; white-space: nowrap; }
31
31
  .ddt-dot { width: 8px; height: 8px; flex: none; border-radius: 50%; background: #aeb3bb; }
32
32
  .ddt-dot[data-tone="success"] { background: var(--ddt-success); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ddt-success) 14%, transparent); }
33
33
  .ddt-dot[data-tone="warning"] { background: var(--ddt-warning); }
34
34
  .ddt-dot[data-tone="error"] { background: var(--ddt-error); }
35
- .ddt-button { min-height: 34px; display: inline-flex; align-items: center; justify-content: center; gap: 7px; padding: 0 13px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-body, #fff); font: inherit; font-size: 13px; font-weight: 560; text-decoration: none; cursor: pointer; transition: border-color .15s ease, background .15s ease, transform .15s ease; }
36
- .ddt-button:hover:not(:disabled) { border-color: #aeb3bb; background: var(--dsw-alias-fill-tertiary, #f7f8fa); }
35
+ .ddt-button { min-height: 34px; display: inline-flex; align-items: center; justify-content: center; gap: 7px; padding: 0 13px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 13px; font-weight: 560; text-decoration: none; cursor: pointer; transition: border-color .15s ease, background .15s ease, transform .15s ease; }
36
+ .ddt-button:hover:not(:disabled) { border-color: #aeb3bb; background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
37
37
  .ddt-button:active:not(:disabled) { transform: translateY(1px); }
38
38
  .ddt-button:focus-visible { outline: 2px solid color-mix(in srgb, var(--ddt-accent) 70%, white); outline-offset: 2px; }
39
39
  .ddt-button:disabled { cursor: not-allowed; opacity: .55; }
@@ -41,7 +41,7 @@ const CSS = String.raw`
41
41
  .ddt-button[data-kind="primary"]:hover:not(:disabled) { border-color: var(--ddt-accent-deep); background: var(--ddt-accent-deep); }
42
42
  .ddt-button[data-kind="danger"] { color: var(--ddt-error); }
43
43
  .ddt-button[data-kind="quiet"] { min-height: 30px; padding: 0 10px; border-color: transparent; background: transparent; }
44
- .ddt-card { overflow: hidden; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-body, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); }
44
+ .ddt-card { overflow: hidden; border: 1px solid var(--dsw-alias-border-l2, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); }
45
45
  .ddt-cardBody { padding: 24px; }
46
46
  .ddt-empty { min-height: 230px; display: grid; grid-template-columns: minmax(0, 1fr) 180px; align-items: center; gap: 30px; }
47
47
  .ddt-empty h3 { margin: 8px 0; font-size: 18px; }
@@ -51,7 +51,7 @@ const CSS = String.raw`
51
51
  .ddt-brandMark svg { filter: drop-shadow(0 3px 8px rgb(0 35 96 / 16%)); }
52
52
  .ddt-qrLayout { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 34px; align-items: start; }
53
53
  .ddt-qrColumn { display: flex; flex-direction: column; align-items: center; gap: 12px; }
54
- .ddt-qrFrame { position: relative; width: min(270px, 100%); aspect-ratio: 1; display: grid; place-items: center; overflow: hidden; padding: 10px; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 16px; background: #fff; }
54
+ .ddt-qrFrame { position: relative; width: min(270px, 100%); aspect-ratio: 1; display: grid; place-items: center; overflow: hidden; padding: 10px; border: 1px solid var(--dsw-alias-border-l2, #e5e6eb); border-radius: 16px; background: #fff; }
55
55
  .ddt-qrFrame::before { content: ''; position: absolute; inset: 6px; border: 1px solid rgb(22 119 255 / 10%); border-radius: 11px; pointer-events: none; }
56
56
  .ddt-qrFrame img { display: block; width: 100%; height: 100%; object-fit: contain; }
57
57
  .ddt-qrFallback { padding: 24px; color: #646a73; text-align: center; }
@@ -87,10 +87,10 @@ const CSS = String.raw`
87
87
  .ddt-accountIdentity p { margin-top: 4px; color: var(--dsw-alias-label-secondary, #646a73); font: 12px ui-monospace, SFMono-Regular, monospace; }
88
88
  .ddt-health { display: inline-flex; align-items: center; gap: 7px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; white-space: nowrap; }
89
89
  .ddt-metrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 20px 0; }
90
- .ddt-metric { min-width: 0; padding: 12px; border-radius: 9px; background: var(--dsw-alias-fill-tertiary, #f7f8fa); }
90
+ .ddt-metric { min-width: 0; padding: 12px; border-radius: 9px; background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
91
91
  .ddt-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; }
92
92
  .ddt-metric dd { overflow: hidden; margin: 5px 0 0; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
93
- .ddt-accountFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); }
93
+ .ddt-accountFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-border-l1, #eef0f3); }
94
94
  .ddt-accountFooter .ddt-actions { flex: none; flex-wrap: nowrap; gap: 8px; margin-top: 0; }
95
95
  .ddt-accountFooter .ddt-button { flex: none; white-space: nowrap; }
96
96
  .ddt-summary { color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; }
@@ -2,6 +2,7 @@ import * as React from "react";
2
2
 
3
3
  import { FeishuLogoGlyph } from "../../channel-logos.js";
4
4
  import { CredentialActionIcon, CredentialBindingPanel, QrActionIcon } from "../../credential-binding.js";
5
+ import { h } from "../../i18n.js";
5
6
  import {
6
7
  FEISHU_ENDPOINTS,
7
8
  FEISHU_RPC_CHANNEL,
@@ -15,8 +16,6 @@ import {
15
16
  import { useAnimationFrameScheduler } from "../../lifecycle.js";
16
17
  import { installFeishuStyles } from "./styles.js";
17
18
 
18
- const h = React.createElement;
19
-
20
19
  export const name = "feishu-settings";
21
20
  export const inject = ["slots", "connection"];
22
21
 
@@ -4,7 +4,7 @@ const CSS = String.raw`
4
4
  .bxf-page {
5
5
  --bxf-accent: var(--dsw-alias-state-business-primary, #3370ff);
6
6
  --bxf-success: var(--dsw-alias-state-success-primary, #20a162);
7
- --bxf-warning: var(--dsw-alias-state-warning-primary, #d97706);
7
+ --bxf-warning: var(--dsw-alias-state-warn-primary, #d97706);
8
8
  --bxf-error: var(--dsw-alias-state-error-primary, #d54941);
9
9
  box-sizing: border-box;
10
10
  width: 100%;
@@ -393,7 +393,7 @@ const CSS = String.raw`
393
393
 
394
394
  .bxf-connectedTop { display: flex; align-items: center; justify-content: space-between; gap: 20px; }
395
395
  .bxf-botIdentity { min-width: 0; display: flex; align-items: center; gap: 13px; }
396
- .bxf-avatar { flex: none; width: 48px; height: 48px; display: grid; place-items: center; overflow: hidden; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 1px 3px rgb(31 35 41 / 7%); }
396
+ .bxf-avatar { flex: none; width: 48px; height: 48px; display: grid; place-items: center; overflow: hidden; border: 1px solid var(--dsw-alias-border-l2, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 1px 3px rgb(31 35 41 / 7%); }
397
397
  .bxf-botName { min-width: 0; }
398
398
  .bxf-botName h3 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 17px; line-height: 24px; font-weight: 650; }
399
399
  .bxf-botName p { overflow: hidden; color: var(--dsw-alias-label-tertiary, #8f959e); font-family: var(--ds-font-family-code, monospace); font-size: 12px; line-height: 18px; text-overflow: ellipsis; white-space: nowrap; margin-top: 2px; }
@@ -407,7 +407,7 @@ const CSS = String.raw`
407
407
  .bxf-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; line-height: 17px; }
408
408
  .bxf-metric dd { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-primary, #1f2329); font-size: 12px; line-height: 18px; font-weight: 550; margin: 3px 0 0; }
409
409
 
410
- .bxf-connectedFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); }
410
+ .bxf-connectedFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-border-l1, #eef0f3); }
411
411
  .bxf-healthSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; }
412
412
  .bxf-healthSummary[data-error="true"] { color: var(--bxf-error); }
413
413
  .bxf-botActions { flex: none; flex-wrap: nowrap; gap: 8px; margin-top: 0; justify-content: flex-end; }
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
 
3
3
  import { QqLogoGlyph } from '../../channel-logos.js';
4
4
  import { CredentialActionIcon, CredentialBindingPanel, QrActionIcon } from '../../credential-binding.js';
5
+ import { h } from '../../i18n.js';
5
6
  import { installDingtalkStyles } from '../dingtalk/styles.js';
6
7
  import {
7
8
  QQ_ENDPOINTS,
@@ -15,7 +16,6 @@ import {
15
16
  } from './api.js';
16
17
  import { installQqStyles } from './styles.js';
17
18
 
18
- const h = React.createElement;
19
19
  const ACTIVE_STATES = new Set(['pending', 'refreshing', 'connecting']);
20
20
 
21
21
  const Button = React.forwardRef(function Button({ children, kind = 'secondary', className = '', ...props }, ref) {
@@ -1,10 +1,9 @@
1
1
  import * as React from 'react';
2
2
 
3
3
  import { CredentialActionIcon, CredentialBindingPanel } from '../../credential-binding.js';
4
+ import { h } from '../../i18n.js';
4
5
  import { installDingtalkStyles } from '../dingtalk/styles.js';
5
6
 
6
- const h = React.createElement;
7
-
8
7
  const Button = React.forwardRef(function Button(
9
8
  { children, kind = 'secondary', className = '', ...props },
10
9
  ref,
@@ -0,0 +1,11 @@
1
+ import { TOKEN_BOT_ENDPOINTS, createTokenChannelApi } from '../shared/token-api.js';
2
+
3
+ export const SLACK_RPC_CHANNEL = '/slack';
4
+ export const SLACK_ENDPOINTS = TOKEN_BOT_ENDPOINTS;
5
+
6
+ const api = createTokenChannelApi('Slack', ' Socket Mode 长连接');
7
+
8
+ export const unwrapRpcResult = api.unwrapRpcResult;
9
+ export const normalizeSnapshot = api.normalizeSnapshot;
10
+ export const presentError = api.presentError;
11
+ export { api as slackClientApi };
@@ -0,0 +1,130 @@
1
+ import * as React from 'react';
2
+
3
+ import { SlackLogoGlyph } from '../../channel-logos.js';
4
+ import { h } from '../../i18n.js';
5
+ import { createTokenChannelSettings } from '../shared/token-channel.js';
6
+ import {
7
+ SLACK_CREATE_APP_URL,
8
+ SLACK_APP_MANIFEST_YAML,
9
+ } from '../../../../src/channels/slack/manifest.mjs';
10
+ import { SLACK_ENDPOINTS, slackClientApi } from './api.js';
11
+ import { installSlackStyles } from './styles.js';
12
+
13
+ export function SlackCredentialPanel({ busy, error, onSubmit, onCancel }) {
14
+ const [botToken, setBotToken] = React.useState('');
15
+ const [appToken, setAppToken] = React.useState('');
16
+ const [copied, setCopied] = React.useState(false);
17
+ const headingId = React.useId();
18
+
19
+ const copyManifest = async () => {
20
+ try {
21
+ await navigator.clipboard.writeText(SLACK_APP_MANIFEST_YAML);
22
+ setCopied(true);
23
+ window.setTimeout(() => setCopied(false), 2_000);
24
+ } catch {
25
+ setCopied(false);
26
+ }
27
+ };
28
+
29
+ const submit = (event) => {
30
+ event.preventDefault();
31
+ const normalizedBotToken = botToken.trim();
32
+ const normalizedAppToken = appToken.trim();
33
+ if (!normalizedBotToken || !normalizedAppToken || busy) return;
34
+ void onSubmit?.({ botToken: normalizedBotToken, appToken: normalizedAppToken });
35
+ };
36
+
37
+ return h('section', {
38
+ className: 'ddt-card dim-surfaceCard dim-credentialPanel dsl-setup',
39
+ 'aria-labelledby': headingId,
40
+ },
41
+ h('h3', { id: headingId, className: 'dim-credentialTitle' }, '接入 Slack 机器人'),
42
+ h('div', { className: 'dsl-guide' },
43
+ h('div', { className: 'dsl-guideCopy' },
44
+ h('strong', null, '先用 Manifest 创建并配置 Slack App'),
45
+ h('p', null, '复制配置后,在 Slack 选择 From a manifest;创建完成后生成 connections:write App Token,并将应用安装到工作区。')),
46
+ h('div', { className: 'dsl-guideActions' },
47
+ h('button', {
48
+ type: 'button',
49
+ className: 'ddt-button',
50
+ onClick: () => void copyManifest(),
51
+ disabled: busy,
52
+ }, copied ? h('span', { className: 'dsl-copyState' }, '已复制 Manifest') : '复制 Manifest'),
53
+ h('a', {
54
+ className: 'ddt-button',
55
+ href: SLACK_CREATE_APP_URL,
56
+ target: '_blank',
57
+ rel: 'noreferrer',
58
+ }, '打开 Slack 创建页'))),
59
+ h('form', { className: 'dim-credentialForm dim-credentialFormSingle', onSubmit: submit },
60
+ h('div', { className: 'dsl-fields' },
61
+ h('label', { className: 'dim-credentialField' },
62
+ h('span', null, 'Bot Token'),
63
+ h('input', {
64
+ type: 'password',
65
+ value: botToken,
66
+ onChange: (event) => setBotToken(event.target.value),
67
+ placeholder: 'xoxb-…',
68
+ maxLength: 4096,
69
+ autoCapitalize: 'none',
70
+ autoCorrect: 'off',
71
+ spellCheck: false,
72
+ autoComplete: 'new-password',
73
+ disabled: busy,
74
+ required: true,
75
+ })),
76
+ h('label', { className: 'dim-credentialField' },
77
+ h('span', null, 'App Token'),
78
+ h('input', {
79
+ type: 'password',
80
+ value: appToken,
81
+ onChange: (event) => setAppToken(event.target.value),
82
+ placeholder: 'xapp-…',
83
+ maxLength: 4096,
84
+ autoCapitalize: 'none',
85
+ autoCorrect: 'off',
86
+ spellCheck: false,
87
+ autoComplete: 'new-password',
88
+ disabled: busy,
89
+ required: true,
90
+ })),
91
+ h('p', { className: 'dsl-tokenHint' }, 'Bot Token 来自 OAuth & Permissions;App Token 来自 Basic Information,并且必须包含 connections:write。')),
92
+ error ? h('p', { className: 'dim-credentialError', role: 'alert' }, error.message ?? String(error)) : null,
93
+ h('div', { className: 'ddt-actions dim-viewActions dim-credentialActions' },
94
+ h('button', {
95
+ type: 'submit',
96
+ className: 'ddt-button',
97
+ 'data-kind': 'primary',
98
+ disabled: busy || !botToken.trim() || !appToken.trim(),
99
+ }, busy ? '正在验证并连接…' : '验证并连接'),
100
+ h('button', {
101
+ type: 'button',
102
+ className: 'ddt-button',
103
+ onClick: onCancel,
104
+ disabled: busy,
105
+ }, '取消'))));
106
+ }
107
+
108
+ const channel = createTokenChannelSettings({
109
+ channel: 'Slack',
110
+ endpoints: SLACK_ENDPOINTS,
111
+ api: slackClientApi,
112
+ LogoGlyph: SlackLogoGlyph,
113
+ installStyles: installSlackStyles,
114
+ pageClass: 'dsl-page',
115
+ avatarClass: 'dsl-avatar',
116
+ connectionLabel: 'Socket Mode 长连接',
117
+ emptyTitle: '接入 Slack 机器人',
118
+ emptyDescription: '使用官方 App Manifest 快速配置机器人,再填写 Bot Token 与 App Token 建立本地 Socket Mode 连接。',
119
+ platformLabel: 'Slack 工作区',
120
+ CredentialPanel: SlackCredentialPanel,
121
+ credentialPayload: ({ botToken, appToken }) => ({ botToken, appToken }),
122
+ credentialAriaLabel: '使用 Manifest 和双 Token 接入 Slack 机器人',
123
+ credentialOpenLabel: '接入机器人',
124
+ credentialCloseLabel: '收起接入',
125
+ credentialNoun: 'Bot Token 与 App Token',
126
+ emptyActionLabel: '开始接入',
127
+ });
128
+
129
+ export const SlackSettingsTab = channel.SettingsTab;
130
+ export const SlackAccountCard = channel.AccountCard;
@@ -0,0 +1,34 @@
1
+ export const SLACK_STYLE_ID = 'xmanrui-dsh-im-slack-settings';
2
+
3
+ const CSS = String.raw`
4
+ .dsl-page { --ddt-accent: #4a154b; --ddt-accent-deep: #321033; --ddt-accent-wash: #f7eef7; }
5
+ .dsl-avatar { color: #fff; background: #4a154b; }
6
+ .dsl-avatar svg { display: block; }
7
+ .dsl-setup { display: grid; gap: 18px; }
8
+ .dsl-guide { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: start; gap: 18px; padding: 16px; border: 1px solid color-mix(in srgb, #4a154b 18%, var(--dsw-alias-border-l2, #e5e6eb)); border-radius: 11px; background: color-mix(in srgb, #4a154b 4%, var(--dsw-alias-bg-layer-1, #fff)); }
9
+ .dsl-guideCopy { min-width: 0; }
10
+ .dsl-guideCopy strong { display: block; margin-bottom: 5px; color: var(--dsw-alias-label-primary, #1f2329); font-size: 13px; }
11
+ .dsl-guideCopy p { margin: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 1.6; }
12
+ .dsl-guideActions { display: flex; align-items: center; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
13
+ .dsl-guideActions .ddt-button { white-space: nowrap; }
14
+ .dsl-copyState { color: var(--dsw-alias-state-success-primary, #20a162); }
15
+ .dsl-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
16
+ .dsl-tokenHint { grid-column: 1 / -1; margin: -4px 0 0; color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; line-height: 1.55; }
17
+ @container (max-width: 680px) {
18
+ .dsl-guide { grid-template-columns: minmax(0, 1fr); }
19
+ .dsl-guideActions { justify-content: flex-start; }
20
+ .dsl-fields { grid-template-columns: minmax(0, 1fr); }
21
+ }
22
+ `;
23
+
24
+ export function installSlackStyles() {
25
+ if (typeof document === 'undefined') return () => {};
26
+ const existing = document.querySelector(`style[data-plugin-css="${SLACK_STYLE_ID}"]`);
27
+ if (existing) return () => {};
28
+ const style = document.createElement('style');
29
+ style.dataset.plugin = '@xmanrui/dsh-im';
30
+ style.dataset.pluginCss = SLACK_STYLE_ID;
31
+ style.textContent = CSS;
32
+ document.head.appendChild(style);
33
+ return () => style.remove();
34
+ }
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
 
3
3
  import { WecomLogoGlyph } from '../../channel-logos.js';
4
4
  import { CredentialActionIcon, CredentialBindingPanel, QrActionIcon } from '../../credential-binding.js';
5
+ import { h } from '../../i18n.js';
5
6
  import { installDingtalkStyles } from '../dingtalk/styles.js';
6
7
  import {
7
8
  WECOM_ENDPOINTS,
@@ -15,7 +16,6 @@ import {
15
16
  } from './api.js';
16
17
  import { installWecomStyles } from './styles.js';
17
18
 
18
- const h = React.createElement;
19
19
  const ACTIVE_STATES = new Set(['pending', 'refreshing', 'connecting']);
20
20
 
21
21
  const Button = React.forwardRef(function Button({ children, kind = 'secondary', className = '', ...props }, ref) {
@@ -2,7 +2,7 @@ export const WECOM_STYLE_ID = 'xmanrui-dsh-im-wecom-settings';
2
2
 
3
3
  const CSS = String.raw`
4
4
  .dwecom-page { --ddt-accent: #3370ff; --ddt-accent-deep: #245bdb; --ddt-accent-wash: #eef4ff; }
5
- .dwecom-avatar, .dwecom-brand { color: #3370ff; background: #fff; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); }
5
+ .dwecom-avatar, .dwecom-brand { color: #3370ff; background: #fff; border: 1px solid var(--dsw-alias-border-l2, #e5e6eb); }
6
6
  .dwecom-avatar svg, .dwecom-brand svg { display: block; }
7
7
  `;
8
8
 
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
 
3
3
  import { WeixinLogoGlyph } from '../../channel-logos.js';
4
4
  import { QrActionIcon } from '../../credential-binding.js';
5
+ import { h } from '../../i18n.js';
5
6
  import {
6
7
  WEIXIN_ENDPOINTS,
7
8
  WEIXIN_RPC_CHANNEL,
@@ -16,8 +17,6 @@ import {
16
17
  import { createPollScheduler, useAnimationFrameScheduler } from '../../lifecycle.js';
17
18
  import { installWeixinStyles } from './styles.js';
18
19
 
19
- const h = React.createElement;
20
-
21
20
  export const name = 'weixin-settings';
22
21
  export const inject = ['slots', 'connection'];
23
22
 
@@ -5,7 +5,7 @@ const CSS = String.raw`
5
5
  --dxw-accent: #07c160;
6
6
  --dxw-accent-dark: #05994c;
7
7
  --dxw-success: var(--dsw-alias-state-success-primary, #20a162);
8
- --dxw-warning: var(--dsw-alias-state-warning-primary, #d97706);
8
+ --dxw-warning: var(--dsw-alias-state-warn-primary, #d97706);
9
9
  --dxw-error: var(--dsw-alias-state-error-primary, #d54941);
10
10
  width: 100%;
11
11
  max-width: 880px;
@@ -24,20 +24,20 @@ const CSS = String.raw`
24
24
  .dxw-heading p { color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 20px; margin-top: 5px; white-space: nowrap; }
25
25
  .dxw-tools, .dxw-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }
26
26
  .dxw-tools { width: 100%; justify-content: space-between; flex-wrap: nowrap; }
27
- .dxw-badge { display: inline-flex; align-items: center; gap: 7px; min-height: 30px; padding: 0 11px; border-radius: 999px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-fill-secondary, #f2f3f5); font-size: 12px; white-space: nowrap; }
27
+ .dxw-badge { display: inline-flex; align-items: center; gap: 7px; min-height: 30px; padding: 0 11px; border-radius: 999px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-module-platform, #f2f3f5); font-size: 12px; white-space: nowrap; }
28
28
  .dxw-dot { width: 8px; height: 8px; border-radius: 50%; background: #aeb3bb; flex: none; }
29
29
  .dxw-dot[data-tone="success"] { background: var(--dxw-success); box-shadow: 0 0 0 3px color-mix(in srgb, var(--dxw-success) 14%, transparent); }
30
30
  .dxw-dot[data-tone="warning"] { background: var(--dxw-warning); }
31
31
  .dxw-dot[data-tone="error"] { background: var(--dxw-error); }
32
- .dxw-button { min-height: 34px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 8px; padding: 0 13px; display: inline-flex; align-items: center; justify-content: center; gap: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-body, #fff); font: inherit; font-size: 13px; font-weight: 560; cursor: pointer; text-decoration: none; transition: border-color .15s ease, background .15s ease, transform .15s ease; }
33
- .dxw-button:hover:not(:disabled) { border-color: #aeb3bb; background: var(--dsw-alias-fill-tertiary, #f7f8fa); }
32
+ .dxw-button { min-height: 34px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; padding: 0 13px; display: inline-flex; align-items: center; justify-content: center; gap: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 13px; font-weight: 560; cursor: pointer; text-decoration: none; transition: border-color .15s ease, background .15s ease, transform .15s ease; }
33
+ .dxw-button:hover:not(:disabled) { border-color: #aeb3bb; background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
34
34
  .dxw-button:active:not(:disabled) { transform: translateY(1px); }
35
35
  .dxw-button:focus-visible, .dxw-input:focus-visible { outline: 2px solid color-mix(in srgb, var(--dxw-accent) 70%, white); outline-offset: 2px; }
36
36
  .dxw-button:disabled { cursor: not-allowed; opacity: .55; }
37
37
  .dxw-button[data-kind="primary"] { color: white; border-color: var(--dxw-accent); background: var(--dxw-accent); }
38
38
  .dxw-button[data-kind="primary"]:hover:not(:disabled) { border-color: var(--dxw-accent-dark); background: var(--dxw-accent-dark); }
39
39
  .dxw-button[data-kind="danger"] { color: var(--dxw-error); }
40
- .dxw-card { overflow: hidden; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-body, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); }
40
+ .dxw-card { overflow: hidden; border: 1px solid var(--dsw-alias-border-l2, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); }
41
41
  .dxw-cardBody { padding: 24px; }
42
42
  .dxw-empty { min-height: 230px; display: grid; grid-template-columns: minmax(0, 1fr) 180px; align-items: center; gap: 30px; }
43
43
  .dxw-empty h3 { font-size: 18px; margin-bottom: 8px; }
@@ -47,7 +47,7 @@ const CSS = String.raw`
47
47
  .dxw-logo svg { width: 62px; height: 62px; }
48
48
  .dxw-qrLayout { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 34px; align-items: center; }
49
49
  .dxw-qrColumn { display: flex; flex-direction: column; align-items: center; gap: 12px; }
50
- .dxw-qrFrame { position: relative; width: 270px; aspect-ratio: 1; display: grid; place-items: center; overflow: hidden; padding: 10px; border: 1px solid var(--dsw-alias-line-border, #e5e6eb); border-radius: 16px; background: white; }
50
+ .dxw-qrFrame { position: relative; width: 270px; aspect-ratio: 1; display: grid; place-items: center; overflow: hidden; padding: 10px; border: 1px solid var(--dsw-alias-border-l2, #e5e6eb); border-radius: 16px; background: white; }
51
51
  .dxw-qrFrame img { display: block; width: 100%; height: 100%; object-fit: contain; }
52
52
  .dxw-qrFallback { padding: 24px; text-align: center; color: #646a73; }
53
53
  .dxw-expired { position: absolute; inset: 0; display: grid; place-items: center; padding: 30px; color: white; text-align: center; font-weight: 650; background: rgb(31 35 41 / 76%); backdrop-filter: blur(3px); }
@@ -63,7 +63,7 @@ const CSS = String.raw`
63
63
  .dxw-verify h3 { margin: 8px 0; font-size: 19px; }
64
64
  .dxw-verify p { color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.6; }
65
65
  .dxw-codeRow { display: flex; justify-content: center; gap: 10px; margin: 24px 0 10px; }
66
- .dxw-input { width: 190px; height: 42px; padding: 0 14px; border: 1px solid var(--dsw-alias-line-border, #dfe1e5); border-radius: 9px; background: var(--dsw-alias-bg-body, white); color: inherit; font: inherit; font-size: 18px; letter-spacing: .16em; text-align: center; }
66
+ .dxw-input { width: 190px; height: 42px; padding: 0 14px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 9px; background: var(--dsw-alias-bg-layer-1, white); color: inherit; font: inherit; font-size: 18px; letter-spacing: .16em; text-align: center; }
67
67
  .dxw-statusNotice, .dxw-error { display: flex; align-items: center; gap: 10px; padding: 13px 15px; border: 1px solid color-mix(in srgb, var(--dxw-error) 28%, transparent); border-radius: 10px; color: var(--dxw-error); background: color-mix(in srgb, var(--dxw-error) 7%, transparent); font-size: 13px; }
68
68
  .dxw-error { align-items: flex-start; flex-direction: column; padding: 22px; }
69
69
  .dxw-error h3 { font-size: 17px; }
@@ -78,10 +78,10 @@ const CSS = String.raw`
78
78
  .dxw-accountIdentity p { color: var(--dsw-alias-label-secondary, #646a73); font: 12px ui-monospace, SFMono-Regular, monospace; margin-top: 4px; }
79
79
  .dxw-health { display: inline-flex; align-items: center; gap: 7px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; white-space: nowrap; }
80
80
  .dxw-metrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin: 20px 0; }
81
- .dxw-metric { padding: 12px 14px; border-radius: 9px; background: var(--dsw-alias-fill-tertiary, #f7f8fa); }
81
+ .dxw-metric { padding: 12px 14px; border-radius: 9px; background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
82
82
  .dxw-metric dt { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; }
83
83
  .dxw-metric dd { overflow: hidden; margin: 5px 0 0; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
84
- .dxw-accountFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-line-divider, #eef0f3); }
84
+ .dxw-accountFooter { display: flex; align-items: center; justify-content: space-between; gap: 15px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-border-l1, #eef0f3); }
85
85
  .dxw-accountFooter .dxw-actions { flex: none; flex-wrap: nowrap; gap: 8px; margin-top: 0; }
86
86
  .dxw-accountFooter .dxw-button { flex: none; white-space: nowrap; }
87
87
  .dxw-summary { color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; }
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
 
3
3
  import { WhatsappLogoGlyph } from '../../channel-logos.js';
4
4
  import { QrActionIcon } from '../../credential-binding.js';
5
+ import { h } from '../../i18n.js';
5
6
  import { installDingtalkStyles } from '../dingtalk/styles.js';
6
7
  import {
7
8
  WHATSAPP_ENDPOINTS,
@@ -14,7 +15,6 @@ import {
14
15
  } from './api.js';
15
16
  import { installWhatsappStyles } from './styles.js';
16
17
 
17
- const h = React.createElement;
18
18
  const ACTIVE_STATES = new Set(['pending', 'connecting']);
19
19
 
20
20
  const Button = React.forwardRef(function Button(
@@ -1,6 +1,5 @@
1
1
  import * as React from 'react';
2
-
3
- const h = React.createElement;
2
+ import { h } from './i18n.js';
4
3
 
5
4
  function ActionIcon({ children }) {
6
5
  return h('svg', {