@nextclaw/ui 0.9.13 → 0.9.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/assets/ChannelsList-Cu_hLbps.js +1 -0
- package/dist/assets/{ChatPage-B9dHVmrV.js → ChatPage-Dmpau_7n.js} +2 -2
- package/dist/assets/{DocBrowser-S-1-qnZQ.js → DocBrowser-C3ijFxFF.js} +1 -1
- package/dist/assets/{LogoBadge-t1JzzCtI.js → LogoBadge-BgjXmBcw.js} +1 -1
- package/dist/assets/{MarketplacePage-CzIHYJpM.js → MarketplacePage-CAIdEiw8.js} +1 -1
- package/dist/assets/{McpMarketplacePage-BTJdjNQ1.js → McpMarketplacePage-DPtH1xcY.js} +1 -1
- package/dist/assets/ModelConfig-D-pqArCg.js +1 -0
- package/dist/assets/{ProvidersList-BOQArFRk.js → ProvidersList-DnWsJqMQ.js} +1 -1
- package/dist/assets/{RemoteAccessPage-CYNQ53xu.js → RemoteAccessPage-BrXq-x0-.js} +1 -1
- package/dist/assets/{RuntimeConfig-B0B73pye.js → RuntimeConfig-UE9VaFO7.js} +1 -1
- package/dist/assets/{SearchConfig-CKy2QkAP.js → SearchConfig-CP-RM3V3.js} +1 -1
- package/dist/assets/{SecretsConfig-BpZLUu88.js → SecretsConfig-CfN_bazs.js} +1 -1
- package/dist/assets/{SessionsConfig-CoFI6Fa2.js → SessionsConfig-CgkKzKGv.js} +1 -1
- package/dist/assets/{chat-message-D3jZIASl.js → chat-message-CGL3sMsS.js} +1 -1
- package/dist/assets/index-D4alkESd.js +8 -0
- package/dist/assets/{label-BOvIOmQx.js → label-CbOSodIL.js} +1 -1
- package/dist/assets/{page-layout-PG3cwSpz.js → page-layout-BtDnyNLf.js} +1 -1
- package/dist/assets/{popover-BB-kINz7.js → popover-DGlUjPQc.js} +1 -1
- package/dist/assets/{security-config-Bb6l-viE.js → security-config-D6Bs1yoK.js} +1 -1
- package/dist/assets/{skeleton-CLSc5FYO.js → skeleton-BLV99JbX.js} +1 -1
- package/dist/assets/{status-dot-Behu7kDZ.js → status-dot-C8vM3IN1.js} +1 -1
- package/dist/assets/{switch-CvNG9775.js → switch-AuwUiga3.js} +1 -1
- package/dist/assets/{tabs-custom-CUdBQO_7.js → tabs-custom-CTS7SaFG.js} +1 -1
- package/dist/assets/{useConfirmDialog-CLLe2uIJ.js → useConfirmDialog-DrMAdNfN.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/logos/weixin.svg +5 -0
- package/package.json +3 -3
- package/public/logos/weixin.svg +5 -0
- package/src/api/config.ts +4 -2
- package/src/components/chat/chat-stream/transport.ts +42 -2
- package/src/components/config/ChannelForm.tsx +1 -122
- package/src/components/config/ChannelsList.test.tsx +127 -0
- package/src/components/config/ChannelsList.tsx +2 -1
- package/src/components/config/ModelConfig.tsx +1 -4
- package/src/components/config/channel-form-fields.ts +131 -0
- package/src/lib/i18n.channels.ts +52 -0
- package/src/lib/i18n.ts +2 -43
- package/src/lib/logos.ts +1 -0
- package/src/transport/app-client.ts +6 -22
- package/src/transport/local.transport.ts +4 -3
- package/src/transport/remote.transport.ts +7 -2
- package/src/transport/sse-stream.test.ts +54 -0
- package/src/transport/sse-stream.ts +3 -17
- package/dist/assets/ChannelsList-bROKR37R.js +0 -1
- package/dist/assets/ModelConfig-BD4o3Kna.js +0 -1
- package/dist/assets/index-CmGwUgcl.js +0 -8
- package/src/components/config/ModelConfig.test.tsx +0 -78
package/dist/index.html
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
8
|
<title>NextClaw - 系统配置</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-D4alkESd.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vendor-TJ2hy_Lv.js">
|
|
11
11
|
<link rel="stylesheet" crossorigin href="/assets/index-SGSkQCPi.css">
|
|
12
12
|
</head>
|
|
@@ -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>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/ui",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.15",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"@nextclaw/agent-chat": "0.1.1",
|
|
31
31
|
"@nextclaw/agent-chat-ui": "0.2.1",
|
|
32
32
|
"@nextclaw/ncp": "0.3.1",
|
|
33
|
-
"@nextclaw/ncp-
|
|
34
|
-
"@nextclaw/ncp-
|
|
33
|
+
"@nextclaw/ncp-http-agent-client": "0.3.1",
|
|
34
|
+
"@nextclaw/ncp-react": "0.3.2"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@testing-library/react": "^16.3.0",
|
|
@@ -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>
|
package/src/api/config.ts
CHANGED
|
@@ -143,8 +143,10 @@ export async function fetchConfigSchema(): Promise<ConfigSchemaResponse> {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
// PUT /api/config/model
|
|
146
|
-
export async function updateModel(data: {
|
|
147
|
-
|
|
146
|
+
export async function updateModel(data: {
|
|
147
|
+
model: string;
|
|
148
|
+
}): Promise<{ model: string }> {
|
|
149
|
+
const response = await api.put<{ model: string }>('/api/config/model', data);
|
|
148
150
|
if (!response.ok) {
|
|
149
151
|
throw new Error(response.error.message);
|
|
150
152
|
}
|
|
@@ -34,6 +34,7 @@ export async function openSendTurnStream(params: {
|
|
|
34
34
|
onSessionEvent: (event: StreamSessionEvent) => void;
|
|
35
35
|
}) {
|
|
36
36
|
let readySessionKey = '';
|
|
37
|
+
let finalResult: { sessionKey: string; reply: string } | null = null;
|
|
37
38
|
const session = appClient.openStream<{ reply?: string; sessionKey?: string }>({
|
|
38
39
|
method: 'POST',
|
|
39
40
|
path: '/api/chat/turn/stream',
|
|
@@ -54,10 +55,29 @@ export async function openSendTurnStream(params: {
|
|
|
54
55
|
}
|
|
55
56
|
if (event.name === 'session_event') {
|
|
56
57
|
params.onSessionEvent({ data: event.payload as StreamSessionEvent['data'] });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (event.name === 'final') {
|
|
61
|
+
const payload = (event.payload ?? {}) as { sessionKey?: string; reply?: string };
|
|
62
|
+
finalResult = {
|
|
63
|
+
sessionKey:
|
|
64
|
+
typeof payload.sessionKey === 'string' && payload.sessionKey.trim()
|
|
65
|
+
? payload.sessionKey
|
|
66
|
+
: readySessionKey,
|
|
67
|
+
reply: typeof payload.reply === 'string' ? payload.reply : ''
|
|
68
|
+
};
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (event.name === 'error') {
|
|
72
|
+
const payload = (event.payload ?? {}) as { message?: string };
|
|
73
|
+
throw new Error(payload.message?.trim() || 'chat stream failed');
|
|
57
74
|
}
|
|
58
75
|
}
|
|
59
76
|
});
|
|
60
|
-
const result = await session.finished;
|
|
77
|
+
const result = await session.finished as { reply?: string; sessionKey?: string } | undefined;
|
|
78
|
+
if (finalResult !== null) {
|
|
79
|
+
return finalResult;
|
|
80
|
+
}
|
|
61
81
|
return {
|
|
62
82
|
sessionKey: typeof result?.sessionKey === 'string' && result.sessionKey.trim()
|
|
63
83
|
? result.sessionKey
|
|
@@ -75,6 +95,7 @@ export async function openResumeRunStream(params: {
|
|
|
75
95
|
onSessionEvent: (event: StreamSessionEvent) => void;
|
|
76
96
|
}) {
|
|
77
97
|
let readySessionKey = '';
|
|
98
|
+
let finalResult: { sessionKey: string; reply: string } | null = null;
|
|
78
99
|
const query = new URLSearchParams();
|
|
79
100
|
if (typeof params.fromEventIndex === 'number') {
|
|
80
101
|
query.set('fromEventIndex', String(Math.max(0, Math.trunc(params.fromEventIndex))));
|
|
@@ -101,10 +122,29 @@ export async function openResumeRunStream(params: {
|
|
|
101
122
|
}
|
|
102
123
|
if (event.name === 'session_event') {
|
|
103
124
|
params.onSessionEvent({ data: event.payload as StreamSessionEvent['data'] });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (event.name === 'final') {
|
|
128
|
+
const payload = (event.payload ?? {}) as { sessionKey?: string; reply?: string };
|
|
129
|
+
finalResult = {
|
|
130
|
+
sessionKey:
|
|
131
|
+
typeof payload.sessionKey === 'string' && payload.sessionKey.trim()
|
|
132
|
+
? payload.sessionKey
|
|
133
|
+
: readySessionKey,
|
|
134
|
+
reply: typeof payload.reply === 'string' ? payload.reply : ''
|
|
135
|
+
};
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (event.name === 'error') {
|
|
139
|
+
const payload = (event.payload ?? {}) as { message?: string };
|
|
140
|
+
throw new Error(payload.message?.trim() || 'chat stream failed');
|
|
104
141
|
}
|
|
105
142
|
}
|
|
106
143
|
});
|
|
107
|
-
const result = await session.finished;
|
|
144
|
+
const result = await session.finished as { reply?: string; sessionKey?: string } | undefined;
|
|
145
|
+
if (finalResult !== null) {
|
|
146
|
+
return finalResult;
|
|
147
|
+
}
|
|
108
148
|
return {
|
|
109
149
|
sessionKey: typeof result?.sessionKey === 'string' && result.sessionKey.trim()
|
|
110
150
|
? result.sessionKey
|
|
@@ -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() {
|
|
@@ -95,10 +95,7 @@ export function ModelConfig() {
|
|
|
95
95
|
|
|
96
96
|
const handleSubmit = (e: React.FormEvent) => {
|
|
97
97
|
e.preventDefault();
|
|
98
|
-
updateModel.mutate({
|
|
99
|
-
model: composedModel,
|
|
100
|
-
workspace
|
|
101
|
-
});
|
|
98
|
+
updateModel.mutate({ model: composedModel });
|
|
102
99
|
};
|
|
103
100
|
|
|
104
101
|
if (isLoading) {
|
|
@@ -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
|
+
};
|