@nextclaw/ui 0.3.10 → 0.3.12
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 +18 -0
- package/dist/assets/index-D3arfjLX.css +1 -0
- package/dist/assets/index-DTd23uLj.js +240 -0
- package/dist/index.html +2 -2
- package/dist/logos/wecom.svg +11 -0
- package/package.json +1 -1
- package/public/logos/wecom.svg +11 -0
- package/src/App.tsx +3 -0
- package/src/api/config.ts +15 -0
- package/src/api/types.ts +41 -0
- package/src/components/config/ChannelForm.tsx +103 -7
- package/src/components/config/RuntimeConfig.tsx +472 -0
- package/src/components/layout/Sidebar.tsx +6 -1
- package/src/hooks/useConfig.ts +17 -0
- package/src/lib/i18n.ts +4 -0
- package/src/lib/logos.ts +1 -0
- package/src/stores/ui.store.ts +1 -1
- package/dist/assets/index-DM9Q3WUX.css +0 -1
- package/dist/assets/index-DO3sh5Tk.js +0 -230
package/dist/index.html
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
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-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-DTd23uLj.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/index-D3arfjLX.css">
|
|
11
11
|
</head>
|
|
12
12
|
|
|
13
13
|
<body>
|
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
<circle cx="50" cy="55" r="26" fill="white" />
|
|
4
|
+
<circle cx="82" cy="73" r="24" fill="#CFF6E2" />
|
|
5
|
+
<circle cx="40" cy="52" r="3.5" fill="#07C160" />
|
|
6
|
+
<circle cx="51" cy="52" r="3.5" fill="#07C160" />
|
|
7
|
+
<circle cx="62" cy="52" r="3.5" fill="#07C160" />
|
|
8
|
+
<circle cx="72" cy="70" r="3.5" fill="#07C160" />
|
|
9
|
+
<circle cx="83" cy="70" r="3.5" fill="#07C160" />
|
|
10
|
+
<circle cx="94" cy="70" r="3.5" fill="#07C160" />
|
|
11
|
+
</svg>
|
package/package.json
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
<circle cx="50" cy="55" r="26" fill="white" />
|
|
4
|
+
<circle cx="82" cy="73" r="24" fill="#CFF6E2" />
|
|
5
|
+
<circle cx="40" cy="52" r="3.5" fill="#07C160" />
|
|
6
|
+
<circle cx="51" cy="52" r="3.5" fill="#07C160" />
|
|
7
|
+
<circle cx="62" cy="52" r="3.5" fill="#07C160" />
|
|
8
|
+
<circle cx="72" cy="70" r="3.5" fill="#07C160" />
|
|
9
|
+
<circle cx="83" cy="70" r="3.5" fill="#07C160" />
|
|
10
|
+
<circle cx="94" cy="70" r="3.5" fill="#07C160" />
|
|
11
|
+
</svg>
|
package/src/App.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { AppLayout } from '@/components/layout/AppLayout';
|
|
|
4
4
|
import { ModelConfig } from '@/components/config/ModelConfig';
|
|
5
5
|
import { ProvidersList } from '@/components/config/ProvidersList';
|
|
6
6
|
import { ChannelsList } from '@/components/config/ChannelsList';
|
|
7
|
+
import { RuntimeConfig } from '@/components/config/RuntimeConfig';
|
|
7
8
|
import { useWebSocket } from '@/hooks/useWebSocket';
|
|
8
9
|
import { Toaster } from 'sonner';
|
|
9
10
|
|
|
@@ -28,6 +29,8 @@ function AppContent() {
|
|
|
28
29
|
return <ProvidersList />;
|
|
29
30
|
case 'channels':
|
|
30
31
|
return <ChannelsList />;
|
|
32
|
+
case 'runtime':
|
|
33
|
+
return <RuntimeConfig />;
|
|
31
34
|
default:
|
|
32
35
|
return <ModelConfig />;
|
|
33
36
|
}
|
package/src/api/config.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
ProviderConfigView,
|
|
7
7
|
ChannelConfigUpdate,
|
|
8
8
|
ProviderConfigUpdate,
|
|
9
|
+
RuntimeConfigUpdate,
|
|
9
10
|
ConfigActionExecuteRequest,
|
|
10
11
|
ConfigActionExecuteResult
|
|
11
12
|
} from './types';
|
|
@@ -78,6 +79,20 @@ export async function updateChannel(
|
|
|
78
79
|
return response.data;
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
// PUT /api/config/runtime
|
|
83
|
+
export async function updateRuntime(
|
|
84
|
+
data: RuntimeConfigUpdate
|
|
85
|
+
): Promise<Pick<ConfigView, 'agents' | 'bindings' | 'session'>> {
|
|
86
|
+
const response = await api.put<Pick<ConfigView, 'agents' | 'bindings' | 'session'>>(
|
|
87
|
+
'/api/config/runtime',
|
|
88
|
+
data
|
|
89
|
+
);
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
throw new Error(response.error.message);
|
|
92
|
+
}
|
|
93
|
+
return response.data;
|
|
94
|
+
}
|
|
95
|
+
|
|
81
96
|
// POST /api/config/actions/:id/execute
|
|
82
97
|
export async function executeConfigAction(
|
|
83
98
|
actionId: string,
|
package/src/api/types.ts
CHANGED
|
@@ -24,6 +24,44 @@ export type ProviderConfigUpdate = {
|
|
|
24
24
|
wireApi?: "auto" | "chat" | "responses" | null;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
export type AgentProfileView = {
|
|
28
|
+
id: string;
|
|
29
|
+
default?: boolean;
|
|
30
|
+
workspace?: string;
|
|
31
|
+
model?: string;
|
|
32
|
+
maxTokens?: number;
|
|
33
|
+
maxToolIterations?: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type BindingPeerView = {
|
|
37
|
+
kind: "direct" | "group" | "channel";
|
|
38
|
+
id: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type AgentBindingView = {
|
|
42
|
+
agentId: string;
|
|
43
|
+
match: {
|
|
44
|
+
channel: string;
|
|
45
|
+
accountId?: string;
|
|
46
|
+
peer?: BindingPeerView;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type SessionConfigView = {
|
|
51
|
+
dmScope?: "main" | "per-peer" | "per-channel-peer" | "per-account-channel-peer";
|
|
52
|
+
agentToAgent?: {
|
|
53
|
+
maxPingPongTurns?: number;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type RuntimeConfigUpdate = {
|
|
58
|
+
agents?: {
|
|
59
|
+
list?: AgentProfileView[];
|
|
60
|
+
};
|
|
61
|
+
bindings?: AgentBindingView[];
|
|
62
|
+
session?: SessionConfigView;
|
|
63
|
+
};
|
|
64
|
+
|
|
27
65
|
export type ChannelConfigUpdate = Record<string, unknown>;
|
|
28
66
|
|
|
29
67
|
export type ConfigView = {
|
|
@@ -34,6 +72,7 @@ export type ConfigView = {
|
|
|
34
72
|
maxTokens?: number;
|
|
35
73
|
maxToolIterations?: number;
|
|
36
74
|
};
|
|
75
|
+
list?: AgentProfileView[];
|
|
37
76
|
context?: {
|
|
38
77
|
bootstrap?: {
|
|
39
78
|
files?: string[];
|
|
@@ -50,6 +89,8 @@ export type ConfigView = {
|
|
|
50
89
|
};
|
|
51
90
|
providers: Record<string, ProviderConfigView>;
|
|
52
91
|
channels: Record<string, Record<string, unknown>>;
|
|
92
|
+
bindings?: AgentBindingView[];
|
|
93
|
+
session?: SessionConfigView;
|
|
53
94
|
tools?: Record<string, unknown>;
|
|
54
95
|
gateway?: Record<string, unknown>;
|
|
55
96
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { useConfig, useConfigSchema, useUpdateChannel, useExecuteConfigAction } from '@/hooks/useConfig';
|
|
3
3
|
import { useUiStore } from '@/stores/ui.store';
|
|
4
4
|
import {
|
|
@@ -20,6 +20,23 @@ import { toast } from 'sonner';
|
|
|
20
20
|
import { MessageCircle, Settings, ToggleLeft, Hash, Mail, Globe, KeyRound } from 'lucide-react';
|
|
21
21
|
import type { ConfigActionManifest } from '@/api/types';
|
|
22
22
|
|
|
23
|
+
type ChannelFieldType = 'boolean' | 'text' | 'email' | 'password' | 'number' | 'tags' | 'select' | 'json';
|
|
24
|
+
type ChannelOption = { value: string; label: string };
|
|
25
|
+
type ChannelField = { name: string; type: ChannelFieldType; label: string; options?: ChannelOption[] };
|
|
26
|
+
|
|
27
|
+
const DM_POLICY_OPTIONS: ChannelOption[] = [
|
|
28
|
+
{ value: 'pairing', label: 'pairing' },
|
|
29
|
+
{ value: 'allowlist', label: 'allowlist' },
|
|
30
|
+
{ value: 'open', label: 'open' },
|
|
31
|
+
{ value: 'disabled', label: 'disabled' }
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const GROUP_POLICY_OPTIONS: ChannelOption[] = [
|
|
35
|
+
{ value: 'open', label: 'open' },
|
|
36
|
+
{ value: 'allowlist', label: 'allowlist' },
|
|
37
|
+
{ value: 'disabled', label: 'disabled' }
|
|
38
|
+
];
|
|
39
|
+
|
|
23
40
|
// Field icon mapping
|
|
24
41
|
const getFieldIcon = (fieldName: string) => {
|
|
25
42
|
if (fieldName.includes('token') || fieldName.includes('secret') || fieldName.includes('password')) {
|
|
@@ -41,12 +58,19 @@ const getFieldIcon = (fieldName: string) => {
|
|
|
41
58
|
};
|
|
42
59
|
|
|
43
60
|
// Channel field definitions
|
|
44
|
-
const CHANNEL_FIELDS: Record<string,
|
|
61
|
+
const CHANNEL_FIELDS: Record<string, ChannelField[]> = {
|
|
45
62
|
telegram: [
|
|
46
63
|
{ name: 'enabled', type: 'boolean', label: t('enabled') },
|
|
47
64
|
{ name: 'token', type: 'password', label: t('botToken') },
|
|
48
65
|
{ name: 'allowFrom', type: 'tags', label: t('allowFrom') },
|
|
49
|
-
{ name: 'proxy', type: 'text', label: t('proxy') }
|
|
66
|
+
{ name: 'proxy', type: 'text', label: t('proxy') },
|
|
67
|
+
{ name: 'accountId', type: 'text', label: 'Account ID' },
|
|
68
|
+
{ name: 'dmPolicy', type: 'select', label: 'DM Policy', options: DM_POLICY_OPTIONS },
|
|
69
|
+
{ name: 'groupPolicy', type: 'select', label: 'Group Policy', options: GROUP_POLICY_OPTIONS },
|
|
70
|
+
{ name: 'groupAllowFrom', type: 'tags', label: 'Group Allow From' },
|
|
71
|
+
{ name: 'requireMention', type: 'boolean', label: 'Require Mention' },
|
|
72
|
+
{ name: 'mentionPatterns', type: 'tags', label: 'Mention Patterns' },
|
|
73
|
+
{ name: 'groups', type: 'json', label: 'Group Rules (JSON)' }
|
|
50
74
|
],
|
|
51
75
|
discord: [
|
|
52
76
|
{ name: 'enabled', type: 'boolean', label: t('enabled') },
|
|
@@ -54,7 +78,16 @@ const CHANNEL_FIELDS: Record<string, Array<{ name: string; type: string; label:
|
|
|
54
78
|
{ name: 'allowBots', type: 'boolean', label: 'Allow Bot Messages' },
|
|
55
79
|
{ name: 'allowFrom', type: 'tags', label: t('allowFrom') },
|
|
56
80
|
{ name: 'gatewayUrl', type: 'text', label: t('gatewayUrl') },
|
|
57
|
-
{ name: 'intents', type: 'number', label: t('intents') }
|
|
81
|
+
{ name: 'intents', type: 'number', label: t('intents') },
|
|
82
|
+
{ name: 'proxy', type: 'text', label: t('proxy') },
|
|
83
|
+
{ name: 'mediaMaxMb', type: 'number', label: 'Attachment Max Size (MB)' },
|
|
84
|
+
{ name: 'accountId', type: 'text', label: 'Account ID' },
|
|
85
|
+
{ name: 'dmPolicy', type: 'select', label: 'DM Policy', options: DM_POLICY_OPTIONS },
|
|
86
|
+
{ name: 'groupPolicy', type: 'select', label: 'Group Policy', options: GROUP_POLICY_OPTIONS },
|
|
87
|
+
{ name: 'groupAllowFrom', type: 'tags', label: 'Group Allow From' },
|
|
88
|
+
{ name: 'requireMention', type: 'boolean', label: 'Require Mention' },
|
|
89
|
+
{ name: 'mentionPatterns', type: 'tags', label: 'Mention Patterns' },
|
|
90
|
+
{ name: 'groups', type: 'json', label: 'Group Rules (JSON)' }
|
|
58
91
|
],
|
|
59
92
|
whatsapp: [
|
|
60
93
|
{ name: 'enabled', type: 'boolean', label: t('enabled') },
|
|
@@ -75,6 +108,16 @@ const CHANNEL_FIELDS: Record<string, Array<{ name: string; type: string; label:
|
|
|
75
108
|
{ name: 'clientSecret', type: 'password', label: t('clientSecret') },
|
|
76
109
|
{ name: 'allowFrom', type: 'tags', label: t('allowFrom') }
|
|
77
110
|
],
|
|
111
|
+
wecom: [
|
|
112
|
+
{ name: 'enabled', type: 'boolean', label: t('enabled') },
|
|
113
|
+
{ name: 'corpId', type: 'text', label: t('corpId') },
|
|
114
|
+
{ name: 'agentId', type: 'text', label: t('agentId') },
|
|
115
|
+
{ name: 'secret', type: 'password', label: t('secret') },
|
|
116
|
+
{ name: 'token', type: 'password', label: t('token') },
|
|
117
|
+
{ name: 'callbackPort', type: 'number', label: t('callbackPort') },
|
|
118
|
+
{ name: 'callbackPath', type: 'text', label: t('callbackPath') },
|
|
119
|
+
{ name: 'allowFrom', type: 'tags', label: t('allowFrom') }
|
|
120
|
+
],
|
|
78
121
|
slack: [
|
|
79
122
|
{ name: 'enabled', type: 'boolean', label: t('enabled') },
|
|
80
123
|
{ name: 'mode', type: 'text', label: t('mode') },
|
|
@@ -160,11 +203,12 @@ export function ChannelForm() {
|
|
|
160
203
|
const executeAction = useExecuteConfigAction();
|
|
161
204
|
|
|
162
205
|
const [formData, setFormData] = useState<Record<string, unknown>>({});
|
|
206
|
+
const [jsonDrafts, setJsonDrafts] = useState<Record<string, string>>({});
|
|
163
207
|
const [runningActionId, setRunningActionId] = useState<string | null>(null);
|
|
164
208
|
|
|
165
209
|
const channelName = channelModal.channel;
|
|
166
210
|
const channelConfig = channelName ? config?.channels[channelName] : null;
|
|
167
|
-
const fields = channelName ? CHANNEL_FIELDS[channelName] : [];
|
|
211
|
+
const fields = useMemo(() => (channelName ? CHANNEL_FIELDS[channelName] : []), [channelName]);
|
|
168
212
|
const uiHints = schema?.uiHints;
|
|
169
213
|
const scope = channelName ? `channels.${channelName}` : null;
|
|
170
214
|
const actions = schema?.actions?.filter((action) => action.scope === scope) ?? [];
|
|
@@ -175,10 +219,19 @@ export function ChannelForm() {
|
|
|
175
219
|
useEffect(() => {
|
|
176
220
|
if (channelConfig) {
|
|
177
221
|
setFormData({ ...channelConfig });
|
|
222
|
+
const nextDrafts: Record<string, string> = {};
|
|
223
|
+
fields
|
|
224
|
+
.filter((field) => field.type === 'json')
|
|
225
|
+
.forEach((field) => {
|
|
226
|
+
const value = channelConfig[field.name];
|
|
227
|
+
nextDrafts[field.name] = JSON.stringify(value ?? {}, null, 2);
|
|
228
|
+
});
|
|
229
|
+
setJsonDrafts(nextDrafts);
|
|
178
230
|
} else {
|
|
179
231
|
setFormData({});
|
|
232
|
+
setJsonDrafts({});
|
|
180
233
|
}
|
|
181
|
-
}, [channelConfig, channelName]);
|
|
234
|
+
}, [channelConfig, channelName, fields]);
|
|
182
235
|
|
|
183
236
|
const updateField = (name: string, value: unknown) => {
|
|
184
237
|
setFormData((prev) => ({ ...prev, [name]: value }));
|
|
@@ -189,8 +242,22 @@ export function ChannelForm() {
|
|
|
189
242
|
|
|
190
243
|
if (!channelName) return;
|
|
191
244
|
|
|
245
|
+
const payload: Record<string, unknown> = { ...formData };
|
|
246
|
+
for (const field of fields) {
|
|
247
|
+
if (field.type !== 'json') {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const raw = jsonDrafts[field.name] ?? '';
|
|
251
|
+
try {
|
|
252
|
+
payload[field.name] = raw.trim() ? JSON.parse(raw) : {};
|
|
253
|
+
} catch {
|
|
254
|
+
toast.error(`Invalid JSON for ${field.name}`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
192
259
|
updateChannel.mutate(
|
|
193
|
-
{ channel: channelName, data:
|
|
260
|
+
{ channel: channelName, data: payload },
|
|
194
261
|
{ onSuccess: () => closeChannelModal() }
|
|
195
262
|
);
|
|
196
263
|
};
|
|
@@ -341,6 +408,35 @@ export function ChannelForm() {
|
|
|
341
408
|
onChange={(tags) => updateField(field.name, tags)}
|
|
342
409
|
/>
|
|
343
410
|
)}
|
|
411
|
+
|
|
412
|
+
{field.type === 'select' && (
|
|
413
|
+
<select
|
|
414
|
+
id={field.name}
|
|
415
|
+
value={(formData[field.name] as string) || ''}
|
|
416
|
+
onChange={(e) => updateField(field.name, e.target.value)}
|
|
417
|
+
className="flex h-10 w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm"
|
|
418
|
+
>
|
|
419
|
+
{(field.options ?? []).map((option) => (
|
|
420
|
+
<option key={option.value} value={option.value}>
|
|
421
|
+
{option.label}
|
|
422
|
+
</option>
|
|
423
|
+
))}
|
|
424
|
+
</select>
|
|
425
|
+
)}
|
|
426
|
+
|
|
427
|
+
{field.type === 'json' && (
|
|
428
|
+
<textarea
|
|
429
|
+
id={field.name}
|
|
430
|
+
value={jsonDrafts[field.name] ?? '{}'}
|
|
431
|
+
onChange={(event) =>
|
|
432
|
+
setJsonDrafts((prev) => ({
|
|
433
|
+
...prev,
|
|
434
|
+
[field.name]: event.target.value
|
|
435
|
+
}))
|
|
436
|
+
}
|
|
437
|
+
className="min-h-[120px] w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-xs font-mono"
|
|
438
|
+
/>
|
|
439
|
+
)}
|
|
344
440
|
</div>
|
|
345
441
|
);
|
|
346
442
|
})}
|