@nextclaw/ui 0.3.8 → 0.3.10
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 +20 -0
- package/dist/assets/index-DM9Q3WUX.css +1 -0
- package/dist/assets/index-DO3sh5Tk.js +230 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/api/config.ts +11 -4
- package/src/api/types.ts +50 -5
- package/src/components/config/ChannelForm.tsx +171 -97
- package/src/components/config/ModelConfig.tsx +2 -27
- package/src/components/config/ProviderForm.tsx +74 -72
- package/src/hooks/useConfig.ts +12 -1
- package/src/lib/i18n.ts +0 -1
- package/dist/assets/index-DI6zQUcL.js +0 -230
- package/dist/assets/index-DahcMyga.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-DO3sh5Tk.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DM9Q3WUX.css">
|
|
11
11
|
</head>
|
|
12
12
|
|
|
13
13
|
<body>
|
package/package.json
CHANGED
package/src/api/config.ts
CHANGED
|
@@ -6,7 +6,8 @@ import type {
|
|
|
6
6
|
ProviderConfigView,
|
|
7
7
|
ChannelConfigUpdate,
|
|
8
8
|
ProviderConfigUpdate,
|
|
9
|
-
|
|
9
|
+
ConfigActionExecuteRequest,
|
|
10
|
+
ConfigActionExecuteResult
|
|
10
11
|
} from './types';
|
|
11
12
|
|
|
12
13
|
// GET /api/config
|
|
@@ -77,9 +78,15 @@ export async function updateChannel(
|
|
|
77
78
|
return response.data;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
// POST /api/
|
|
81
|
-
export async function
|
|
82
|
-
|
|
81
|
+
// POST /api/config/actions/:id/execute
|
|
82
|
+
export async function executeConfigAction(
|
|
83
|
+
actionId: string,
|
|
84
|
+
data: ConfigActionExecuteRequest
|
|
85
|
+
): Promise<ConfigActionExecuteResult> {
|
|
86
|
+
const response = await api.post<ConfigActionExecuteResult>(
|
|
87
|
+
`/api/config/actions/${actionId}/execute`,
|
|
88
|
+
data
|
|
89
|
+
);
|
|
83
90
|
if (!response.ok) {
|
|
84
91
|
throw new Error(response.error.message);
|
|
85
92
|
}
|
package/src/api/types.ts
CHANGED
|
@@ -32,7 +32,6 @@ export type ConfigView = {
|
|
|
32
32
|
model: string;
|
|
33
33
|
workspace?: string;
|
|
34
34
|
maxTokens?: number;
|
|
35
|
-
temperature?: number;
|
|
36
35
|
maxToolIterations?: number;
|
|
37
36
|
};
|
|
38
37
|
context?: {
|
|
@@ -96,14 +95,60 @@ export type ConfigUiHints = Record<string, ConfigUiHint>;
|
|
|
96
95
|
export type ConfigSchemaResponse = {
|
|
97
96
|
schema: Record<string, unknown>;
|
|
98
97
|
uiHints: ConfigUiHints;
|
|
98
|
+
actions: ConfigActionManifest[];
|
|
99
99
|
version: string;
|
|
100
100
|
generatedAt: string;
|
|
101
101
|
};
|
|
102
102
|
|
|
103
|
-
export type
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
export type ConfigActionType = 'httpProbe' | 'oauthStart' | 'webhookVerify' | 'openUrl' | 'copyToken';
|
|
104
|
+
|
|
105
|
+
export type ConfigActionManifest = {
|
|
106
|
+
id: string;
|
|
107
|
+
version: string;
|
|
108
|
+
scope: string;
|
|
109
|
+
title: string;
|
|
110
|
+
description?: string;
|
|
111
|
+
type: ConfigActionType;
|
|
112
|
+
trigger: 'manual' | 'afterSave';
|
|
113
|
+
requires?: string[];
|
|
114
|
+
request: {
|
|
115
|
+
method: 'GET' | 'POST' | 'PUT';
|
|
116
|
+
path: string;
|
|
117
|
+
timeoutMs?: number;
|
|
118
|
+
};
|
|
119
|
+
success?: {
|
|
120
|
+
message?: string;
|
|
121
|
+
};
|
|
122
|
+
failure?: {
|
|
123
|
+
message?: string;
|
|
124
|
+
};
|
|
125
|
+
saveBeforeRun?: boolean;
|
|
126
|
+
savePatch?: Record<string, unknown>;
|
|
127
|
+
resultMap?: Record<string, string>;
|
|
128
|
+
policy?: {
|
|
129
|
+
roles?: string[];
|
|
130
|
+
rateLimitKey?: string;
|
|
131
|
+
cooldownMs?: number;
|
|
132
|
+
audit?: boolean;
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export type ConfigActionExecuteRequest = {
|
|
137
|
+
scope?: string;
|
|
138
|
+
draftConfig?: Record<string, unknown>;
|
|
139
|
+
context?: {
|
|
140
|
+
actor?: string;
|
|
141
|
+
traceId?: string;
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export type ConfigActionExecuteResult = {
|
|
146
|
+
ok: boolean;
|
|
147
|
+
status: 'success' | 'failed';
|
|
148
|
+
message: string;
|
|
149
|
+
data?: Record<string, unknown>;
|
|
150
|
+
patch?: Record<string, unknown>;
|
|
151
|
+
nextActions?: string[];
|
|
107
152
|
};
|
|
108
153
|
|
|
109
154
|
// WebSocket events
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
|
-
import { useConfig, useConfigSchema, useUpdateChannel } from '@/hooks/useConfig';
|
|
3
|
-
import { probeFeishu } from '@/api/config';
|
|
2
|
+
import { useConfig, useConfigSchema, useUpdateChannel, useExecuteConfigAction } from '@/hooks/useConfig';
|
|
4
3
|
import { useUiStore } from '@/stores/ui.store';
|
|
5
4
|
import {
|
|
6
5
|
Dialog,
|
|
@@ -19,6 +18,7 @@ import { t } from '@/lib/i18n';
|
|
|
19
18
|
import { hintForPath } from '@/lib/config-hints';
|
|
20
19
|
import { toast } from 'sonner';
|
|
21
20
|
import { MessageCircle, Settings, ToggleLeft, Hash, Mail, Globe, KeyRound } from 'lucide-react';
|
|
21
|
+
import type { ConfigActionManifest } from '@/api/types';
|
|
22
22
|
|
|
23
23
|
// Field icon mapping
|
|
24
24
|
const getFieldIcon = (fieldName: string) => {
|
|
@@ -51,6 +51,7 @@ const CHANNEL_FIELDS: Record<string, Array<{ name: string; type: string; label:
|
|
|
51
51
|
discord: [
|
|
52
52
|
{ name: 'enabled', type: 'boolean', label: t('enabled') },
|
|
53
53
|
{ name: 'token', type: 'password', label: t('botToken') },
|
|
54
|
+
{ name: 'allowBots', type: 'boolean', label: 'Allow Bot Messages' },
|
|
54
55
|
{ name: 'allowFrom', type: 'tags', label: t('allowFrom') },
|
|
55
56
|
{ name: 'gatewayUrl', type: 'text', label: t('gatewayUrl') },
|
|
56
57
|
{ name: 'intents', type: 'number', label: t('intents') }
|
|
@@ -78,6 +79,7 @@ const CHANNEL_FIELDS: Record<string, Array<{ name: string; type: string; label:
|
|
|
78
79
|
{ name: 'enabled', type: 'boolean', label: t('enabled') },
|
|
79
80
|
{ name: 'mode', type: 'text', label: t('mode') },
|
|
80
81
|
{ name: 'webhookPath', type: 'text', label: t('webhookPath') },
|
|
82
|
+
{ name: 'allowBots', type: 'boolean', label: 'Allow Bot Messages' },
|
|
81
83
|
{ name: 'botToken', type: 'password', label: t('botToken') },
|
|
82
84
|
{ name: 'appToken', type: 'password', label: t('appToken') }
|
|
83
85
|
],
|
|
@@ -120,19 +122,52 @@ const channelColors: Record<string, string> = {
|
|
|
120
122
|
default: 'from-slate-400 to-gray-500'
|
|
121
123
|
};
|
|
122
124
|
|
|
125
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
126
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function deepMergeRecords(base: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown> {
|
|
130
|
+
const next: Record<string, unknown> = { ...base };
|
|
131
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
132
|
+
const prev = next[key];
|
|
133
|
+
if (isRecord(prev) && isRecord(value)) {
|
|
134
|
+
next[key] = deepMergeRecords(prev, value);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
next[key] = value;
|
|
138
|
+
}
|
|
139
|
+
return next;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function buildScopeDraft(scope: string, value: Record<string, unknown>): Record<string, unknown> {
|
|
143
|
+
const segments = scope.split('.');
|
|
144
|
+
const output: Record<string, unknown> = {};
|
|
145
|
+
let cursor: Record<string, unknown> = output;
|
|
146
|
+
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
147
|
+
const segment = segments[index];
|
|
148
|
+
cursor[segment] = {};
|
|
149
|
+
cursor = cursor[segment] as Record<string, unknown>;
|
|
150
|
+
}
|
|
151
|
+
cursor[segments[segments.length - 1]] = value;
|
|
152
|
+
return output;
|
|
153
|
+
}
|
|
154
|
+
|
|
123
155
|
export function ChannelForm() {
|
|
124
156
|
const { channelModal, closeChannelModal } = useUiStore();
|
|
125
157
|
const { data: config } = useConfig();
|
|
126
158
|
const { data: schema } = useConfigSchema();
|
|
127
159
|
const updateChannel = useUpdateChannel();
|
|
160
|
+
const executeAction = useExecuteConfigAction();
|
|
128
161
|
|
|
129
162
|
const [formData, setFormData] = useState<Record<string, unknown>>({});
|
|
130
|
-
const [
|
|
163
|
+
const [runningActionId, setRunningActionId] = useState<string | null>(null);
|
|
131
164
|
|
|
132
165
|
const channelName = channelModal.channel;
|
|
133
166
|
const channelConfig = channelName ? config?.channels[channelName] : null;
|
|
134
167
|
const fields = channelName ? CHANNEL_FIELDS[channelName] : [];
|
|
135
168
|
const uiHints = schema?.uiHints;
|
|
169
|
+
const scope = channelName ? `channels.${channelName}` : null;
|
|
170
|
+
const actions = schema?.actions?.filter((action) => action.scope === scope) ?? [];
|
|
136
171
|
const channelLabel = channelName
|
|
137
172
|
? hintForPath(`channels.${channelName}`, uiHints)?.label ?? channelName
|
|
138
173
|
: channelName;
|
|
@@ -160,23 +195,59 @@ export function ChannelForm() {
|
|
|
160
195
|
);
|
|
161
196
|
};
|
|
162
197
|
|
|
163
|
-
const
|
|
164
|
-
if (!
|
|
165
|
-
|
|
198
|
+
const applyActionPatchToForm = (patch?: Record<string, unknown>) => {
|
|
199
|
+
if (!patch || !channelName) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const channelsNode = patch.channels;
|
|
203
|
+
if (!isRecord(channelsNode)) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const channelPatch = channelsNode[channelName];
|
|
207
|
+
if (!isRecord(channelPatch)) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
setFormData((prev) => deepMergeRecords(prev, channelPatch));
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const handleManualAction = async (action: ConfigActionManifest) => {
|
|
214
|
+
if (!channelName || !scope) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
setRunningActionId(action.id);
|
|
166
219
|
try {
|
|
167
|
-
|
|
168
|
-
|
|
220
|
+
let nextData = { ...formData };
|
|
221
|
+
|
|
222
|
+
if (action.saveBeforeRun) {
|
|
223
|
+
nextData = {
|
|
224
|
+
...nextData,
|
|
225
|
+
...(action.savePatch ?? {})
|
|
226
|
+
};
|
|
169
227
|
setFormData(nextData);
|
|
228
|
+
await updateChannel.mutateAsync({ channel: channelName, data: nextData });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const result = await executeAction.mutateAsync({
|
|
232
|
+
actionId: action.id,
|
|
233
|
+
data: {
|
|
234
|
+
scope,
|
|
235
|
+
draftConfig: buildScopeDraft(scope, nextData)
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
applyActionPatchToForm(result.patch);
|
|
240
|
+
|
|
241
|
+
if (result.ok) {
|
|
242
|
+
toast.success(result.message || t('success'));
|
|
243
|
+
} else {
|
|
244
|
+
toast.error(result.message || t('error'));
|
|
170
245
|
}
|
|
171
|
-
await updateChannel.mutateAsync({ channel: channelName, data: nextData });
|
|
172
|
-
const probe = await probeFeishu();
|
|
173
|
-
const botLabel = probe.botName ? ` (${probe.botName})` : '';
|
|
174
|
-
toast.success(t('feishuVerifySuccess') + botLabel);
|
|
175
246
|
} catch (error) {
|
|
176
247
|
const message = error instanceof Error ? error.message : String(error);
|
|
177
|
-
toast.error(`${t('
|
|
248
|
+
toast.error(`${t('error')}: ${message}`);
|
|
178
249
|
} finally {
|
|
179
|
-
|
|
250
|
+
setRunningActionId(null);
|
|
180
251
|
}
|
|
181
252
|
};
|
|
182
253
|
|
|
@@ -198,8 +269,8 @@ export function ChannelForm() {
|
|
|
198
269
|
</div>
|
|
199
270
|
</DialogHeader>
|
|
200
271
|
|
|
201
|
-
<
|
|
202
|
-
<
|
|
272
|
+
<form onSubmit={handleSubmit} className="flex flex-col flex-1 overflow-hidden">
|
|
273
|
+
<div className="flex-1 overflow-y-auto custom-scrollbar py-2 pr-2 space-y-5">
|
|
203
274
|
{fields.map((field) => {
|
|
204
275
|
const hint = channelName
|
|
205
276
|
? hintForPath(`channels.${channelName}.${field.name}`, uiHints)
|
|
@@ -209,98 +280,101 @@ export function ChannelForm() {
|
|
|
209
280
|
|
|
210
281
|
return (
|
|
211
282
|
<div key={field.name} className="space-y-2.5">
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
283
|
+
<Label
|
|
284
|
+
htmlFor={field.name}
|
|
285
|
+
className="text-sm font-medium text-gray-900 flex items-center gap-2"
|
|
286
|
+
>
|
|
287
|
+
{getFieldIcon(field.name)}
|
|
288
|
+
{label}
|
|
289
|
+
</Label>
|
|
290
|
+
|
|
291
|
+
{field.type === 'boolean' && (
|
|
292
|
+
<div className="flex items-center justify-between p-3 rounded-xl bg-gray-50">
|
|
293
|
+
<span className="text-sm text-gray-500">
|
|
294
|
+
{(formData[field.name] as boolean) ? t('enabled') : t('disabled')}
|
|
295
|
+
</span>
|
|
296
|
+
<Switch
|
|
297
|
+
id={field.name}
|
|
298
|
+
checked={(formData[field.name] as boolean) || false}
|
|
299
|
+
onCheckedChange={(checked) => updateField(field.name, checked)}
|
|
300
|
+
className="data-[state=checked]:bg-emerald-500"
|
|
301
|
+
/>
|
|
302
|
+
</div>
|
|
303
|
+
)}
|
|
304
|
+
|
|
305
|
+
{(field.type === 'text' || field.type === 'email') && (
|
|
306
|
+
<Input
|
|
307
|
+
id={field.name}
|
|
308
|
+
type={field.type}
|
|
309
|
+
value={(formData[field.name] as string) || ''}
|
|
310
|
+
onChange={(e) => updateField(field.name, e.target.value)}
|
|
311
|
+
placeholder={placeholder}
|
|
312
|
+
className="rounded-xl"
|
|
313
|
+
/>
|
|
314
|
+
)}
|
|
315
|
+
|
|
316
|
+
{field.type === 'password' && (
|
|
317
|
+
<Input
|
|
318
|
+
id={field.name}
|
|
319
|
+
type="password"
|
|
320
|
+
value={(formData[field.name] as string) || ''}
|
|
321
|
+
onChange={(e) => updateField(field.name, e.target.value)}
|
|
322
|
+
placeholder={placeholder ?? 'Leave blank to keep unchanged'}
|
|
323
|
+
className="rounded-xl"
|
|
324
|
+
/>
|
|
325
|
+
)}
|
|
326
|
+
|
|
327
|
+
{field.type === 'number' && (
|
|
328
|
+
<Input
|
|
226
329
|
id={field.name}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
330
|
+
type="number"
|
|
331
|
+
value={(formData[field.name] as number) || 0}
|
|
332
|
+
onChange={(e) => updateField(field.name, parseInt(e.target.value) || 0)}
|
|
333
|
+
placeholder={placeholder}
|
|
334
|
+
className="rounded-xl"
|
|
335
|
+
/>
|
|
336
|
+
)}
|
|
337
|
+
|
|
338
|
+
{field.type === 'tags' && (
|
|
339
|
+
<TagInput
|
|
340
|
+
value={(formData[field.name] as string[]) || []}
|
|
341
|
+
onChange={(tags) => updateField(field.name, tags)}
|
|
230
342
|
/>
|
|
231
|
-
|
|
232
|
-
)}
|
|
233
|
-
|
|
234
|
-
{(field.type === 'text' || field.type === 'email') && (
|
|
235
|
-
<Input
|
|
236
|
-
id={field.name}
|
|
237
|
-
type={field.type}
|
|
238
|
-
value={(formData[field.name] as string) || ''}
|
|
239
|
-
onChange={(e) => updateField(field.name, e.target.value)}
|
|
240
|
-
placeholder={placeholder}
|
|
241
|
-
className="rounded-xl"
|
|
242
|
-
/>
|
|
243
|
-
)}
|
|
244
|
-
|
|
245
|
-
{field.type === 'password' && (
|
|
246
|
-
<Input
|
|
247
|
-
id={field.name}
|
|
248
|
-
type="password"
|
|
249
|
-
value={(formData[field.name] as string) || ''}
|
|
250
|
-
onChange={(e) => updateField(field.name, e.target.value)}
|
|
251
|
-
placeholder={placeholder ?? 'Leave blank to keep unchanged'}
|
|
252
|
-
className="rounded-xl"
|
|
253
|
-
/>
|
|
254
|
-
)}
|
|
255
|
-
|
|
256
|
-
{field.type === 'number' && (
|
|
257
|
-
<Input
|
|
258
|
-
id={field.name}
|
|
259
|
-
type="number"
|
|
260
|
-
value={(formData[field.name] as number) || 0}
|
|
261
|
-
onChange={(e) => updateField(field.name, parseInt(e.target.value) || 0)}
|
|
262
|
-
placeholder={placeholder}
|
|
263
|
-
className="rounded-xl"
|
|
264
|
-
/>
|
|
265
|
-
)}
|
|
266
|
-
|
|
267
|
-
{field.type === 'tags' && (
|
|
268
|
-
<TagInput
|
|
269
|
-
value={(formData[field.name] as string[]) || []}
|
|
270
|
-
onChange={(tags) => updateField(field.name, tags)}
|
|
271
|
-
/>
|
|
272
|
-
)}
|
|
343
|
+
)}
|
|
273
344
|
</div>
|
|
274
345
|
);
|
|
275
346
|
})}
|
|
347
|
+
</div>
|
|
276
348
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
349
|
+
<DialogFooter className="pt-4 flex-shrink-0">
|
|
350
|
+
<Button
|
|
351
|
+
type="button"
|
|
352
|
+
variant="outline"
|
|
353
|
+
onClick={closeChannelModal}
|
|
354
|
+
>
|
|
355
|
+
{t('cancel')}
|
|
356
|
+
</Button>
|
|
357
|
+
<Button
|
|
358
|
+
type="submit"
|
|
359
|
+
disabled={updateChannel.isPending || Boolean(runningActionId)}
|
|
360
|
+
>
|
|
361
|
+
{updateChannel.isPending ? 'Saving...' : t('save')}
|
|
362
|
+
</Button>
|
|
363
|
+
{actions
|
|
364
|
+
.filter((action) => action.trigger === 'manual')
|
|
365
|
+
.map((action) => (
|
|
292
366
|
<Button
|
|
367
|
+
key={action.id}
|
|
293
368
|
type="button"
|
|
294
|
-
onClick={
|
|
295
|
-
disabled={updateChannel.isPending ||
|
|
369
|
+
onClick={() => handleManualAction(action)}
|
|
370
|
+
disabled={updateChannel.isPending || Boolean(runningActionId)}
|
|
296
371
|
variant="secondary"
|
|
297
372
|
>
|
|
298
|
-
{
|
|
373
|
+
{runningActionId === action.id ? t('connecting') : action.title}
|
|
299
374
|
</Button>
|
|
300
|
-
)}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
</div>
|
|
375
|
+
))}
|
|
376
|
+
</DialogFooter>
|
|
377
|
+
</form>
|
|
304
378
|
</DialogContent>
|
|
305
379
|
</Dialog>
|
|
306
380
|
);
|
|
@@ -16,19 +16,16 @@ export function ModelConfig() {
|
|
|
16
16
|
const [model, setModel] = useState('');
|
|
17
17
|
const [workspace, setWorkspace] = useState('');
|
|
18
18
|
const [maxTokens, setMaxTokens] = useState(8192);
|
|
19
|
-
const [temperature, setTemperature] = useState(0.7);
|
|
20
19
|
const uiHints = schema?.uiHints;
|
|
21
20
|
const modelHint = hintForPath('agents.defaults.model', uiHints);
|
|
22
21
|
const workspaceHint = hintForPath('agents.defaults.workspace', uiHints);
|
|
23
22
|
const maxTokensHint = hintForPath('agents.defaults.maxTokens', uiHints);
|
|
24
|
-
const temperatureHint = hintForPath('agents.defaults.temperature', uiHints);
|
|
25
23
|
|
|
26
24
|
useEffect(() => {
|
|
27
25
|
if (config?.agents?.defaults) {
|
|
28
26
|
setModel(config.agents.defaults.model || '');
|
|
29
27
|
setWorkspace(config.agents.defaults.workspace || '');
|
|
30
28
|
setMaxTokens(config.agents.defaults.maxTokens || 8192);
|
|
31
|
-
setTemperature(config.agents.defaults.temperature || 0.7);
|
|
32
29
|
}
|
|
33
30
|
}, [config]);
|
|
34
31
|
|
|
@@ -63,10 +60,6 @@ export function ModelConfig() {
|
|
|
63
60
|
<Skeleton className="h-4 w-28 mb-3" />
|
|
64
61
|
<Skeleton className="h-2 w-full rounded-full" />
|
|
65
62
|
</div>
|
|
66
|
-
<div>
|
|
67
|
-
<Skeleton className="h-4 w-32 mb-3" />
|
|
68
|
-
<Skeleton className="h-2 w-full rounded-full" />
|
|
69
|
-
</div>
|
|
70
63
|
</div>
|
|
71
64
|
</Card>
|
|
72
65
|
</div>
|
|
@@ -77,7 +70,7 @@ export function ModelConfig() {
|
|
|
77
70
|
<div className="max-w-4xl animate-fade-in pb-20">
|
|
78
71
|
<div className="mb-10">
|
|
79
72
|
<h2 className="text-2xl font-bold text-gray-900">Model Configuration</h2>
|
|
80
|
-
<p className="text-sm text-gray-500 mt-1">Configure default AI model and
|
|
73
|
+
<p className="text-sm text-gray-500 mt-1">Configure default AI model and runtime limits</p>
|
|
81
74
|
</div>
|
|
82
75
|
|
|
83
76
|
<form onSubmit={handleSubmit} className="space-y-8">
|
|
@@ -142,7 +135,7 @@ export function ModelConfig() {
|
|
|
142
135
|
<h3 className="text-lg font-bold text-gray-900">Generation Parameters</h3>
|
|
143
136
|
</div>
|
|
144
137
|
|
|
145
|
-
<div className="grid grid-cols-1
|
|
138
|
+
<div className="grid grid-cols-1 gap-12">
|
|
146
139
|
<div className="space-y-4">
|
|
147
140
|
<div className="flex justify-between items-center mb-2">
|
|
148
141
|
<Label className="text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
|
@@ -160,24 +153,6 @@ export function ModelConfig() {
|
|
|
160
153
|
className="w-full h-1 bg-gray-200 rounded-full appearance-none cursor-pointer accent-primary"
|
|
161
154
|
/>
|
|
162
155
|
</div>
|
|
163
|
-
|
|
164
|
-
<div className="space-y-4">
|
|
165
|
-
<div className="flex justify-between items-center mb-2">
|
|
166
|
-
<Label className="text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
|
167
|
-
{temperatureHint?.label ?? 'Temperature'}
|
|
168
|
-
</Label>
|
|
169
|
-
<span className="text-sm font-semibold text-gray-900">{temperature}</span>
|
|
170
|
-
</div>
|
|
171
|
-
<input
|
|
172
|
-
type="range"
|
|
173
|
-
min="0"
|
|
174
|
-
max="2"
|
|
175
|
-
step="0.1"
|
|
176
|
-
value={temperature}
|
|
177
|
-
onChange={(e) => setTemperature(parseFloat(e.target.value))}
|
|
178
|
-
className="w-full h-1 bg-gray-200 rounded-full appearance-none cursor-pointer accent-primary"
|
|
179
|
-
/>
|
|
180
|
-
</div>
|
|
181
156
|
</div>
|
|
182
157
|
</div>
|
|
183
158
|
|