@nextclaw/ui 0.5.39 → 0.5.41
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 +12 -0
- package/dist/assets/{ChannelsList-DI_LXa6G.js → ChannelsList-C2Qd0t9b.js} +1 -1
- package/dist/assets/{ChatPage-CHiqC2bZ.js → ChatPage-BqlsPWdy.js} +1 -1
- package/dist/assets/{CronConfig-BnGmNC74.js → CronConfig-CdvpKuSy.js} +1 -1
- package/dist/assets/{DocBrowser-B9xGvXuH.js → DocBrowser-Bgmeukiz.js} +1 -1
- package/dist/assets/{MarketplacePage-CTiHuMSY.js → MarketplacePage-t3Sib5LO.js} +3 -3
- package/dist/assets/ModelConfig-DZUg-4N3.js +1 -0
- package/dist/assets/ProvidersList-BvtEBIdN.js +1 -0
- package/dist/assets/{RuntimeConfig-a7k7OTvs.js → RuntimeConfig-Dv-06ffH.js} +1 -1
- package/dist/assets/{SecretsConfig-Dv3N-3XD.js → SecretsConfig-Djc-_j3o.js} +2 -2
- package/dist/assets/{SessionsConfig-DTZsKDvH.js → SessionsConfig-BY8rxQ7m.js} +1 -1
- package/dist/assets/{card-BrerW-Nv.js → card-ITZ1xl10.js} +1 -1
- package/dist/assets/index-C8UPX5Q7.js +2 -0
- package/dist/assets/{label-BDeY_kN8.js → label-BowGMNYa.js} +1 -1
- package/dist/assets/{logos-Qxa07LFc.js → logos-BlD3kGKz.js} +1 -1
- package/dist/assets/{page-layout-BRgOXrOh.js → page-layout-C62p_dlG.js} +1 -1
- package/dist/assets/{switch-CblqC0lN.js → switch-CpKwqv79.js} +1 -1
- package/dist/assets/{tabs-custom-B8x9xWoI.js → tabs-custom-D1fYWpL_.js} +1 -1
- package/dist/assets/useConfig-0trWsjx9.js +6 -0
- package/dist/assets/{useConfirmDialog-BxqJXdQR.js → useConfirmDialog-B_7sVz-j.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/api/config.ts +28 -0
- package/src/api/types.ts +15 -0
- package/src/components/config/ModelConfig.tsx +2 -1
- package/src/components/config/ProviderForm.tsx +83 -7
- package/src/components/config/ProvidersList.tsx +38 -6
- package/src/components/marketplace/MarketplacePage.tsx +2 -2
- package/src/hooks/useConfig.ts +36 -0
- package/src/lib/i18n.ts +13 -0
- package/dist/assets/ModelConfig-Dz8pUTqH.js +0 -1
- package/dist/assets/ProvidersList-Du9TbDku.js +0 -1
- package/dist/assets/index-BeI_Pucj.js +0 -2
- package/dist/assets/useConfig-VP_0ZVm1.js +0 -6
package/src/api/config.ts
CHANGED
|
@@ -8,6 +8,9 @@ import type {
|
|
|
8
8
|
ProviderConfigUpdate,
|
|
9
9
|
ProviderConnectionTestRequest,
|
|
10
10
|
ProviderConnectionTestResult,
|
|
11
|
+
ProviderCreateRequest,
|
|
12
|
+
ProviderCreateResult,
|
|
13
|
+
ProviderDeleteResult,
|
|
11
14
|
RuntimeConfigUpdate,
|
|
12
15
|
SecretsConfigUpdate,
|
|
13
16
|
SecretsView,
|
|
@@ -81,6 +84,31 @@ export async function updateProvider(
|
|
|
81
84
|
return response.data;
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
// POST /api/config/providers
|
|
88
|
+
export async function createProvider(
|
|
89
|
+
data: ProviderCreateRequest = {}
|
|
90
|
+
): Promise<ProviderCreateResult> {
|
|
91
|
+
const response = await api.post<ProviderCreateResult>(
|
|
92
|
+
'/api/config/providers',
|
|
93
|
+
data
|
|
94
|
+
);
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
throw new Error(response.error.message);
|
|
97
|
+
}
|
|
98
|
+
return response.data;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// DELETE /api/config/providers/:provider
|
|
102
|
+
export async function deleteProvider(provider: string): Promise<ProviderDeleteResult> {
|
|
103
|
+
const response = await api.delete<ProviderDeleteResult>(
|
|
104
|
+
`/api/config/providers/${provider}`
|
|
105
|
+
);
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
throw new Error(response.error.message);
|
|
108
|
+
}
|
|
109
|
+
return response.data;
|
|
110
|
+
}
|
|
111
|
+
|
|
84
112
|
// POST /api/config/providers/:provider/test
|
|
85
113
|
export async function testProviderConnection(
|
|
86
114
|
provider: string,
|
package/src/api/types.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type ApiResponse<T> =
|
|
|
10
10
|
| { ok: false; error: ApiError };
|
|
11
11
|
|
|
12
12
|
export type ProviderConfigView = {
|
|
13
|
+
displayName?: string;
|
|
13
14
|
apiKeySet: boolean;
|
|
14
15
|
apiKeyMasked?: string;
|
|
15
16
|
apiBase?: string | null;
|
|
@@ -19,6 +20,7 @@ export type ProviderConfigView = {
|
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
export type ProviderConfigUpdate = {
|
|
23
|
+
displayName?: string | null;
|
|
22
24
|
apiKey?: string | null;
|
|
23
25
|
apiBase?: string | null;
|
|
24
26
|
extraHeaders?: Record<string, string> | null;
|
|
@@ -30,6 +32,18 @@ export type ProviderConnectionTestRequest = ProviderConfigUpdate & {
|
|
|
30
32
|
model?: string | null;
|
|
31
33
|
};
|
|
32
34
|
|
|
35
|
+
export type ProviderCreateRequest = ProviderConfigUpdate;
|
|
36
|
+
|
|
37
|
+
export type ProviderCreateResult = {
|
|
38
|
+
name: string;
|
|
39
|
+
provider: ProviderConfigView;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type ProviderDeleteResult = {
|
|
43
|
+
deleted: boolean;
|
|
44
|
+
provider: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
33
47
|
export type ProviderConnectionTestErrorCode =
|
|
34
48
|
| 'API_KEY_REQUIRED'
|
|
35
49
|
| 'MODEL_REQUIRED'
|
|
@@ -325,6 +339,7 @@ export type ConfigView = {
|
|
|
325
339
|
export type ProviderSpecView = {
|
|
326
340
|
name: string;
|
|
327
341
|
displayName?: string;
|
|
342
|
+
isCustom?: boolean;
|
|
328
343
|
modelPrefix?: string;
|
|
329
344
|
keywords: string[];
|
|
330
345
|
envKey: string;
|
|
@@ -100,9 +100,10 @@ export function ModelConfig() {
|
|
|
100
100
|
(config?.providers?.[provider.name]?.models ?? []).map((model) => toProviderLocalModel(model, aliases))
|
|
101
101
|
);
|
|
102
102
|
const allModels = normalizeStringList([...defaultModels, ...customModels]);
|
|
103
|
+
const configDisplayName = config?.providers?.[provider.name]?.displayName?.trim();
|
|
103
104
|
return {
|
|
104
105
|
name: provider.name,
|
|
105
|
-
displayName: provider.displayName || provider.name,
|
|
106
|
+
displayName: configDisplayName || provider.displayName || provider.name,
|
|
106
107
|
prefix,
|
|
107
108
|
aliases,
|
|
108
109
|
models: allModels
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
useConfig,
|
|
4
|
+
useConfigMeta,
|
|
5
|
+
useConfigSchema,
|
|
6
|
+
useDeleteProvider,
|
|
7
|
+
useTestProviderConnection,
|
|
8
|
+
useUpdateProvider
|
|
9
|
+
} from '@/hooks/useConfig';
|
|
3
10
|
import { Button } from '@/components/ui/button';
|
|
4
11
|
import { Input } from '@/components/ui/input';
|
|
5
12
|
import { Label } from '@/components/ui/label';
|
|
@@ -10,7 +17,7 @@ import { StatusDot } from '@/components/ui/status-dot';
|
|
|
10
17
|
import { t } from '@/lib/i18n';
|
|
11
18
|
import { hintForPath } from '@/lib/config-hints';
|
|
12
19
|
import type { ProviderConfigUpdate, ProviderConnectionTestRequest } from '@/api/types';
|
|
13
|
-
import { KeyRound, Globe, Hash, RotateCcw, CircleDotDashed, Sparkles, Plus, X } from 'lucide-react';
|
|
20
|
+
import { KeyRound, Globe, Hash, RotateCcw, CircleDotDashed, Sparkles, Plus, X, Trash2 } from 'lucide-react';
|
|
14
21
|
import { toast } from 'sonner';
|
|
15
22
|
import { CONFIG_DETAIL_CARD_CLASS, CONFIG_EMPTY_DETAIL_CARD_CLASS } from './config-layout';
|
|
16
23
|
|
|
@@ -18,6 +25,7 @@ type WireApiType = 'auto' | 'chat' | 'responses';
|
|
|
18
25
|
|
|
19
26
|
type ProviderFormProps = {
|
|
20
27
|
providerName?: string;
|
|
28
|
+
onProviderDeleted?: (providerName: string) => void;
|
|
21
29
|
};
|
|
22
30
|
|
|
23
31
|
function normalizeHeaders(input: Record<string, string> | null | undefined): Record<string, string> | null {
|
|
@@ -129,11 +137,12 @@ function serializeModelsForSave(models: string[], defaultModels: string[]): stri
|
|
|
129
137
|
return models;
|
|
130
138
|
}
|
|
131
139
|
|
|
132
|
-
export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
140
|
+
export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormProps) {
|
|
133
141
|
const { data: config } = useConfig();
|
|
134
142
|
const { data: meta } = useConfigMeta();
|
|
135
143
|
const { data: schema } = useConfigSchema();
|
|
136
144
|
const updateProvider = useUpdateProvider();
|
|
145
|
+
const deleteProvider = useDeleteProvider();
|
|
137
146
|
const testProviderConnection = useTestProviderConnection();
|
|
138
147
|
|
|
139
148
|
const [apiKey, setApiKey] = useState('');
|
|
@@ -142,17 +151,22 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
142
151
|
const [wireApi, setWireApi] = useState<WireApiType>('auto');
|
|
143
152
|
const [models, setModels] = useState<string[]>([]);
|
|
144
153
|
const [modelDraft, setModelDraft] = useState('');
|
|
154
|
+
const [providerDisplayName, setProviderDisplayName] = useState('');
|
|
145
155
|
|
|
146
156
|
const providerSpec = meta?.providers.find((p) => p.name === providerName);
|
|
147
157
|
const providerConfig = providerName ? config?.providers[providerName] : null;
|
|
148
158
|
const uiHints = schema?.uiHints;
|
|
159
|
+
const isCustomProvider = Boolean(providerSpec?.isCustom);
|
|
149
160
|
|
|
150
161
|
const apiKeyHint = providerName ? hintForPath(`providers.${providerName}.apiKey`, uiHints) : undefined;
|
|
151
162
|
const apiBaseHint = providerName ? hintForPath(`providers.${providerName}.apiBase`, uiHints) : undefined;
|
|
152
163
|
const extraHeadersHint = providerName ? hintForPath(`providers.${providerName}.extraHeaders`, uiHints) : undefined;
|
|
153
164
|
const wireApiHint = providerName ? hintForPath(`providers.${providerName}.wireApi`, uiHints) : undefined;
|
|
165
|
+
const defaultDisplayName = providerSpec?.displayName || providerName || '';
|
|
166
|
+
const currentDisplayName = (providerConfig?.displayName || '').trim();
|
|
167
|
+
const effectiveDisplayName = currentDisplayName || defaultDisplayName;
|
|
154
168
|
|
|
155
|
-
const providerTitle =
|
|
169
|
+
const providerTitle = providerDisplayName.trim() || effectiveDisplayName || providerName || t('providersSelectPlaceholder');
|
|
156
170
|
const providerModelPrefix = providerSpec?.modelPrefix || providerName || '';
|
|
157
171
|
const providerModelAliases = useMemo(
|
|
158
172
|
() => normalizeModelList([providerModelPrefix, providerName || '']),
|
|
@@ -192,6 +206,7 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
192
206
|
setWireApi('auto');
|
|
193
207
|
setModels([]);
|
|
194
208
|
setModelDraft('');
|
|
209
|
+
setProviderDisplayName('');
|
|
195
210
|
return;
|
|
196
211
|
}
|
|
197
212
|
|
|
@@ -201,7 +216,8 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
201
216
|
setWireApi(currentWireApi);
|
|
202
217
|
setModels(currentEditableModels);
|
|
203
218
|
setModelDraft('');
|
|
204
|
-
|
|
219
|
+
setProviderDisplayName(effectiveDisplayName);
|
|
220
|
+
}, [providerName, currentApiBase, providerConfig?.extraHeaders, currentWireApi, currentEditableModels, effectiveDisplayName]);
|
|
205
221
|
|
|
206
222
|
const hasChanges = useMemo(() => {
|
|
207
223
|
if (!providerName) {
|
|
@@ -212,10 +228,16 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
212
228
|
const headersChanged = !headersEqual(extraHeaders, currentHeaders);
|
|
213
229
|
const wireApiChanged = providerSpec?.supportsWireApi ? wireApi !== currentWireApi : false;
|
|
214
230
|
const modelsChanged = !modelListsEqual(models, currentEditableModels);
|
|
231
|
+
const displayNameChanged = isCustomProvider
|
|
232
|
+
? providerDisplayName.trim() !== effectiveDisplayName
|
|
233
|
+
: false;
|
|
215
234
|
|
|
216
|
-
return apiKeyChanged || apiBaseChanged || headersChanged || wireApiChanged || modelsChanged;
|
|
235
|
+
return apiKeyChanged || apiBaseChanged || headersChanged || wireApiChanged || modelsChanged || displayNameChanged;
|
|
217
236
|
}, [
|
|
218
237
|
providerName,
|
|
238
|
+
isCustomProvider,
|
|
239
|
+
providerDisplayName,
|
|
240
|
+
effectiveDisplayName,
|
|
219
241
|
apiKey,
|
|
220
242
|
apiBase,
|
|
221
243
|
currentApiBase,
|
|
@@ -235,6 +257,7 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
235
257
|
setWireApi((providerSpec?.defaultWireApi || 'auto') as WireApiType);
|
|
236
258
|
setModels(defaultModels);
|
|
237
259
|
setModelDraft('');
|
|
260
|
+
setProviderDisplayName(defaultDisplayName);
|
|
238
261
|
};
|
|
239
262
|
|
|
240
263
|
const handleAddModel = () => {
|
|
@@ -261,6 +284,11 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
261
284
|
const trimmedApiKey = apiKey.trim();
|
|
262
285
|
const trimmedApiBase = apiBase.trim();
|
|
263
286
|
const normalizedHeaders = normalizeHeaders(extraHeaders);
|
|
287
|
+
const trimmedDisplayName = providerDisplayName.trim();
|
|
288
|
+
|
|
289
|
+
if (isCustomProvider && trimmedDisplayName !== effectiveDisplayName) {
|
|
290
|
+
payload.displayName = trimmedDisplayName.length > 0 ? trimmedDisplayName : null;
|
|
291
|
+
}
|
|
264
292
|
|
|
265
293
|
if (trimmedApiKey.length > 0) {
|
|
266
294
|
payload.apiKey = trimmedApiKey;
|
|
@@ -289,10 +317,12 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
289
317
|
return;
|
|
290
318
|
}
|
|
291
319
|
|
|
320
|
+
const preferredModel = models.find((modelName) => modelName.trim().length > 0) ?? '';
|
|
321
|
+
const testModel = toProviderLocalModelId(preferredModel, providerModelAliases);
|
|
292
322
|
const payload: ProviderConnectionTestRequest = {
|
|
293
323
|
apiBase: apiBase.trim(),
|
|
294
324
|
extraHeaders: normalizeHeaders(extraHeaders),
|
|
295
|
-
model:
|
|
325
|
+
model: testModel || null
|
|
296
326
|
};
|
|
297
327
|
if (apiKey.trim().length > 0) {
|
|
298
328
|
payload.apiKey = apiKey.trim();
|
|
@@ -321,6 +351,22 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
321
351
|
}
|
|
322
352
|
};
|
|
323
353
|
|
|
354
|
+
const handleDeleteProvider = async () => {
|
|
355
|
+
if (!providerName || !isCustomProvider) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
const confirmed = window.confirm(t('providerDeleteConfirm'));
|
|
359
|
+
if (!confirmed) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
await deleteProvider.mutateAsync({ provider: providerName });
|
|
364
|
+
onProviderDeleted?.(providerName);
|
|
365
|
+
} catch {
|
|
366
|
+
// toast handled by mutation hook
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
324
370
|
if (!providerName || !providerSpec || !providerConfig) {
|
|
325
371
|
return (
|
|
326
372
|
<div className={CONFIG_EMPTY_DETAIL_CARD_CLASS}>
|
|
@@ -348,6 +394,24 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
348
394
|
|
|
349
395
|
<form onSubmit={handleSubmit} className="flex min-h-0 flex-1 flex-col">
|
|
350
396
|
<div className="min-h-0 flex-1 space-y-6 overflow-y-auto px-6 py-5">
|
|
397
|
+
{isCustomProvider && (
|
|
398
|
+
<div className="space-y-2.5">
|
|
399
|
+
<Label htmlFor="providerDisplayName" className="flex items-center gap-2 text-sm font-medium text-gray-900">
|
|
400
|
+
<Hash className="h-3.5 w-3.5 text-gray-500" />
|
|
401
|
+
{t('providerDisplayName')}
|
|
402
|
+
</Label>
|
|
403
|
+
<Input
|
|
404
|
+
id="providerDisplayName"
|
|
405
|
+
type="text"
|
|
406
|
+
value={providerDisplayName}
|
|
407
|
+
onChange={(e) => setProviderDisplayName(e.target.value)}
|
|
408
|
+
placeholder={defaultDisplayName || t('providerDisplayNamePlaceholder')}
|
|
409
|
+
className="rounded-xl"
|
|
410
|
+
/>
|
|
411
|
+
<p className="text-xs text-gray-500">{t('providerDisplayNameHelp')}</p>
|
|
412
|
+
</div>
|
|
413
|
+
)}
|
|
414
|
+
|
|
351
415
|
<div className="space-y-2.5">
|
|
352
416
|
<Label htmlFor="apiKey" className="flex items-center gap-2 text-sm font-medium text-gray-900">
|
|
353
417
|
<KeyRound className="h-3.5 w-3.5 text-gray-500" />
|
|
@@ -378,6 +442,7 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
378
442
|
className="rounded-xl"
|
|
379
443
|
/>
|
|
380
444
|
<p className="text-xs text-gray-500">{apiBaseHelpText}</p>
|
|
445
|
+
{isCustomProvider && <p className="text-xs text-gray-500">{t('providerOpenAICompatHint')}</p>}
|
|
381
446
|
</div>
|
|
382
447
|
|
|
383
448
|
{providerSpec.supportsWireApi && (
|
|
@@ -471,6 +536,17 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
471
536
|
<RotateCcw className="mr-2 h-4 w-4" />
|
|
472
537
|
{t('resetToDefault')}
|
|
473
538
|
</Button>
|
|
539
|
+
{isCustomProvider && (
|
|
540
|
+
<Button
|
|
541
|
+
type="button"
|
|
542
|
+
variant="outline"
|
|
543
|
+
onClick={handleDeleteProvider}
|
|
544
|
+
disabled={deleteProvider.isPending}
|
|
545
|
+
>
|
|
546
|
+
<Trash2 className="mr-2 h-4 w-4" />
|
|
547
|
+
{deleteProvider.isPending ? t('saving') : t('providerDelete')}
|
|
548
|
+
</Button>
|
|
549
|
+
)}
|
|
474
550
|
<Button type="button" variant="outline" onClick={handleTestConnection} disabled={testProviderConnection.isPending}>
|
|
475
551
|
<CircleDotDashed className="mr-2 h-4 w-4" />
|
|
476
552
|
{testProviderConnection.isPending ? t('providerTestingConnection') : t('providerTestConnection')}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import { useConfig, useConfigMeta, useConfigSchema } from '@/hooks/useConfig';
|
|
3
|
-
import { Search, KeyRound } from 'lucide-react';
|
|
2
|
+
import { useConfig, useConfigMeta, useConfigSchema, useCreateProvider } from '@/hooks/useConfig';
|
|
3
|
+
import { Search, KeyRound, Plus } from 'lucide-react';
|
|
4
4
|
import { ProviderForm } from './ProviderForm';
|
|
5
5
|
import { cn } from '@/lib/utils';
|
|
6
6
|
import { Tabs } from '@/components/ui/tabs-custom';
|
|
@@ -11,6 +11,7 @@ import { StatusDot } from '@/components/ui/status-dot';
|
|
|
11
11
|
import { t } from '@/lib/i18n';
|
|
12
12
|
import { PageLayout, PageHeader } from '@/components/layout/page-layout';
|
|
13
13
|
import { Input } from '@/components/ui/input';
|
|
14
|
+
import { Button } from '@/components/ui/button';
|
|
14
15
|
import { CONFIG_SIDEBAR_CARD_CLASS, CONFIG_SPLIT_GRID_CLASS } from './config-layout';
|
|
15
16
|
|
|
16
17
|
function formatBasePreview(base?: string | null): string | null {
|
|
@@ -30,6 +31,7 @@ export function ProvidersList() {
|
|
|
30
31
|
const { data: config } = useConfig();
|
|
31
32
|
const { data: meta } = useConfigMeta();
|
|
32
33
|
const { data: schema } = useConfigSchema();
|
|
34
|
+
const createProvider = useCreateProvider();
|
|
33
35
|
|
|
34
36
|
const [activeTab, setActiveTab] = useState('installed');
|
|
35
37
|
const [selectedProvider, setSelectedProvider] = useState<string | undefined>();
|
|
@@ -60,7 +62,8 @@ export function ProvidersList() {
|
|
|
60
62
|
if (!keyword) {
|
|
61
63
|
return true;
|
|
62
64
|
}
|
|
63
|
-
const
|
|
65
|
+
const configDisplayName = baseConfig[provider.name]?.displayName?.trim();
|
|
66
|
+
const display = (configDisplayName || provider.displayName || provider.name).toLowerCase();
|
|
64
67
|
return display.includes(keyword) || provider.name.toLowerCase().includes(keyword);
|
|
65
68
|
});
|
|
66
69
|
}, [meta, config, activeTab, query]);
|
|
@@ -79,6 +82,17 @@ export function ProvidersList() {
|
|
|
79
82
|
|
|
80
83
|
const selectedName = selectedProvider;
|
|
81
84
|
|
|
85
|
+
const handleCreateCustomProvider = async () => {
|
|
86
|
+
try {
|
|
87
|
+
const result = await createProvider.mutateAsync({ data: {} });
|
|
88
|
+
setActiveTab('all');
|
|
89
|
+
setQuery('');
|
|
90
|
+
setSelectedProvider(result.name);
|
|
91
|
+
} catch {
|
|
92
|
+
// toast handled in hook
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
82
96
|
if (!config || !meta) {
|
|
83
97
|
return <div className="p-8">{t('providersLoading')}</div>;
|
|
84
98
|
}
|
|
@@ -89,8 +103,18 @@ export function ProvidersList() {
|
|
|
89
103
|
|
|
90
104
|
<div className={CONFIG_SPLIT_GRID_CLASS}>
|
|
91
105
|
<section className={CONFIG_SIDEBAR_CARD_CLASS}>
|
|
92
|
-
<div className="border-b border-gray-100 px-4 pt-4">
|
|
106
|
+
<div className="border-b border-gray-100 px-4 pt-4 pb-3 space-y-3">
|
|
93
107
|
<Tabs tabs={tabs} activeTab={activeTab} onChange={setActiveTab} className="mb-0" />
|
|
108
|
+
<Button
|
|
109
|
+
type="button"
|
|
110
|
+
variant="outline"
|
|
111
|
+
className="w-full justify-center"
|
|
112
|
+
onClick={handleCreateCustomProvider}
|
|
113
|
+
disabled={createProvider.isPending}
|
|
114
|
+
>
|
|
115
|
+
<Plus className="mr-2 h-4 w-4" />
|
|
116
|
+
{createProvider.isPending ? t('saving') : t('providerAddCustom')}
|
|
117
|
+
</Button>
|
|
94
118
|
</div>
|
|
95
119
|
|
|
96
120
|
<div className="border-b border-gray-100 px-4 py-3">
|
|
@@ -110,6 +134,7 @@ export function ProvidersList() {
|
|
|
110
134
|
const providerConfig = config.providers[provider.name];
|
|
111
135
|
const isReady = Boolean(providerConfig?.apiKeySet);
|
|
112
136
|
const isActive = selectedName === provider.name;
|
|
137
|
+
const providerLabel = providerConfig?.displayName?.trim() || provider.displayName || provider.name;
|
|
113
138
|
const providerHint = hintForPath(`providers.${provider.name}`, uiHints);
|
|
114
139
|
const resolvedBase = providerConfig?.apiBase || provider.defaultApiBase || '';
|
|
115
140
|
const basePreview = formatBasePreview(resolvedBase);
|
|
@@ -140,7 +165,7 @@ export function ProvidersList() {
|
|
|
140
165
|
fallback={<span className="text-sm font-semibold uppercase text-gray-500">{provider.name[0]}</span>}
|
|
141
166
|
/>
|
|
142
167
|
<div className="min-w-0">
|
|
143
|
-
<p className="truncate text-sm font-semibold text-gray-900">{
|
|
168
|
+
<p className="truncate text-sm font-semibold text-gray-900">{providerLabel}</p>
|
|
144
169
|
<p className="line-clamp-1 text-[11px] text-gray-500">{description}</p>
|
|
145
170
|
</div>
|
|
146
171
|
</div>
|
|
@@ -165,7 +190,14 @@ export function ProvidersList() {
|
|
|
165
190
|
</div>
|
|
166
191
|
</section>
|
|
167
192
|
|
|
168
|
-
<ProviderForm
|
|
193
|
+
<ProviderForm
|
|
194
|
+
providerName={selectedName}
|
|
195
|
+
onProviderDeleted={(deletedProvider) => {
|
|
196
|
+
if (deletedProvider === selectedProvider) {
|
|
197
|
+
setSelectedProvider(undefined);
|
|
198
|
+
}
|
|
199
|
+
}}
|
|
200
|
+
/>
|
|
169
201
|
</div>
|
|
170
202
|
</PageLayout>
|
|
171
203
|
);
|
|
@@ -271,7 +271,7 @@ function buildGenericDetailDataUrl(params: {
|
|
|
271
271
|
.wrap { max-width: 980px; margin: 0 auto; padding: 28px 20px 40px; }
|
|
272
272
|
.hero { border: 1px solid #dbeafe; border-radius: 16px; background: linear-gradient(180deg, #eff6ff, #ffffff); padding: 20px; box-shadow: 0 6px 20px rgba(30, 64, 175, 0.08); }
|
|
273
273
|
.hero h1 { margin: 0; font-size: 26px; }
|
|
274
|
-
.meta { margin-top: 8px; color: #475569; font-size: 13px; }
|
|
274
|
+
.meta { margin-top: 8px; color: #475569; font-size: 13px; overflow-wrap: anywhere; word-break: break-word; }
|
|
275
275
|
.summary { margin-top: 14px; font-size: 14px; line-height: 1.7; color: #334155; }
|
|
276
276
|
.grid { display: grid; grid-template-columns: 260px 1fr; gap: 14px; margin-top: 16px; }
|
|
277
277
|
.card { border: 1px solid #e2e8f0; background: #fff; border-radius: 14px; overflow: hidden; }
|
|
@@ -280,7 +280,7 @@ function buildGenericDetailDataUrl(params: {
|
|
|
280
280
|
.code { white-space: pre-wrap; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; line-height: 1.6; margin: 0; }
|
|
281
281
|
.tags { margin-top: 10px; }
|
|
282
282
|
.tag { display: inline-block; margin: 0 6px 6px 0; padding: 4px 9px; border-radius: 999px; background: #e0e7ff; color: #3730a3; font-size: 11px; }
|
|
283
|
-
.source { color: #2563eb; text-decoration: none; }
|
|
283
|
+
.source { color: #2563eb; text-decoration: none; overflow-wrap: anywhere; word-break: break-all; }
|
|
284
284
|
@media (max-width: 860px) { .grid { grid-template-columns: 1fr; } }
|
|
285
285
|
</style>
|
|
286
286
|
</head>
|
package/src/hooks/useConfig.ts
CHANGED
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
fetchConfigMeta,
|
|
5
5
|
fetchConfigSchema,
|
|
6
6
|
updateModel,
|
|
7
|
+
createProvider,
|
|
8
|
+
deleteProvider,
|
|
7
9
|
updateProvider,
|
|
8
10
|
testProviderConnection,
|
|
9
11
|
updateChannel,
|
|
@@ -71,6 +73,40 @@ export function useUpdateProvider() {
|
|
|
71
73
|
updateProvider(provider, data as Parameters<typeof updateProvider>[1]),
|
|
72
74
|
onSuccess: () => {
|
|
73
75
|
queryClient.invalidateQueries({ queryKey: ['config'] });
|
|
76
|
+
queryClient.invalidateQueries({ queryKey: ['config-meta'] });
|
|
77
|
+
toast.success(t('configSaved'));
|
|
78
|
+
},
|
|
79
|
+
onError: (error: Error) => {
|
|
80
|
+
toast.error(t('configSaveFailed') + ': ' + error.message);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function useCreateProvider() {
|
|
86
|
+
const queryClient = useQueryClient();
|
|
87
|
+
|
|
88
|
+
return useMutation({
|
|
89
|
+
mutationFn: ({ data }: { data?: unknown }) =>
|
|
90
|
+
createProvider((data ?? {}) as Parameters<typeof createProvider>[0]),
|
|
91
|
+
onSuccess: () => {
|
|
92
|
+
queryClient.invalidateQueries({ queryKey: ['config'] });
|
|
93
|
+
queryClient.invalidateQueries({ queryKey: ['config-meta'] });
|
|
94
|
+
toast.success(t('configSaved'));
|
|
95
|
+
},
|
|
96
|
+
onError: (error: Error) => {
|
|
97
|
+
toast.error(t('configSaveFailed') + ': ' + error.message);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function useDeleteProvider() {
|
|
103
|
+
const queryClient = useQueryClient();
|
|
104
|
+
|
|
105
|
+
return useMutation({
|
|
106
|
+
mutationFn: ({ provider }: { provider: string }) => deleteProvider(provider),
|
|
107
|
+
onSuccess: () => {
|
|
108
|
+
queryClient.invalidateQueries({ queryKey: ['config'] });
|
|
109
|
+
queryClient.invalidateQueries({ queryKey: ['config-meta'] });
|
|
74
110
|
toast.success(t('configSaved'));
|
|
75
111
|
},
|
|
76
112
|
onError: (error: Error) => {
|
package/src/lib/i18n.ts
CHANGED
|
@@ -184,6 +184,9 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
184
184
|
providersTabAll: { zh: '全部提供商', en: 'All Providers' },
|
|
185
185
|
providersFilterPlaceholder: { zh: '搜索提供商', en: 'Search providers' },
|
|
186
186
|
providersNoMatch: { zh: '没有匹配的提供商', en: 'No matching providers' },
|
|
187
|
+
providerAddCustom: { zh: '新增自定义提供商', en: 'Add Custom Provider' },
|
|
188
|
+
providerDelete: { zh: '删除该提供商', en: 'Delete Provider' },
|
|
189
|
+
providerDeleteConfirm: { zh: '确认删除这个自定义提供商吗?删除后不可恢复。', en: 'Delete this custom provider? This action cannot be undone.' },
|
|
187
190
|
providersSelectPlaceholder: { zh: '选择提供商', en: 'Select Provider' },
|
|
188
191
|
providersSelectTitle: { zh: '选择左侧提供商开始配置', en: 'Select a provider from the left to configure' },
|
|
189
192
|
providersSelectDescription: { zh: '你可以连续切换多个提供商并逐个保存配置。', en: 'Switch between providers continuously and save each configuration.' },
|
|
@@ -202,12 +205,22 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
202
205
|
showKey: { zh: '显示密钥', en: 'Show Key' },
|
|
203
206
|
hideKey: { zh: '隐藏密钥', en: 'Hide Key' },
|
|
204
207
|
providerFormDescription: { zh: '配置 AI 提供商的 API 密钥与参数', en: 'Configure API keys and parameters for AI provider' },
|
|
208
|
+
providerDisplayName: { zh: '自定义名称', en: 'Custom Name' },
|
|
209
|
+
providerDisplayNamePlaceholder: { zh: '例如:中转站 A', en: 'For example: Relay A' },
|
|
210
|
+
providerDisplayNameHelp: {
|
|
211
|
+
zh: '仅用于界面展示,便于区分多个自定义 Provider。',
|
|
212
|
+
en: 'Display-only label to distinguish multiple custom providers.'
|
|
213
|
+
},
|
|
205
214
|
enterApiKey: { zh: '请输入 API 密钥', en: 'Enter API Key' },
|
|
206
215
|
providerApiBaseHelp: { zh: '留空或恢复默认即可使用预置 API Base。', en: 'Leave empty or reset to use the default API base.' },
|
|
207
216
|
providerApiBaseHelpMinimax: {
|
|
208
217
|
zh: 'MiniMax 中国区请使用 https://api.minimaxi.com/v1;海外请使用 https://api.minimax.io/v1。',
|
|
209
218
|
en: 'Use https://api.minimaxi.com/v1 for Mainland China accounts, and https://api.minimax.io/v1 for overseas accounts.'
|
|
210
219
|
},
|
|
220
|
+
providerOpenAICompatHint: {
|
|
221
|
+
zh: '自定义提供商默认按 OpenAI 兼容 API 格式接入(Chat Completions / Responses)。',
|
|
222
|
+
en: 'Custom providers use OpenAI-compatible API format by default (Chat Completions / Responses).'
|
|
223
|
+
},
|
|
211
224
|
providerExtraHeadersHelp: { zh: '用于自定义请求头(可选)。', en: 'Optional custom request headers.' },
|
|
212
225
|
providerTestConnection: { zh: '测试连接', en: 'Test Connection' },
|
|
213
226
|
providerTestingConnection: { zh: '测试中...', en: 'Testing...' },
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{j as e,r as c,F as Q,g as V,m as X,s as Z,J as H,O as ee,U as se}from"./vendor-DN_iJQc4.js";import{P as te,a as ae,B as re}from"./page-layout-BRgOXrOh.js";import{C as _}from"./card-BrerW-Nv.js";import{I as q,u as ne,a as le,b as oe,c as ie}from"./useConfig-VP_0ZVm1.js";import{L as $}from"./label-BDeY_kN8.js";import{c as B,t as d,S as ce,a as de,b as me,d as xe,e as ue,D as he,f as fe}from"./index-BeI_Pucj.js";import{h as A}from"./config-hints-CApS3K_7.js";function u({className:a,...n}){return e.jsx("div",{className:B("animate-pulse rounded-md bg-slate-200",a),...n})}function pe(a){const n=new Set;for(const r of a){const l=r.trim();l.length>0&&n.add(l)}return[...n]}function ge({id:a,value:n,onChange:r,options:l,placeholder:m,className:j,inputClassName:x,emptyText:C,createText:b,maxItems:S=Number.POSITIVE_INFINITY,onEnter:k}){const[v,h]=c.useState(!1),N=c.useMemo(()=>pe(l),[l]),i=n.trim().toLowerCase(),f=c.useMemo(()=>{const t=N.map((o,w)=>({option:o,index:w}));i.length>0&&t.sort((o,w)=>{const y=o.option.toLowerCase(),M=w.option.toLowerCase(),I=y===i?0:y.startsWith(i)?1:y.includes(i)?2:3,L=M===i?0:M.startsWith(i)?1:M.includes(i)?2:3;return I!==L?I-L:o.index-w.index});const g=t.map(o=>o.option);return Number.isFinite(S)?g.slice(0,Math.max(1,S)):g},[N,i,S]),P=n.trim().length>0&&N.some(t=>t===n.trim());return e.jsxs("div",{className:B("relative",j),onBlur:()=>{setTimeout(()=>h(!1),120)},children:[e.jsx(q,{id:a,value:n,onFocus:()=>h(!0),onChange:t=>{r(t.target.value),v||h(!0)},onKeyDown:t=>{t.key==="Enter"&&(k&&(t.preventDefault(),k()),h(!1))},placeholder:m,className:B("pr-10",x)}),e.jsx("button",{type:"button",onMouseDown:t=>t.preventDefault(),onClick:()=>h(t=>!t),className:"absolute inset-y-0 right-0 inline-flex w-10 items-center justify-center text-gray-400 hover:text-gray-600","aria-label":"toggle model options",children:e.jsx(Q,{className:"h-4 w-4"})}),v&&e.jsx("div",{className:"absolute z-20 mt-1 w-full overflow-hidden rounded-xl border border-gray-200 bg-white shadow-lg",children:e.jsxs("div",{className:"max-h-60 overflow-y-auto py-1",children:[!P&&n.trim().length>0&&e.jsxs("button",{type:"button",onMouseDown:t=>t.preventDefault(),onClick:()=>{r(n.trim()),h(!1)},className:"flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-gray-50",children:[e.jsx(V,{className:"h-4 w-4 text-transparent"}),e.jsx("span",{className:"truncate text-gray-700",children:b?b.replace("{value}",n.trim()):n.trim()})]}),f.map(t=>e.jsxs("button",{type:"button",onMouseDown:g=>g.preventDefault(),onClick:()=>{r(t),h(!1)},className:"flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-gray-50",children:[e.jsx(V,{className:B("h-4 w-4",t===n.trim()?"text-primary":"text-transparent")}),e.jsx("span",{className:"truncate text-gray-700",children:t})]},t)),f.length===0&&n.trim().length===0&&e.jsx("div",{className:"px-3 py-2 text-sm text-gray-500",children:C??"No models available"})]})})]})}function z(a){if(!a||a.length===0)return[];const n=new Set;for(const r of a){const l=r.trim();l&&n.add(l)}return[...n]}function je(a,n){const r=a.trim(),l=n.trim();if(!r||!l)return r;const m=`${l}/`;return r.startsWith(m)?r.slice(m.length):r}function T(a,n){let r=a.trim();if(!r)return"";for(const l of n)r=je(r,l);return r.trim()}function Ne(a,n){const r=a.trim();if(!r)return null;let l=null;for(const m of n)for(const j of m.aliases){const x=j.trim();x&&(r===x||r.startsWith(`${x}/`))&&(!l||x.length>l.score)&&(l={name:m.name,score:x.length})}return(l==null?void 0:l.name)??null}function ke(){const{data:a,isLoading:n}=ne(),{data:r}=le(),{data:l}=oe(),m=ie(),[j,x]=c.useState(""),[C,b]=c.useState(""),[S,k]=c.useState(""),[v,h]=c.useState(8192),N=l==null?void 0:l.uiHints,i=A("agents.defaults.model",N),f=A("agents.defaults.workspace",N),P=A("agents.defaults.maxTokens",N),t=c.useMemo(()=>((r==null?void 0:r.providers)??[]).map(s=>{var D,R;const O=(s.modelPrefix||s.name||"").trim(),p=z([s.modelPrefix||"",s.name||""]),U=z((s.defaultModels??[]).map(W=>T(W,p))),F=z((((R=(D=a==null?void 0:a.providers)==null?void 0:D[s.name])==null?void 0:R.models)??[]).map(W=>T(W,p))),E=z([...U,...F]);return{name:s.name,displayName:s.displayName||s.name,prefix:O,aliases:p,models:E}}),[r,a]),g=c.useMemo(()=>new Map(t.map(s=>[s.name,s])),[t]),o=g.get(j)??t[0],w=(o==null?void 0:o.name)??"",y=c.useMemo(()=>(o==null?void 0:o.aliases)??[],[o]),M=c.useMemo(()=>(o==null?void 0:o.models)??[],[o]);c.useEffect(()=>{j||t.length===0||x(t[0].name)},[j,t]),c.useEffect(()=>{var F,E,D;if(!((F=a==null?void 0:a.agents)!=null&&F.defaults))return;const s=(a.agents.defaults.model||"").trim(),p=Ne(s,t)??((E=t[0])==null?void 0:E.name)??"",U=((D=g.get(p))==null?void 0:D.aliases)??[];x(p),b(T(s,U)),k(a.agents.defaults.workspace||""),h(a.agents.defaults.maxTokens||8192)},[a,t,g]);const I=c.useMemo(()=>{const s=new Set;for(const O of M){const p=O.trim();p&&s.add(p)}return[...s]},[M]),L=c.useMemo(()=>{const s=T(C,y);return s?!o||!o.prefix?s:`${o.prefix}/${s}`:""},[C,o,y]),G=(i==null?void 0:i.help)??"",J=s=>{x(s),b("")},K=s=>{b(T(s,y))},Y=s=>{s.preventDefault(),m.mutate({model:L,maxTokens:v})};return n?e.jsxs("div",{className:"max-w-2xl space-y-6",children:[e.jsxs("div",{className:"space-y-2",children:[e.jsx(u,{className:"h-8 w-32"}),e.jsx(u,{className:"h-4 w-48"})]}),e.jsxs(_,{className:"rounded-2xl border-gray-200 p-6",children:[e.jsxs("div",{className:"flex items-center gap-4 mb-6",children:[e.jsx(u,{className:"h-12 w-12 rounded-xl"}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(u,{className:"h-5 w-24"}),e.jsx(u,{className:"h-3 w-32"})]})]}),e.jsx(u,{className:"h-4 w-20 mb-2"}),e.jsx(u,{className:"h-10 w-full rounded-xl"})]}),e.jsxs(_,{className:"rounded-2xl border-gray-200 p-6",children:[e.jsx(u,{className:"h-5 w-24 mb-2"}),e.jsx(u,{className:"h-3 w-40 mb-6"}),e.jsx("div",{className:"space-y-6",children:e.jsxs("div",{children:[e.jsx(u,{className:"h-4 w-28 mb-3"}),e.jsx(u,{className:"h-2 w-full rounded-full"})]})})]})]}):e.jsxs(te,{children:[e.jsx(ae,{title:d("modelPageTitle"),description:d("modelPageDescription")}),e.jsxs("form",{onSubmit:Y,className:"space-y-8",children:[e.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-8",children:[e.jsxs("div",{className:"p-8 rounded-2xl bg-white border border-gray-200 shadow-card",children:[e.jsxs("div",{className:"flex items-center gap-4 mb-8",children:[e.jsx("div",{className:"h-10 w-10 rounded-xl bg-primary flex items-center justify-center text-white",children:e.jsx(X,{className:"h-5 w-5"})}),e.jsx("h3",{className:"text-lg font-bold text-gray-900",children:d("defaultModel")})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx($,{htmlFor:"model",className:"text-xs font-semibold text-gray-500 uppercase tracking-wider",children:(i==null?void 0:i.label)??"Model Name"}),e.jsxs("div",{className:"flex flex-col gap-2 sm:flex-row sm:items-center",children:[e.jsx("div",{className:"sm:w-[38%] sm:min-w-[170px]",children:e.jsxs(ce,{value:w,onValueChange:J,children:[e.jsx(de,{className:"h-10 w-full rounded-xl",children:e.jsx(me,{placeholder:d("providersSelectPlaceholder")})}),e.jsx(xe,{children:t.map(s=>e.jsx(ue,{value:s.name,children:s.displayName},s.name))})]})}),e.jsx("span",{className:"hidden h-10 items-center justify-center leading-none text-lg font-semibold text-gray-300 sm:inline-flex",children:"/"}),e.jsx(ge,{id:"model",value:C,onChange:K,options:I,placeholder:(i==null?void 0:i.placeholder)??"gpt-5.1",className:"sm:flex-1",inputClassName:"h-10 rounded-xl",emptyText:d("modelPickerNoOptions"),createText:d("modelPickerUseCustom")},w)]}),e.jsx("p",{className:"text-xs text-gray-400",children:G}),e.jsx("p",{className:"text-xs text-gray-500",children:d("modelInputCustomHint")}),e.jsxs("a",{href:`${he}/guide/model-selection`,className:"inline-flex items-center gap-1.5 text-xs text-primary hover:text-primary-hover transition-colors",children:[e.jsx(Z,{className:"h-3.5 w-3.5"}),d("channelsGuideTitle")]})]})]}),e.jsxs("div",{className:"p-8 rounded-2xl bg-white border border-gray-200 shadow-card",children:[e.jsxs("div",{className:"flex items-center gap-4 mb-8",children:[e.jsx("div",{className:"h-10 w-10 rounded-xl bg-primary flex items-center justify-center text-white",children:e.jsx(H,{className:"h-5 w-5"})}),e.jsx("h3",{className:"text-lg font-bold text-gray-900",children:d("workspace")})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx($,{htmlFor:"workspace",className:"text-xs font-semibold text-gray-500 uppercase tracking-wider",children:(f==null?void 0:f.label)??"Default Path"}),e.jsx(q,{id:"workspace",value:S,onChange:s=>k(s.target.value),placeholder:(f==null?void 0:f.placeholder)??"/path/to/workspace",className:"rounded-xl"})]})]})]}),e.jsxs("div",{className:"p-8 rounded-2xl bg-white border border-gray-200 shadow-card",children:[e.jsxs("div",{className:"flex items-center gap-4 mb-10",children:[e.jsx("div",{className:"h-10 w-10 rounded-xl bg-primary flex items-center justify-center text-white",children:e.jsx(ee,{className:"h-5 w-5"})}),e.jsx("h3",{className:"text-lg font-bold text-gray-900",children:d("generationParameters")})]}),e.jsx("div",{className:"grid grid-cols-1 gap-12",children:e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{className:"flex justify-between items-center mb-2",children:[e.jsx($,{className:"text-xs font-semibold text-gray-500 uppercase tracking-wider",children:(P==null?void 0:P.label)??d("maxTokens")}),e.jsx("span",{className:"text-sm font-semibold text-gray-900",children:fe(v)})]}),e.jsx("input",{type:"range",min:"1000",max:"32000",step:"1000",value:v,onChange:s=>h(parseInt(s.target.value)),className:"w-full h-1 bg-gray-200 rounded-full appearance-none cursor-pointer accent-primary"})]})})]}),e.jsx("div",{className:"flex justify-end pt-4",children:e.jsx(re,{type:"submit",disabled:m.isPending,size:"lg",children:m.isPending?e.jsx(se,{className:"h-5 w-5 animate-spin"}):d("saveChanges")})})]})]})}export{ke as ModelConfig};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as p,j as e,aa as he,ab as pe,a8 as fe,a6 as te,K as ae,a1 as ye,ac as ee,m as ge,ad as je,ae as be,af as we,ag as V,a0 as Ne}from"./vendor-DN_iJQc4.js";import{I as B,u as re,a as ne,b as le,g as ve,h as Ae}from"./useConfig-VP_0ZVm1.js";import{B as L,P as Ce,a as Pe}from"./page-layout-BRgOXrOh.js";import{L as F}from"./label-BDeY_kN8.js";import{t as a,c as H,S as Se,a as Ee,b as Le,d as Me,e as Te}from"./index-BeI_Pucj.js";import{C as Ke,a as Be,S as ie,b as De,c as $e,L as ke,g as Ie}from"./logos-Qxa07LFc.js";import{h as _}from"./config-hints-CApS3K_7.js";import{T as Fe}from"./tabs-custom-B8x9xWoI.js";function _e({maskedValue:s,isSet:t,className:n,...i}){const[x,g]=p.useState(!1);return e.jsxs("div",{className:"relative",children:[e.jsx(B,{type:x?"text":"password",className:H("pr-20",n),placeholder:t?`${a("apiKeySet")} (${a("unchanged")})`:"",...i}),e.jsx("div",{className:"absolute right-2 top-1/2 -translate-y-1/2 flex gap-1",children:(t||s)&&e.jsx(L,{type:"button",variant:"ghost",size:"icon",className:"h-7 w-7",onClick:()=>g(!x),children:x?e.jsx(he,{className:"h-4 w-4"}):e.jsx(pe,{className:"h-4 w-4"})})})]})}function Oe({value:s,onChange:t,className:n}){const i=s?Object.entries(s):[],x=(m,f,j)=>{const y=[...i];y[m]=[f,j],t(Object.fromEntries(y))},g=()=>{t({...s,"":""})},h=m=>{const f=i.filter((j,y)=>y!==m);t(Object.fromEntries(f))};return e.jsxs("div",{className:H("space-y-2",n),children:[i.map(([m,f],j)=>e.jsxs("div",{className:"flex gap-2",children:[e.jsx(B,{type:"text",value:m,onChange:y=>x(j,y.target.value,f),placeholder:a("headerName"),className:"flex-1"}),e.jsx(B,{type:"text",value:f,onChange:y=>x(j,m,y.target.value),placeholder:a("headerValue"),className:"flex-1"}),e.jsx(L,{type:"button",variant:"ghost",size:"icon",onClick:()=>h(j),children:e.jsx(fe,{className:"h-4 w-4 text-red-500"})})]},j)),e.jsxs(L,{type:"button",variant:"outline",size:"sm",onClick:g,children:[e.jsx(te,{className:"h-4 w-4 mr-2"}),a("add")]})]})}function O(s){if(!s)return null;const t=Object.entries(s).map(([n,i])=>[n.trim(),i]).filter(([n])=>n.length>0);return t.length===0?null:Object.fromEntries(t)}function se(s,t){const n=O(s),i=O(t);if(n===null&&i===null)return!0;if(!n||!i)return!1;const x=Object.entries(n).sort(([h],[m])=>h.localeCompare(m)),g=Object.entries(i).sort(([h],[m])=>h.localeCompare(m));return x.length!==g.length?!1:x.every(([h,m],f)=>h===g[f][0]&&m===g[f][1])}function q(s){if(!s||s.length===0)return[];const t=new Set;for(const n of s){const i=n.trim();i&&t.add(i)}return[...t]}function Re(s,t){const n=s.trim();if(!n||!t.trim())return n;const i=`${t.trim()}/`;return n.startsWith(i)?n.slice(i.length):n}function U(s,t){let n=s.trim();if(!n)return"";for(const i of t){const x=i.trim();x&&(n=Re(n,x))}return n.trim()}function Q(s,t){return s.length!==t.length?!1:s.every((n,i)=>n===t[i])}function We(s,t){const n=[...s];for(const i of t)n.includes(i)||n.push(i);return n}function ze(s,t){return t.length===0?s:t.every(i=>!s.includes(i))?We(s,t):t}function He(s,t){return Q(s,t)?[]:s}function Ge({providerName:s}){const{data:t}=re(),{data:n}=ne(),{data:i}=le(),x=ve(),g=Ae(),[h,m]=p.useState(""),[f,j]=p.useState(""),[y,M]=p.useState(null),[A,T]=p.useState("auto"),[b,C]=p.useState([]),[d,u]=p.useState(""),r=n==null?void 0:n.providers.find(l=>l.name===s),c=s?t==null?void 0:t.providers[s]:null,w=i==null?void 0:i.uiHints,P=s?_(`providers.${s}.apiKey`,w):void 0,v=s?_(`providers.${s}.apiBase`,w):void 0,S=s?_(`providers.${s}.extraHeaders`,w):void 0,E=s?_(`providers.${s}.wireApi`,w):void 0,ce=(r==null?void 0:r.displayName)||s||a("providersSelectPlaceholder"),X=(r==null?void 0:r.modelPrefix)||s||"",D=p.useMemo(()=>q([X,s||""]),[X,s]),R=(r==null?void 0:r.defaultApiBase)||"",$=(c==null?void 0:c.apiBase)||R,G=O((c==null?void 0:c.extraHeaders)||null),k=(c==null?void 0:c.wireApi)||(r==null?void 0:r.defaultWireApi)||"auto",W=p.useMemo(()=>q(((r==null?void 0:r.defaultModels)??[]).map(l=>U(l,D))),[r==null?void 0:r.defaultModels,D]),Y=p.useMemo(()=>q(((c==null?void 0:c.models)??[]).map(l=>U(l,D))),[c==null?void 0:c.models,D]),I=p.useMemo(()=>ze(W,Y),[W,Y]),oe=s==="minimax"?a("providerApiBaseHelpMinimax"):(v==null?void 0:v.help)||a("providerApiBaseHelp");p.useEffect(()=>{if(!s){m(""),j(""),M(null),T("auto"),C([]),u("");return}m(""),j($),M((c==null?void 0:c.extraHeaders)||null),T(k),C(I),u("")},[s,$,c==null?void 0:c.extraHeaders,k,I]);const J=p.useMemo(()=>{if(!s)return!1;const l=h.trim().length>0,o=f.trim()!==$.trim(),N=!se(y,G),K=r!=null&&r.supportsWireApi?A!==k:!1,z=!Q(b,I);return l||o||N||K||z},[s,h,f,$,y,G,r==null?void 0:r.supportsWireApi,A,k,b,I]),de=()=>{m(""),j(R),M(null),T((r==null?void 0:r.defaultWireApi)||"auto"),C(W),u("")},Z=()=>{const l=U(d,D);if(l){if(b.includes(l)){u("");return}C(o=>[...o,l]),u("")}},me=l=>{if(l.preventDefault(),!s)return;const o={},N=h.trim(),K=f.trim(),z=O(y);N.length>0&&(o.apiKey=N),K!==$.trim()&&(o.apiBase=K.length>0&&K!==R?K:null),se(z,G)||(o.extraHeaders=z),r!=null&&r.supportsWireApi&&A!==k&&(o.wireApi=A),Q(b,I)||(o.models=He(b,W)),x.mutate({provider:s,data:o})},ue=async()=>{if(!s)return;const l={apiBase:f.trim(),extraHeaders:O(y),model:(t==null?void 0:t.agents.defaults.model)??null};h.trim().length>0&&(l.apiKey=h.trim()),r!=null&&r.supportsWireApi&&(l.wireApi=A);try{const o=await g.mutateAsync({provider:s,data:l});if(o.success){V.success(`${a("providerTestConnectionSuccess")} (${o.latencyMs}ms)`);return}const N=[`provider=${o.provider}`,`latency=${o.latencyMs}ms`];o.model&&N.push(`model=${o.model}`),V.error(`${a("providerTestConnectionFailed")}: ${o.message} | ${N.join(" | ")}`)}catch(o){const N=o instanceof Error?o.message:String(o);V.error(`${a("providerTestConnectionFailed")}: ${N}`)}};if(!s||!r||!c)return e.jsx("div",{className:Ke,children:e.jsxs("div",{children:[e.jsx("h3",{className:"text-base font-semibold text-gray-900",children:a("providersSelectTitle")}),e.jsx("p",{className:"mt-2 text-sm text-gray-500",children:a("providersSelectDescription")})]})});const xe=c.apiKeySet?a("statusReady"):a("statusSetup");return e.jsxs("div",{className:Be,children:[e.jsx("div",{className:"border-b border-gray-100 px-6 py-5",children:e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("h3",{className:"truncate text-lg font-semibold text-gray-900",children:ce}),e.jsx("p",{className:"mt-1 text-sm text-gray-500",children:a("providerFormDescription")})]}),e.jsx(ie,{status:c.apiKeySet?"ready":"setup",label:xe})]})}),e.jsxs("form",{onSubmit:me,className:"flex min-h-0 flex-1 flex-col",children:[e.jsxs("div",{className:"min-h-0 flex-1 space-y-6 overflow-y-auto px-6 py-5",children:[e.jsxs("div",{className:"space-y-2.5",children:[e.jsxs(F,{htmlFor:"apiKey",className:"flex items-center gap-2 text-sm font-medium text-gray-900",children:[e.jsx(ae,{className:"h-3.5 w-3.5 text-gray-500"}),(P==null?void 0:P.label)??a("apiKey")]}),e.jsx(_e,{id:"apiKey",value:h,isSet:c.apiKeySet,onChange:l=>m(l.target.value),placeholder:c.apiKeySet?a("apiKeySet"):(P==null?void 0:P.placeholder)??a("enterApiKey"),className:"rounded-xl"}),e.jsx("p",{className:"text-xs text-gray-500",children:a("leaveBlankToKeepUnchanged")})]}),e.jsxs("div",{className:"space-y-2.5",children:[e.jsxs(F,{htmlFor:"apiBase",className:"flex items-center gap-2 text-sm font-medium text-gray-900",children:[e.jsx(ye,{className:"h-3.5 w-3.5 text-gray-500"}),(v==null?void 0:v.label)??a("apiBase")]}),e.jsx(B,{id:"apiBase",type:"text",value:f,onChange:l=>j(l.target.value),placeholder:R||(v==null?void 0:v.placeholder)||"https://api.example.com",className:"rounded-xl"}),e.jsx("p",{className:"text-xs text-gray-500",children:oe})]}),r.supportsWireApi&&e.jsxs("div",{className:"space-y-2.5",children:[e.jsxs(F,{htmlFor:"wireApi",className:"flex items-center gap-2 text-sm font-medium text-gray-900",children:[e.jsx(ee,{className:"h-3.5 w-3.5 text-gray-500"}),(E==null?void 0:E.label)??a("wireApi")]}),e.jsxs(Se,{value:A,onValueChange:l=>T(l),children:[e.jsx(Ee,{className:"rounded-xl",children:e.jsx(Le,{})}),e.jsx(Me,{children:(r.wireApiOptions||["auto","chat","responses"]).map(l=>e.jsx(Te,{value:l,children:l==="chat"?a("wireApiChat"):l==="responses"?a("wireApiResponses"):a("wireApiAuto")},l))})]}),(E==null?void 0:E.help)&&e.jsx("p",{className:"text-xs text-gray-500",children:E.help})]}),e.jsxs("div",{className:"space-y-2.5",children:[e.jsxs(F,{className:"flex items-center gap-2 text-sm font-medium text-gray-900",children:[e.jsx(ee,{className:"h-3.5 w-3.5 text-gray-500"}),(S==null?void 0:S.label)??a("extraHeaders")]}),e.jsx(Oe,{value:y,onChange:M}),e.jsx("p",{className:"text-xs text-gray-500",children:(S==null?void 0:S.help)||a("providerExtraHeadersHelp")})]}),e.jsxs("div",{className:"space-y-2.5",children:[e.jsxs(F,{className:"flex items-center gap-2 text-sm font-medium text-gray-900",children:[e.jsx(ge,{className:"h-3.5 w-3.5 text-gray-500"}),a("providerModelsTitle")]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(B,{value:d,onChange:l=>u(l.target.value),onKeyDown:l=>{l.key==="Enter"&&(l.preventDefault(),Z())},placeholder:a("providerModelInputPlaceholder"),className:"flex-1"}),e.jsxs(L,{type:"button",variant:"outline",onClick:Z,disabled:d.trim().length===0,children:[e.jsx(te,{className:"mr-1.5 h-4 w-4"}),a("providerAddModel")]})]}),e.jsx("p",{className:"text-xs text-gray-500",children:a("providerModelInputHint")}),b.length===0?e.jsx("div",{className:"rounded-xl border border-dashed border-gray-200 bg-gray-50 px-3 py-2 text-xs text-gray-500",children:a("providerModelsEmpty")}):e.jsx("div",{className:"flex flex-wrap gap-2",children:b.map(l=>e.jsxs("div",{className:"group inline-flex max-w-full items-center gap-1 rounded-full border border-gray-200 bg-white px-3 py-1.5",children:[e.jsx("span",{className:"max-w-[180px] truncate text-sm text-gray-800 sm:max-w-[240px]",children:l}),e.jsx("button",{type:"button",onClick:()=>C(o=>o.filter(N=>N!==l)),className:"inline-flex h-5 w-5 items-center justify-center rounded-full text-gray-400 transition-opacity hover:bg-gray-100 hover:text-gray-600 opacity-100 md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100","aria-label":a("remove"),children:e.jsx(je,{className:"h-3 w-3"})})]},l))}),e.jsx("p",{className:"text-xs text-gray-500",children:a("providerModelsHelp")})]})]}),e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3 border-t border-gray-100 px-6 py-4",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsxs(L,{type:"button",variant:"outline",onClick:de,children:[e.jsx(be,{className:"mr-2 h-4 w-4"}),a("resetToDefault")]}),e.jsxs(L,{type:"button",variant:"outline",onClick:ue,disabled:g.isPending,children:[e.jsx(we,{className:"mr-2 h-4 w-4"}),g.isPending?a("providerTestingConnection"):a("providerTestConnection")]})]}),e.jsx(L,{type:"submit",disabled:x.isPending||!J,children:x.isPending?a("saving"):J?a("save"):a("unchanged")})]})]})]})}function Ve(s){if(!s)return null;try{const t=new URL(s),n=t.pathname&&t.pathname!=="/"?t.pathname:"";return`${t.host}${n}`}catch{return s.replace(/^https?:\/\//,"")}}function ss(){const{data:s}=re(),{data:t}=ne(),{data:n}=le(),[i,x]=p.useState("installed"),[g,h]=p.useState(),[m,f]=p.useState(""),j=n==null?void 0:n.uiHints,y=(t==null?void 0:t.providers)??[],M=(s==null?void 0:s.providers)??{},A=y.filter(d=>{var u;return(u=M[d.name])==null?void 0:u.apiKeySet}).length,T=[{id:"installed",label:a("providersTabConfigured"),count:A},{id:"all",label:a("providersTabAll"),count:y.length}],b=p.useMemo(()=>{const d=(t==null?void 0:t.providers)??[],u=(s==null?void 0:s.providers)??{},r=m.trim().toLowerCase();return d.filter(c=>{var w;return i==="installed"?!!((w=u[c.name])!=null&&w.apiKeySet):!0}).filter(c=>r?(c.displayName||c.name).toLowerCase().includes(r)||c.name.toLowerCase().includes(r):!0)},[t,s,i,m]);p.useEffect(()=>{if(b.length===0){h(void 0);return}b.some(u=>u.name===g)||h(b[0].name)},[b,g]);const C=g;return!s||!t?e.jsx("div",{className:"p-8",children:a("providersLoading")}):e.jsxs(Ce,{children:[e.jsx(Pe,{title:a("providersPageTitle"),description:a("providersPageDescription")}),e.jsxs("div",{className:De,children:[e.jsxs("section",{className:$e,children:[e.jsx("div",{className:"border-b border-gray-100 px-4 pt-4",children:e.jsx(Fe,{tabs:T,activeTab:i,onChange:x,className:"mb-0"})}),e.jsx("div",{className:"border-b border-gray-100 px-4 py-3",children:e.jsxs("div",{className:"relative",children:[e.jsx(Ne,{className:"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400"}),e.jsx(B,{value:m,onChange:d=>f(d.target.value),placeholder:a("providersFilterPlaceholder"),className:"h-10 rounded-xl pl-9"})]})}),e.jsxs("div",{className:"min-h-0 flex-1 space-y-2 overflow-y-auto p-3",children:[b.map(d=>{const u=s.providers[d.name],r=!!(u!=null&&u.apiKeySet),c=C===d.name,w=_(`providers.${d.name}`,j),P=(u==null?void 0:u.apiBase)||d.defaultApiBase||"",S=Ve(P)||(w==null?void 0:w.help)||a("providersDefaultDescription");return e.jsx("button",{type:"button",onClick:()=>h(d.name),className:H("w-full rounded-xl border p-2.5 text-left transition-all",c?"border-primary/30 bg-primary-50/40 shadow-sm":"border-gray-200/70 bg-white hover:border-gray-300 hover:bg-gray-50/70"),children:e.jsxs("div",{className:"flex items-start justify-between gap-3",children:[e.jsxs("div",{className:"flex min-w-0 items-center gap-3",children:[e.jsx(ke,{name:d.name,src:Ie(d.name),className:H("h-10 w-10 rounded-lg border",r?"border-primary/30 bg-white":"border-gray-200/70 bg-white"),imgClassName:"h-5 w-5 object-contain",fallback:e.jsx("span",{className:"text-sm font-semibold uppercase text-gray-500",children:d.name[0]})}),e.jsxs("div",{className:"min-w-0",children:[e.jsx("p",{className:"truncate text-sm font-semibold text-gray-900",children:d.displayName||d.name}),e.jsx("p",{className:"line-clamp-1 text-[11px] text-gray-500",children:S})]})]}),e.jsx(ie,{status:r?"ready":"setup",label:r?a("statusReady"):a("statusSetup"),className:"min-w-[56px] justify-center"})]})},d.name)}),b.length===0&&e.jsxs("div",{className:"flex h-full min-h-[220px] flex-col items-center justify-center rounded-xl border border-dashed border-gray-200 bg-gray-50/70 py-10 text-center",children:[e.jsx("div",{className:"mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-white",children:e.jsx(ae,{className:"h-5 w-5 text-gray-300"})}),e.jsx("p",{className:"text-sm font-medium text-gray-700",children:a("providersNoMatch")})]})]})]}),e.jsx(Ge,{providerName:C})]})]})}export{ss as ProvidersList};
|