@nextclaw/ui 0.9.12 → 0.9.14

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 (75) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/package.json +17 -16
  3. package/public/logos/weixin.svg +5 -0
  4. package/src/components/chat/ncp/ncp-app-client-fetch.test.ts +9 -0
  5. package/src/components/chat/ncp/ncp-app-client-fetch.ts +10 -0
  6. package/src/components/chat/ncp/ncp-session-adapter.ts +0 -1
  7. package/src/components/config/ChannelForm.tsx +1 -122
  8. package/src/components/config/ChannelsList.test.tsx +127 -0
  9. package/src/components/config/ChannelsList.tsx +2 -1
  10. package/src/components/config/channel-form-fields.ts +131 -0
  11. package/src/lib/i18n.channels.ts +52 -0
  12. package/src/lib/i18n.ts +2 -43
  13. package/src/lib/logos.ts +1 -0
  14. package/src/transport/app-client.test.ts +49 -0
  15. package/src/transport/app-client.ts +22 -6
  16. package/src/transport/local.transport.ts +4 -1
  17. package/src/transport/remote.transport.ts +5 -1
  18. package/src/transport/sse-stream.test.ts +68 -0
  19. package/src/transport/sse-stream.ts +48 -7
  20. package/src/transport/transport.types.ts +2 -0
  21. package/LICENSE +0 -21
  22. package/dist/assets/ChannelsList-CJy2GG1a.js +0 -1
  23. package/dist/assets/ChatPage-C7WxI8VY.js +0 -41
  24. package/dist/assets/DocBrowser-Nu-ae-eS.js +0 -1
  25. package/dist/assets/LogoBadge-DbbMxPlr.js +0 -1
  26. package/dist/assets/MarketplacePage-BQYQPeg2.js +0 -49
  27. package/dist/assets/McpMarketplacePage-kiMJbS8r.js +0 -40
  28. package/dist/assets/ModelConfig-DRQ07Snj.js +0 -1
  29. package/dist/assets/ProvidersList-C0NjzKX1.js +0 -1
  30. package/dist/assets/RemoteAccessPage-DVJ5hBNJ.js +0 -1
  31. package/dist/assets/RuntimeConfig-BkYWyRW7.js +0 -1
  32. package/dist/assets/SearchConfig-DZTW8Wnq.js +0 -1
  33. package/dist/assets/SecretsConfig-WMcwg5KV.js +0 -3
  34. package/dist/assets/SessionsConfig-CWtCXQRn.js +0 -2
  35. package/dist/assets/chat-message-BcjCODYN.js +0 -3
  36. package/dist/assets/config-hints-CApS3K_7.js +0 -1
  37. package/dist/assets/config-layout-BHnOoweL.js +0 -1
  38. package/dist/assets/index-BOhlxC12.js +0 -8
  39. package/dist/assets/index-SGSkQCPi.css +0 -1
  40. package/dist/assets/label-DOWMfYPL.js +0 -1
  41. package/dist/assets/marketplace-localization-Dk31LJJJ.js +0 -1
  42. package/dist/assets/page-layout-DQtmTgqR.js +0 -1
  43. package/dist/assets/popover-k11l1-ko.js +0 -1
  44. package/dist/assets/provider-models-BOeNnjk9.js +0 -1
  45. package/dist/assets/security-config-FFy-bOJb.js +0 -1
  46. package/dist/assets/skeleton-DQ4QRdSe.js +0 -1
  47. package/dist/assets/status-dot-CsZRxe8p.js +0 -1
  48. package/dist/assets/switch-DfMy8G96.js +0 -1
  49. package/dist/assets/tabs-custom-CITPDGXY.js +0 -1
  50. package/dist/assets/useConfirmDialog-Dr39o-0I.js +0 -1
  51. package/dist/assets/vendor-TJ2hy_Lv.js +0 -441
  52. package/dist/index.html +0 -18
  53. package/dist/logo.svg +0 -5
  54. package/dist/logos/aihubmix.png +0 -0
  55. package/dist/logos/anthropic.svg +0 -1
  56. package/dist/logos/dashscope.png +0 -0
  57. package/dist/logos/deepseek.png +0 -0
  58. package/dist/logos/dingtalk.svg +0 -1
  59. package/dist/logos/discord.svg +0 -1
  60. package/dist/logos/email.svg +0 -1
  61. package/dist/logos/feishu.svg +0 -12
  62. package/dist/logos/gemini.svg +0 -1
  63. package/dist/logos/groq.svg +0 -1
  64. package/dist/logos/minimax.svg +0 -1
  65. package/dist/logos/mochat.svg +0 -6
  66. package/dist/logos/moonshot.png +0 -0
  67. package/dist/logos/openai.svg +0 -1
  68. package/dist/logos/openrouter.svg +0 -1
  69. package/dist/logos/qq.svg +0 -1
  70. package/dist/logos/slack.svg +0 -1
  71. package/dist/logos/telegram.svg +0 -1
  72. package/dist/logos/vllm.svg +0 -1
  73. package/dist/logos/wecom.svg +0 -11
  74. package/dist/logos/whatsapp.svg +0 -1
  75. package/dist/logos/zhipu.svg +0 -15
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @nextclaw/ui
2
2
 
3
+ ## 0.9.14
4
+
5
+ ### Patch Changes
6
+
7
+ - Add the Weixin channel entry to the Channels page so users can configure personal Weixin accounts directly from the frontend.
8
+
9
+ ## 0.9.13
10
+
11
+ ### Patch Changes
12
+
13
+ - Fix local UI runtime probe fallback so local NextClaw instances keep using local transport
14
+ instead of breaking on `/_remote/runtime` HTML responses.
15
+
3
16
  ## 0.9.12
4
17
 
5
18
  ### Patch Changes
package/package.json CHANGED
@@ -1,10 +1,24 @@
1
1
  {
2
2
  "name": "@nextclaw/ui",
3
- "version": "0.9.12",
3
+ "version": "0.9.14",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
7
+ "scripts": {
8
+ "dev": "vite",
9
+ "build": "tsc && vite build",
10
+ "prepack": "pnpm run build",
11
+ "preview": "vite preview",
12
+ "lint": "eslint .",
13
+ "tsc": "tsc --noEmit",
14
+ "test": "vitest run"
15
+ },
7
16
  "dependencies": {
17
+ "@nextclaw/agent-chat": "workspace:*",
18
+ "@nextclaw/agent-chat-ui": "workspace:*",
19
+ "@nextclaw/ncp": "workspace:*",
20
+ "@nextclaw/ncp-http-agent-client": "workspace:*",
21
+ "@nextclaw/ncp-react": "workspace:*",
8
22
  "@radix-ui/react-dialog": "^1.1.2",
9
23
  "@radix-ui/react-label": "^2.1.0",
10
24
  "@radix-ui/react-popover": "^1.1.15",
@@ -26,12 +40,7 @@
26
40
  "sonner": "^1.7.1",
27
41
  "tailwind-merge": "^2.5.4",
28
42
  "zod": "^3.23.8",
29
- "zustand": "^5.0.2",
30
- "@nextclaw/ncp": "0.3.1",
31
- "@nextclaw/ncp-http-agent-client": "0.3.1",
32
- "@nextclaw/agent-chat-ui": "0.2.1",
33
- "@nextclaw/ncp-react": "0.3.2",
34
- "@nextclaw/agent-chat": "0.1.1"
43
+ "zustand": "^5.0.2"
35
44
  },
36
45
  "devDependencies": {
37
46
  "@testing-library/react": "^16.3.0",
@@ -48,13 +57,5 @@
48
57
  "typescript": "^5.6.3",
49
58
  "vite": "^6.0.1",
50
59
  "vitest": "^2.1.2"
51
- },
52
- "scripts": {
53
- "dev": "vite",
54
- "build": "tsc && vite build",
55
- "preview": "vite preview",
56
- "lint": "eslint .",
57
- "tsc": "tsc --noEmit",
58
- "test": "vitest run"
59
60
  }
60
- }
61
+ }
@@ -0,0 +1,5 @@
1
+ <svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="128" height="128" rx="28" fill="#07C160"/>
3
+ <path d="M49.579 31C28.272 31 11 45.904 11 64.281c0 10.576 5.753 20.006 14.731 26.155L20.58 104l16.847-8.932a45.23 45.23 0 0 0 12.152 1.637c21.307 0 38.579-14.903 38.579-33.281C88.158 45.904 70.886 31 49.58 31Zm-13.03 20.08c2.223 0 4.026 1.726 4.026 3.855 0 2.13-1.803 3.856-4.026 3.856-2.224 0-4.027-1.726-4.027-3.856 0-2.129 1.803-3.855 4.027-3.855Zm26.58 7.711c-2.223 0-4.027-1.726-4.027-3.856 0-2.129 1.804-3.855 4.027-3.855 2.224 0 4.027 1.726 4.027 3.855 0 2.13-1.803 3.856-4.027 3.856Z" fill="#fff"/>
4
+ <path d="M82.807 49.043C68.447 49.043 56.807 58.944 56.807 71.155c0 12.21 11.64 22.111 26 22.111 4.228 0 8.218-.857 11.717-2.374L108.5 97l-4.618-11.29c5.164-4.02 8.425-9.116 8.425-14.555 0-12.211-11.64-22.112-25.5-22.112Zm-8.308 18.78c-1.617 0-2.928-1.19-2.928-2.66 0-1.468 1.311-2.658 2.928-2.658 1.618 0 2.929 1.19 2.929 2.659 0 1.468-1.311 2.659-2.929 2.659Zm16.615 0c-1.617 0-2.928-1.19-2.928-2.66 0-1.468 1.311-2.658 2.928-2.658 1.618 0 2.929 1.19 2.929 2.659 0 1.468-1.311 2.659-2.929 2.659Z" fill="#D6F8E4"/>
5
+ </svg>
@@ -61,6 +61,15 @@ describe('ncp-app-client-fetch', () => {
61
61
  method: 'GET',
62
62
  path: '/api/ncp/agent/stream?sessionId=s1',
63
63
  signal: undefined,
64
+ terminalEventPayloadTypes: {
65
+ 'ncp-event': [
66
+ 'message.completed',
67
+ 'message.failed',
68
+ 'run.finished',
69
+ 'run.error',
70
+ 'message.abort'
71
+ ]
72
+ },
64
73
  onEvent: expect.any(Function)
65
74
  });
66
75
  expect(text).toContain('event: ncp-event');
@@ -2,6 +2,13 @@ import { API_BASE } from '@/api/api-base';
2
2
  import { appClient } from '@/transport';
3
3
 
4
4
  type FetchLike = typeof fetch;
5
+ const NCP_TERMINAL_EVENT_PAYLOAD_TYPES = [
6
+ 'message.completed',
7
+ 'message.failed',
8
+ 'run.finished',
9
+ 'run.error',
10
+ 'message.abort'
11
+ ] as const;
5
12
 
6
13
  export function createNcpAppClientFetch(): FetchLike {
7
14
  return async (input, init) => {
@@ -92,6 +99,9 @@ function createSseResponse(request: RequestSnapshot): Response {
92
99
  path: request.path,
93
100
  ...(request.body !== undefined ? { body: request.body } : {}),
94
101
  signal: request.signal,
102
+ terminalEventPayloadTypes: {
103
+ 'ncp-event': NCP_TERMINAL_EVENT_PAYLOAD_TYPES
104
+ },
95
105
  onEvent: (event) => {
96
106
  controller.enqueue(encoder.encode(encodeSseFrame(event.name, event.payload)));
97
107
  }
@@ -168,7 +168,6 @@ export function adaptNcpMessageToUiMessage(message: NcpMessageView): UIMessage {
168
168
  }
169
169
 
170
170
  export function adaptNcpMessagesToUiMessages(messages: readonly NcpMessageView[]): UIMessage[] {
171
- console.log('[adaptNcpMessagesToUiMessages]', { messages });
172
171
  return messages.map(adaptNcpMessageToUiMessage);
173
172
  }
174
173
 
@@ -17,35 +17,12 @@ import type { ConfigActionManifest } from '@/api/types';
17
17
  import { resolveChannelTutorialUrl } from '@/lib/channel-tutorials';
18
18
  import { getChannelLogo } from '@/lib/logos';
19
19
  import { CONFIG_DETAIL_CARD_CLASS, CONFIG_EMPTY_DETAIL_CARD_CLASS } from './config-layout';
20
-
21
- type ChannelFieldType = 'boolean' | 'text' | 'email' | 'password' | 'number' | 'tags' | 'select' | 'json';
22
- type ChannelOption = { value: string; label: string };
23
- type ChannelField = { name: string; type: ChannelFieldType; label: string; options?: ChannelOption[] };
20
+ import { buildChannelFields } from './channel-form-fields';
24
21
 
25
22
  type ChannelFormProps = {
26
23
  channelName?: string;
27
24
  };
28
25
 
29
- const DM_POLICY_OPTIONS: ChannelOption[] = [
30
- { value: 'pairing', label: 'pairing' },
31
- { value: 'allowlist', label: 'allowlist' },
32
- { value: 'open', label: 'open' },
33
- { value: 'disabled', label: 'disabled' }
34
- ];
35
-
36
- const GROUP_POLICY_OPTIONS: ChannelOption[] = [
37
- { value: 'open', label: 'open' },
38
- { value: 'allowlist', label: 'allowlist' },
39
- { value: 'disabled', label: 'disabled' }
40
- ];
41
-
42
- const STREAMING_MODE_OPTIONS: ChannelOption[] = [
43
- { value: 'off', label: 'off' },
44
- { value: 'partial', label: 'partial' },
45
- { value: 'block', label: 'block' },
46
- { value: 'progress', label: 'progress' }
47
- ];
48
-
49
26
  const getFieldIcon = (fieldName: string) => {
50
27
  if (fieldName.includes('token') || fieldName.includes('secret') || fieldName.includes('password')) {
51
28
  return <KeyRound className="h-3.5 w-3.5 text-gray-500" />;
@@ -65,104 +42,6 @@ const getFieldIcon = (fieldName: string) => {
65
42
  return <Settings className="h-3.5 w-3.5 text-gray-500" />;
66
43
  };
67
44
 
68
- function buildChannelFields(): Record<string, ChannelField[]> {
69
- return {
70
- telegram: [
71
- { name: 'enabled', type: 'boolean', label: t('enabled') },
72
- { name: 'token', type: 'password', label: t('botToken') },
73
- { name: 'allowFrom', type: 'tags', label: t('allowFrom') },
74
- { name: 'proxy', type: 'text', label: t('proxy') },
75
- { name: 'accountId', type: 'text', label: t('accountId') },
76
- { name: 'dmPolicy', type: 'select', label: t('dmPolicy'), options: DM_POLICY_OPTIONS },
77
- { name: 'groupPolicy', type: 'select', label: t('groupPolicy'), options: GROUP_POLICY_OPTIONS },
78
- { name: 'groupAllowFrom', type: 'tags', label: t('groupAllowFrom') },
79
- { name: 'requireMention', type: 'boolean', label: t('requireMention') },
80
- { name: 'mentionPatterns', type: 'tags', label: t('mentionPatterns') },
81
- { name: 'groups', type: 'json', label: t('groupRulesJson') }
82
- ],
83
- discord: [
84
- { name: 'enabled', type: 'boolean', label: t('enabled') },
85
- { name: 'token', type: 'password', label: t('botToken') },
86
- { name: 'allowBots', type: 'boolean', label: t('allowBotMessages') },
87
- { name: 'allowFrom', type: 'tags', label: t('allowFrom') },
88
- { name: 'gatewayUrl', type: 'text', label: t('gatewayUrl') },
89
- { name: 'intents', type: 'number', label: t('intents') },
90
- { name: 'proxy', type: 'text', label: t('proxy') },
91
- { name: 'mediaMaxMb', type: 'number', label: t('attachmentMaxSizeMb') },
92
- { name: 'streaming', type: 'select', label: t('streamingMode'), options: STREAMING_MODE_OPTIONS },
93
- { name: 'draftChunk', type: 'json', label: t('draftChunkingJson') },
94
- { name: 'textChunkLimit', type: 'number', label: t('textChunkLimit') },
95
- { name: 'accountId', type: 'text', label: t('accountId') },
96
- { name: 'dmPolicy', type: 'select', label: t('dmPolicy'), options: DM_POLICY_OPTIONS },
97
- { name: 'groupPolicy', type: 'select', label: t('groupPolicy'), options: GROUP_POLICY_OPTIONS },
98
- { name: 'groupAllowFrom', type: 'tags', label: t('groupAllowFrom') },
99
- { name: 'requireMention', type: 'boolean', label: t('requireMention') },
100
- { name: 'mentionPatterns', type: 'tags', label: t('mentionPatterns') },
101
- { name: 'groups', type: 'json', label: t('groupRulesJson') }
102
- ],
103
- whatsapp: [
104
- { name: 'enabled', type: 'boolean', label: t('enabled') },
105
- { name: 'bridgeUrl', type: 'text', label: t('bridgeUrl') },
106
- { name: 'allowFrom', type: 'tags', label: t('allowFrom') }
107
- ],
108
- feishu: [
109
- { name: 'enabled', type: 'boolean', label: t('enabled') },
110
- { name: 'appId', type: 'text', label: t('appId') },
111
- { name: 'appSecret', type: 'password', label: t('appSecret') },
112
- { name: 'encryptKey', type: 'password', label: t('encryptKey') },
113
- { name: 'verificationToken', type: 'password', label: t('verificationToken') },
114
- { name: 'allowFrom', type: 'tags', label: t('allowFrom') }
115
- ],
116
- dingtalk: [
117
- { name: 'enabled', type: 'boolean', label: t('enabled') },
118
- { name: 'clientId', type: 'text', label: t('clientId') },
119
- { name: 'clientSecret', type: 'password', label: t('clientSecret') },
120
- { name: 'allowFrom', type: 'tags', label: t('allowFrom') }
121
- ],
122
- wecom: [
123
- { name: 'enabled', type: 'boolean', label: t('enabled') },
124
- { name: 'corpId', type: 'text', label: t('corpId') },
125
- { name: 'agentId', type: 'text', label: t('agentId') },
126
- { name: 'secret', type: 'password', label: t('secret') },
127
- { name: 'token', type: 'password', label: t('token') },
128
- { name: 'callbackPort', type: 'number', label: t('callbackPort') },
129
- { name: 'callbackPath', type: 'text', label: t('callbackPath') },
130
- { name: 'allowFrom', type: 'tags', label: t('allowFrom') }
131
- ],
132
- slack: [
133
- { name: 'enabled', type: 'boolean', label: t('enabled') },
134
- { name: 'mode', type: 'text', label: t('mode') },
135
- { name: 'webhookPath', type: 'text', label: t('webhookPath') },
136
- { name: 'allowBots', type: 'boolean', label: t('allowBotMessages') },
137
- { name: 'botToken', type: 'password', label: t('botToken') },
138
- { name: 'appToken', type: 'password', label: t('appToken') }
139
- ],
140
- email: [
141
- { name: 'enabled', type: 'boolean', label: t('enabled') },
142
- { name: 'consentGranted', type: 'boolean', label: t('consentGranted') },
143
- { name: 'imapHost', type: 'text', label: t('imapHost') },
144
- { name: 'imapPort', type: 'number', label: t('imapPort') },
145
- { name: 'imapUsername', type: 'text', label: t('imapUsername') },
146
- { name: 'imapPassword', type: 'password', label: t('imapPassword') },
147
- { name: 'fromAddress', type: 'email', label: t('fromAddress') }
148
- ],
149
- mochat: [
150
- { name: 'enabled', type: 'boolean', label: t('enabled') },
151
- { name: 'baseUrl', type: 'text', label: t('baseUrl') },
152
- { name: 'clawToken', type: 'password', label: t('clawToken') },
153
- { name: 'agentUserId', type: 'text', label: t('agentUserId') },
154
- { name: 'allowFrom', type: 'tags', label: t('allowFrom') }
155
- ],
156
- qq: [
157
- { name: 'enabled', type: 'boolean', label: t('enabled') },
158
- { name: 'appId', type: 'text', label: t('appId') },
159
- { name: 'secret', type: 'password', label: t('appSecret') },
160
- { name: 'markdownSupport', type: 'boolean', label: t('markdownSupport') },
161
- { name: 'allowFrom', type: 'tags', label: t('allowFrom') }
162
- ]
163
- };
164
- }
165
-
166
45
  function isRecord(value: unknown): value is Record<string, unknown> {
167
46
  return typeof value === 'object' && value !== null && !Array.isArray(value);
168
47
  }
@@ -0,0 +1,127 @@
1
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { ChannelsList } from '@/components/config/ChannelsList';
4
+
5
+ const mocks = vi.hoisted(() => ({
6
+ mutate: vi.fn(),
7
+ mutateAsync: vi.fn(),
8
+ configQuery: {
9
+ data: {
10
+ channels: {
11
+ weixin: {
12
+ enabled: false,
13
+ defaultAccountId: '1344b2b24720@im.bot',
14
+ baseUrl: 'https://ilinkai.weixin.qq.com',
15
+ pollTimeoutMs: 35000,
16
+ allowFrom: ['o9cq804svxfyCCTIqzddDqRBeMC0@im.wechat'],
17
+ accounts: {
18
+ '1344b2b24720@im.bot': {
19
+ enabled: true
20
+ }
21
+ }
22
+ }
23
+ }
24
+ },
25
+ isLoading: false
26
+ },
27
+ metaQuery: {
28
+ data: {
29
+ channels: [
30
+ {
31
+ name: 'weixin',
32
+ displayName: 'Weixin',
33
+ enabled: false
34
+ }
35
+ ]
36
+ }
37
+ },
38
+ schemaQuery: {
39
+ data: {
40
+ uiHints: {
41
+ 'channels.weixin': {
42
+ label: 'Weixin',
43
+ help: 'Weixin QR login + getupdates long-poll channel'
44
+ },
45
+ 'channels.weixin.baseUrl': {
46
+ label: 'API Base URL'
47
+ }
48
+ },
49
+ actions: []
50
+ }
51
+ }
52
+ }));
53
+
54
+ vi.mock('@/hooks/useConfig', () => ({
55
+ useConfig: () => mocks.configQuery,
56
+ useConfigMeta: () => mocks.metaQuery,
57
+ useConfigSchema: () => mocks.schemaQuery,
58
+ useUpdateChannel: () => ({
59
+ mutate: mocks.mutate,
60
+ mutateAsync: mocks.mutateAsync,
61
+ isPending: false
62
+ }),
63
+ useExecuteConfigAction: () => ({
64
+ mutateAsync: vi.fn(),
65
+ isPending: false
66
+ })
67
+ }));
68
+
69
+ describe('ChannelsList', () => {
70
+ beforeEach(() => {
71
+ mocks.mutate.mockReset();
72
+ mocks.mutateAsync.mockReset();
73
+ });
74
+
75
+ it('renders weixin and submits weixin-specific config fields', async () => {
76
+ const user = userEvent.setup();
77
+
78
+ render(<ChannelsList />);
79
+
80
+ await user.click(await screen.findByRole('button', { name: /All Channels/i }));
81
+
82
+ expect((await screen.findAllByText('Weixin')).length).toBeGreaterThan(0);
83
+ expect(await screen.findByLabelText('Default Account ID')).toBeTruthy();
84
+
85
+ const timeoutInput = await screen.findByLabelText('Long Poll Timeout (ms)');
86
+ await user.clear(timeoutInput);
87
+ await user.type(timeoutInput, '45000');
88
+
89
+ const accountsJson = await screen.findByLabelText('Accounts JSON');
90
+ await user.clear(accountsJson);
91
+ fireEvent.change(accountsJson, {
92
+ target: {
93
+ value: JSON.stringify(
94
+ {
95
+ '1344b2b24720@im.bot': {
96
+ enabled: true,
97
+ baseUrl: 'https://ilinkai.weixin.qq.com'
98
+ }
99
+ },
100
+ null,
101
+ 2
102
+ )
103
+ }
104
+ });
105
+
106
+ await user.click(screen.getByRole('button', { name: /save/i }));
107
+
108
+ await waitFor(() => {
109
+ expect(mocks.mutate).toHaveBeenCalledWith({
110
+ channel: 'weixin',
111
+ data: expect.objectContaining({
112
+ enabled: false,
113
+ defaultAccountId: '1344b2b24720@im.bot',
114
+ baseUrl: 'https://ilinkai.weixin.qq.com',
115
+ pollTimeoutMs: 45000,
116
+ allowFrom: ['o9cq804svxfyCCTIqzddDqRBeMC0@im.wechat'],
117
+ accounts: {
118
+ '1344b2b24720@im.bot': {
119
+ enabled: true,
120
+ baseUrl: 'https://ilinkai.weixin.qq.com'
121
+ }
122
+ }
123
+ })
124
+ });
125
+ });
126
+ });
127
+ });
@@ -20,7 +20,8 @@ const channelDescriptionKeys: Record<string, string> = {
20
20
  email: 'channelDescEmail',
21
21
  webhook: 'channelDescWebhook',
22
22
  discord: 'channelDescDiscord',
23
- feishu: 'channelDescFeishu'
23
+ feishu: 'channelDescFeishu',
24
+ weixin: 'channelDescWeixin'
24
25
  };
25
26
 
26
27
  export function ChannelsList() {
@@ -0,0 +1,131 @@
1
+ import { t } from '@/lib/i18n';
2
+
3
+ export type ChannelFieldType = 'boolean' | 'text' | 'email' | 'password' | 'number' | 'tags' | 'select' | 'json';
4
+ export type ChannelOption = { value: string; label: string };
5
+ export type ChannelField = { name: string; type: ChannelFieldType; label: string; options?: ChannelOption[] };
6
+
7
+ const DM_POLICY_OPTIONS: ChannelOption[] = [
8
+ { value: 'pairing', label: 'pairing' },
9
+ { value: 'allowlist', label: 'allowlist' },
10
+ { value: 'open', label: 'open' },
11
+ { value: 'disabled', label: 'disabled' }
12
+ ];
13
+
14
+ const GROUP_POLICY_OPTIONS: ChannelOption[] = [
15
+ { value: 'open', label: 'open' },
16
+ { value: 'allowlist', label: 'allowlist' },
17
+ { value: 'disabled', label: 'disabled' }
18
+ ];
19
+
20
+ const STREAMING_MODE_OPTIONS: ChannelOption[] = [
21
+ { value: 'off', label: 'off' },
22
+ { value: 'partial', label: 'partial' },
23
+ { value: 'block', label: 'block' },
24
+ { value: 'progress', label: 'progress' }
25
+ ];
26
+
27
+ export function buildChannelFields(): Record<string, ChannelField[]> {
28
+ return {
29
+ telegram: [
30
+ { name: 'enabled', type: 'boolean', label: t('enabled') },
31
+ { name: 'token', type: 'password', label: t('botToken') },
32
+ { name: 'allowFrom', type: 'tags', label: t('allowFrom') },
33
+ { name: 'proxy', type: 'text', label: t('proxy') },
34
+ { name: 'accountId', type: 'text', label: t('accountId') },
35
+ { name: 'dmPolicy', type: 'select', label: t('dmPolicy'), options: DM_POLICY_OPTIONS },
36
+ { name: 'groupPolicy', type: 'select', label: t('groupPolicy'), options: GROUP_POLICY_OPTIONS },
37
+ { name: 'groupAllowFrom', type: 'tags', label: t('groupAllowFrom') },
38
+ { name: 'requireMention', type: 'boolean', label: t('requireMention') },
39
+ { name: 'mentionPatterns', type: 'tags', label: t('mentionPatterns') },
40
+ { name: 'groups', type: 'json', label: t('groupRulesJson') }
41
+ ],
42
+ discord: [
43
+ { name: 'enabled', type: 'boolean', label: t('enabled') },
44
+ { name: 'token', type: 'password', label: t('botToken') },
45
+ { name: 'allowBots', type: 'boolean', label: t('allowBotMessages') },
46
+ { name: 'allowFrom', type: 'tags', label: t('allowFrom') },
47
+ { name: 'gatewayUrl', type: 'text', label: t('gatewayUrl') },
48
+ { name: 'intents', type: 'number', label: t('intents') },
49
+ { name: 'proxy', type: 'text', label: t('proxy') },
50
+ { name: 'mediaMaxMb', type: 'number', label: t('attachmentMaxSizeMb') },
51
+ { name: 'streaming', type: 'select', label: t('streamingMode'), options: STREAMING_MODE_OPTIONS },
52
+ { name: 'draftChunk', type: 'json', label: t('draftChunkingJson') },
53
+ { name: 'textChunkLimit', type: 'number', label: t('textChunkLimit') },
54
+ { name: 'accountId', type: 'text', label: t('accountId') },
55
+ { name: 'dmPolicy', type: 'select', label: t('dmPolicy'), options: DM_POLICY_OPTIONS },
56
+ { name: 'groupPolicy', type: 'select', label: t('groupPolicy'), options: GROUP_POLICY_OPTIONS },
57
+ { name: 'groupAllowFrom', type: 'tags', label: t('groupAllowFrom') },
58
+ { name: 'requireMention', type: 'boolean', label: t('requireMention') },
59
+ { name: 'mentionPatterns', type: 'tags', label: t('mentionPatterns') },
60
+ { name: 'groups', type: 'json', label: t('groupRulesJson') }
61
+ ],
62
+ whatsapp: [
63
+ { name: 'enabled', type: 'boolean', label: t('enabled') },
64
+ { name: 'bridgeUrl', type: 'text', label: t('bridgeUrl') },
65
+ { name: 'allowFrom', type: 'tags', label: t('allowFrom') }
66
+ ],
67
+ feishu: [
68
+ { name: 'enabled', type: 'boolean', label: t('enabled') },
69
+ { name: 'appId', type: 'text', label: t('appId') },
70
+ { name: 'appSecret', type: 'password', label: t('appSecret') },
71
+ { name: 'encryptKey', type: 'password', label: t('encryptKey') },
72
+ { name: 'verificationToken', type: 'password', label: t('verificationToken') },
73
+ { name: 'allowFrom', type: 'tags', label: t('allowFrom') }
74
+ ],
75
+ dingtalk: [
76
+ { name: 'enabled', type: 'boolean', label: t('enabled') },
77
+ { name: 'clientId', type: 'text', label: t('clientId') },
78
+ { name: 'clientSecret', type: 'password', label: t('clientSecret') },
79
+ { name: 'allowFrom', type: 'tags', label: t('allowFrom') }
80
+ ],
81
+ wecom: [
82
+ { name: 'enabled', type: 'boolean', label: t('enabled') },
83
+ { name: 'corpId', type: 'text', label: t('corpId') },
84
+ { name: 'agentId', type: 'text', label: t('agentId') },
85
+ { name: 'secret', type: 'password', label: t('secret') },
86
+ { name: 'token', type: 'password', label: t('token') },
87
+ { name: 'callbackPort', type: 'number', label: t('callbackPort') },
88
+ { name: 'callbackPath', type: 'text', label: t('callbackPath') },
89
+ { name: 'allowFrom', type: 'tags', label: t('allowFrom') }
90
+ ],
91
+ weixin: [
92
+ { name: 'enabled', type: 'boolean', label: t('enabled') },
93
+ { name: 'defaultAccountId', type: 'text', label: t('defaultAccountId') },
94
+ { name: 'baseUrl', type: 'text', label: t('baseUrl') },
95
+ { name: 'pollTimeoutMs', type: 'number', label: t('pollTimeoutMs') },
96
+ { name: 'allowFrom', type: 'tags', label: t('allowFrom') },
97
+ { name: 'accounts', type: 'json', label: t('accountsJson') }
98
+ ],
99
+ slack: [
100
+ { name: 'enabled', type: 'boolean', label: t('enabled') },
101
+ { name: 'mode', type: 'text', label: t('mode') },
102
+ { name: 'webhookPath', type: 'text', label: t('webhookPath') },
103
+ { name: 'allowBots', type: 'boolean', label: t('allowBotMessages') },
104
+ { name: 'botToken', type: 'password', label: t('botToken') },
105
+ { name: 'appToken', type: 'password', label: t('appToken') }
106
+ ],
107
+ email: [
108
+ { name: 'enabled', type: 'boolean', label: t('enabled') },
109
+ { name: 'consentGranted', type: 'boolean', label: t('consentGranted') },
110
+ { name: 'imapHost', type: 'text', label: t('imapHost') },
111
+ { name: 'imapPort', type: 'number', label: t('imapPort') },
112
+ { name: 'imapUsername', type: 'text', label: t('imapUsername') },
113
+ { name: 'imapPassword', type: 'password', label: t('imapPassword') },
114
+ { name: 'fromAddress', type: 'email', label: t('fromAddress') }
115
+ ],
116
+ mochat: [
117
+ { name: 'enabled', type: 'boolean', label: t('enabled') },
118
+ { name: 'baseUrl', type: 'text', label: t('baseUrl') },
119
+ { name: 'clawToken', type: 'password', label: t('clawToken') },
120
+ { name: 'agentUserId', type: 'text', label: t('agentUserId') },
121
+ { name: 'allowFrom', type: 'tags', label: t('allowFrom') }
122
+ ],
123
+ qq: [
124
+ { name: 'enabled', type: 'boolean', label: t('enabled') },
125
+ { name: 'appId', type: 'text', label: t('appId') },
126
+ { name: 'secret', type: 'password', label: t('appSecret') },
127
+ { name: 'markdownSupport', type: 'boolean', label: t('markdownSupport') },
128
+ { name: 'allowFrom', type: 'tags', label: t('allowFrom') }
129
+ ]
130
+ };
131
+ }
@@ -0,0 +1,52 @@
1
+ export const CHANNEL_LABELS: Record<string, { zh: string; en: string }> = {
2
+ channelsPageTitle: { zh: '消息渠道', en: 'Message Channels' },
3
+ channelsPageDescription: {
4
+ zh: '在一个页面中连续筛选、切换并配置各个消息渠道。',
5
+ en: 'Filter, switch, and configure messaging channels in one continuous workspace.'
6
+ },
7
+ channelsLoading: { zh: '加载渠道中...', en: 'Loading channels...' },
8
+ channelsTabEnabled: { zh: '已启用', en: 'Enabled' },
9
+ channelsTabAll: { zh: '全部渠道', en: 'All Channels' },
10
+ channelsFilterPlaceholder: { zh: '搜索渠道', en: 'Search channels' },
11
+ channelsNoMatch: { zh: '没有匹配的渠道', en: 'No matching channels' },
12
+ channelsSelectTitle: { zh: '选择左侧渠道开始配置', en: 'Select a channel from the left to configure' },
13
+ channelsSelectDescription: { zh: '你可以连续切换多个渠道并逐个保存配置。', en: 'Switch between channels continuously and save each configuration.' },
14
+ channelsFormDescription: { zh: '配置消息渠道参数', en: 'Configure message channel parameters' },
15
+ channelsEmptyTitle: { zh: '暂无启用渠道', en: 'No channels enabled' },
16
+ channelsEmptyDescription: { zh: '启用一个消息渠道以开始接收消息。', en: 'Enable a messaging channel to start receiving messages.' },
17
+ channelDescriptionDefault: { zh: '配置该通信渠道', en: 'Configure this communication channel' },
18
+ channelDescTelegram: { zh: '连接 Telegram 机器人以进行即时消息收发', en: 'Connect with Telegram bots for instant messaging' },
19
+ channelDescSlack: { zh: '接入 Slack 工作区进行团队协作消息处理', en: 'Integrate with Slack workspaces for team collaboration' },
20
+ channelDescEmail: { zh: '通过邮件协议收发消息', en: 'Send and receive messages via email protocols' },
21
+ channelDescWebhook: { zh: '接收 HTTP Webhook 以支持自定义集成', en: 'Receive HTTP webhooks for custom integrations' },
22
+ channelDescDiscord: { zh: '将 Discord 机器人连接到你的社区服务器', en: 'Connect Discord bots to your community servers' },
23
+ channelDescFeishu: { zh: '企业消息与协作平台接入', en: 'Enterprise messaging and collaboration platform' },
24
+ channelDescWeixin: { zh: '通过微信扫码登录并使用长轮询收发个人微信消息', en: 'Use QR login and long-poll updates for personal Weixin messaging' },
25
+ configureMessageChannelParameters: { zh: '配置消息渠道参数', en: 'Configure message channel parameters' },
26
+ channelsGuideTitle: { zh: '查看指南', en: 'View Guide' },
27
+ allowFrom: { zh: '允许来源', en: 'Allow From' },
28
+ token: { zh: 'Token', en: 'Token' },
29
+ botToken: { zh: 'Bot Token', en: 'Bot Token' },
30
+ appToken: { zh: 'App Token', en: 'App Token' },
31
+ appId: { zh: 'App ID', en: 'App ID' },
32
+ corpId: { zh: '企业 ID', en: 'Corp ID' },
33
+ agentId: { zh: '应用 Agent ID', en: 'Agent ID' },
34
+ appSecret: { zh: 'App Secret', en: 'App Secret' },
35
+ markdownSupport: { zh: 'Markdown 支持', en: 'Markdown Support' },
36
+ clientId: { zh: 'Client ID', en: 'Client ID' },
37
+ clientSecret: { zh: 'Client Secret', en: 'Client Secret' },
38
+ encryptKey: { zh: '加密密钥', en: 'Encrypt Key' },
39
+ verificationToken: { zh: '验证令牌', en: 'Verification Token' },
40
+ bridgeUrl: { zh: '桥接 URL', en: 'Bridge URL' },
41
+ gatewayUrl: { zh: '网关 URL', en: 'Gateway URL' },
42
+ proxy: { zh: '代理', en: 'Proxy' },
43
+ intents: { zh: 'Intents', en: 'Intents' },
44
+ mode: { zh: '模式', en: 'Mode' },
45
+ webhookPath: { zh: 'Webhook 路径', en: 'Webhook Path' },
46
+ callbackPort: { zh: '回调端口', en: 'Callback Port' },
47
+ callbackPath: { zh: '回调路径', en: 'Callback Path' },
48
+ defaultAccountId: { zh: '默认账号 ID', en: 'Default Account ID' },
49
+ pollTimeoutMs: { zh: '长轮询超时(毫秒)', en: 'Long Poll Timeout (ms)' },
50
+ accountsJson: { zh: '账号配置 JSON', en: 'Accounts JSON' },
51
+ groupPolicy: { zh: '群组策略', en: 'Group Policy' }
52
+ };
package/src/lib/i18n.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { CHANNEL_LABELS } from './i18n.channels';
1
2
  import { MARKETPLACE_LABELS } from './i18n.marketplace';
2
3
  import { REMOTE_LABELS } from './i18n.remote';
3
4
 
@@ -312,49 +313,7 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
312
313
  leaveBlankToKeepUnchanged: { zh: '留空则保持不变', en: 'Leave blank to keep unchanged' },
313
314
 
314
315
  // Channel
315
- channelsPageTitle: { zh: '消息渠道', en: 'Message Channels' },
316
- channelsPageDescription: { zh: '在一个页面中连续筛选、切换并配置各个消息渠道。', en: 'Filter, switch, and configure messaging channels in one continuous workspace.' },
317
- channelsLoading: { zh: '加载渠道中...', en: 'Loading channels...' },
318
- channelsTabEnabled: { zh: '已启用', en: 'Enabled' },
319
- channelsTabAll: { zh: '全部渠道', en: 'All Channels' },
320
- channelsFilterPlaceholder: { zh: '搜索渠道', en: 'Search channels' },
321
- channelsNoMatch: { zh: '没有匹配的渠道', en: 'No matching channels' },
322
- channelsSelectTitle: { zh: '选择左侧渠道开始配置', en: 'Select a channel from the left to configure' },
323
- channelsSelectDescription: { zh: '你可以连续切换多个渠道并逐个保存配置。', en: 'Switch between channels continuously and save each configuration.' },
324
- channelsFormDescription: { zh: '配置消息渠道参数', en: 'Configure message channel parameters' },
325
- channelsEmptyTitle: { zh: '暂无启用渠道', en: 'No channels enabled' },
326
- channelsEmptyDescription: { zh: '启用一个消息渠道以开始接收消息。', en: 'Enable a messaging channel to start receiving messages.' },
327
- channelDescriptionDefault: { zh: '配置该通信渠道', en: 'Configure this communication channel' },
328
- channelDescTelegram: { zh: '连接 Telegram 机器人以进行即时消息收发', en: 'Connect with Telegram bots for instant messaging' },
329
- channelDescSlack: { zh: '接入 Slack 工作区进行团队协作消息处理', en: 'Integrate with Slack workspaces for team collaboration' },
330
- channelDescEmail: { zh: '通过邮件协议收发消息', en: 'Send and receive messages via email protocols' },
331
- channelDescWebhook: { zh: '接收 HTTP Webhook 以支持自定义集成', en: 'Receive HTTP webhooks for custom integrations' },
332
- channelDescDiscord: { zh: '将 Discord 机器人连接到你的社区服务器', en: 'Connect Discord bots to your community servers' },
333
- channelDescFeishu: { zh: '企业消息与协作平台接入', en: 'Enterprise messaging and collaboration platform' },
334
- configureMessageChannelParameters: { zh: '配置消息渠道参数', en: 'Configure message channel parameters' },
335
- channelsGuideTitle: { zh: '查看指南', en: 'View Guide' },
336
- allowFrom: { zh: '允许来源', en: 'Allow From' },
337
- token: { zh: 'Token', en: 'Token' },
338
- botToken: { zh: 'Bot Token', en: 'Bot Token' },
339
- appToken: { zh: 'App Token', en: 'App Token' },
340
- appId: { zh: 'App ID', en: 'App ID' },
341
- corpId: { zh: '企业 ID', en: 'Corp ID' },
342
- agentId: { zh: '应用 Agent ID', en: 'Agent ID' },
343
- appSecret: { zh: 'App Secret', en: 'App Secret' },
344
- markdownSupport: { zh: 'Markdown 支持', en: 'Markdown Support' },
345
- clientId: { zh: 'Client ID', en: 'Client ID' },
346
- clientSecret: { zh: 'Client Secret', en: 'Client Secret' },
347
- encryptKey: { zh: '加密密钥', en: 'Encrypt Key' },
348
- verificationToken: { zh: '验证令牌', en: 'Verification Token' },
349
- bridgeUrl: { zh: '桥接 URL', en: 'Bridge URL' },
350
- gatewayUrl: { zh: '网关 URL', en: 'Gateway URL' },
351
- proxy: { zh: '代理', en: 'Proxy' },
352
- intents: { zh: 'Intents', en: 'Intents' },
353
- mode: { zh: '模式', en: 'Mode' },
354
- webhookPath: { zh: 'Webhook 路径', en: 'Webhook Path' },
355
- callbackPort: { zh: '回调端口', en: 'Callback Port' },
356
- callbackPath: { zh: '回调路径', en: 'Callback Path' },
357
- groupPolicy: { zh: '群组策略', en: 'Group Policy' },
316
+ ...CHANNEL_LABELS,
358
317
  consentGranted: { zh: '同意条款', en: 'Consent Granted' },
359
318
  imapHost: { zh: 'IMAP 服务器', en: 'IMAP Host' },
360
319
  imapPort: { zh: 'IMAP 端口', en: 'IMAP Port' },