@nextclaw/ui 0.3.11 → 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 +9 -0
- package/dist/assets/index-D3arfjLX.css +1 -0
- package/dist/assets/index-DTd23uLj.js +240 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- 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 +93 -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/stores/ui.store.ts +1 -1
- package/dist/assets/index-CANDXRNv.js +0 -230
- package/dist/assets/index-DM9Q3WUX.css +0 -1
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>
|
package/package.json
CHANGED
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') },
|
|
@@ -170,11 +203,12 @@ export function ChannelForm() {
|
|
|
170
203
|
const executeAction = useExecuteConfigAction();
|
|
171
204
|
|
|
172
205
|
const [formData, setFormData] = useState<Record<string, unknown>>({});
|
|
206
|
+
const [jsonDrafts, setJsonDrafts] = useState<Record<string, string>>({});
|
|
173
207
|
const [runningActionId, setRunningActionId] = useState<string | null>(null);
|
|
174
208
|
|
|
175
209
|
const channelName = channelModal.channel;
|
|
176
210
|
const channelConfig = channelName ? config?.channels[channelName] : null;
|
|
177
|
-
const fields = channelName ? CHANNEL_FIELDS[channelName] : [];
|
|
211
|
+
const fields = useMemo(() => (channelName ? CHANNEL_FIELDS[channelName] : []), [channelName]);
|
|
178
212
|
const uiHints = schema?.uiHints;
|
|
179
213
|
const scope = channelName ? `channels.${channelName}` : null;
|
|
180
214
|
const actions = schema?.actions?.filter((action) => action.scope === scope) ?? [];
|
|
@@ -185,10 +219,19 @@ export function ChannelForm() {
|
|
|
185
219
|
useEffect(() => {
|
|
186
220
|
if (channelConfig) {
|
|
187
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);
|
|
188
230
|
} else {
|
|
189
231
|
setFormData({});
|
|
232
|
+
setJsonDrafts({});
|
|
190
233
|
}
|
|
191
|
-
}, [channelConfig, channelName]);
|
|
234
|
+
}, [channelConfig, channelName, fields]);
|
|
192
235
|
|
|
193
236
|
const updateField = (name: string, value: unknown) => {
|
|
194
237
|
setFormData((prev) => ({ ...prev, [name]: value }));
|
|
@@ -199,8 +242,22 @@ export function ChannelForm() {
|
|
|
199
242
|
|
|
200
243
|
if (!channelName) return;
|
|
201
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
|
+
|
|
202
259
|
updateChannel.mutate(
|
|
203
|
-
{ channel: channelName, data:
|
|
260
|
+
{ channel: channelName, data: payload },
|
|
204
261
|
{ onSuccess: () => closeChannelModal() }
|
|
205
262
|
);
|
|
206
263
|
};
|
|
@@ -351,6 +408,35 @@ export function ChannelForm() {
|
|
|
351
408
|
onChange={(tags) => updateField(field.name, tags)}
|
|
352
409
|
/>
|
|
353
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
|
+
)}
|
|
354
440
|
</div>
|
|
355
441
|
);
|
|
356
442
|
})}
|