@nextclaw/ui 0.5.38 → 0.5.40
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-3B_zyiKA.js → ChannelsList-B30C2Tc1.js} +1 -1
- package/dist/assets/{ChatPage-DusH09PT.js → ChatPage-avQKMYYz.js} +1 -1
- package/dist/assets/{CronConfig-5GTz5wPt.js → CronConfig-BMlXWhYq.js} +1 -1
- package/dist/assets/{DocBrowser-BtqGmg0N.js → DocBrowser-DJvqOv4P.js} +1 -1
- package/dist/assets/{MarketplacePage-BEW4M9BT.js → MarketplacePage-ClpeHXw_.js} +1 -1
- package/dist/assets/ModelConfig-C7goS4WN.js +1 -0
- package/dist/assets/ProvidersList-D-PD0V-a.js +1 -0
- package/dist/assets/{RuntimeConfig-BwTxGi_U.js → RuntimeConfig-CIv4GGnC.js} +1 -1
- package/dist/assets/{SecretsConfig-x36MY4ym.js → SecretsConfig-4_Q0C2xp.js} +2 -2
- package/dist/assets/{SessionsConfig-qEffYDZ0.js → SessionsConfig-CtSAiFfb.js} +1 -1
- package/dist/assets/{card-Bq6uwDJQ.js → card-sNuXU_r7.js} +1 -1
- package/dist/assets/index-C1zSMeji.js +2 -0
- package/dist/assets/{label-Cq1vSfWg.js → label-D4Ubt_vJ.js} +1 -1
- package/dist/assets/{logos-BKBMs40Q.js → logos-DKUohpuv.js} +1 -1
- package/dist/assets/{page-layout-D8MW2vP-.js → page-layout-BrRUG9hW.js} +1 -1
- package/dist/assets/{switch-CycMxy31.js → switch-B3olqa--.js} +1 -1
- package/dist/assets/{tabs-custom-N4olWJSw.js → tabs-custom-ClgZ4Lyg.js} +1 -1
- package/dist/assets/useConfig-DiBU1IBR.js +6 -0
- package/dist/assets/{useConfirmDialog-DE0Yp8Ai.js → useConfirmDialog-i-F39tiz.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 +87 -8
- package/src/components/config/ProvidersList.tsx +38 -6
- package/src/hooks/useConfig.ts +36 -0
- package/src/lib/i18n.ts +17 -0
- package/dist/assets/ModelConfig-CwxXYqME.js +0 -1
- package/dist/assets/ProvidersList-D4oaYHpJ.js +0 -1
- package/dist/assets/index-wB2uPrKu.js +0 -2
- package/dist/assets/useConfig-tR_KAfMV.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 || '']),
|
|
@@ -180,6 +194,9 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
180
194
|
() => resolveEditableModels(defaultModels, currentModels),
|
|
181
195
|
[defaultModels, currentModels]
|
|
182
196
|
);
|
|
197
|
+
const apiBaseHelpText = providerName === 'minimax'
|
|
198
|
+
? t('providerApiBaseHelpMinimax')
|
|
199
|
+
: (apiBaseHint?.help || t('providerApiBaseHelp'));
|
|
183
200
|
|
|
184
201
|
useEffect(() => {
|
|
185
202
|
if (!providerName) {
|
|
@@ -189,6 +206,7 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
189
206
|
setWireApi('auto');
|
|
190
207
|
setModels([]);
|
|
191
208
|
setModelDraft('');
|
|
209
|
+
setProviderDisplayName('');
|
|
192
210
|
return;
|
|
193
211
|
}
|
|
194
212
|
|
|
@@ -198,7 +216,8 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
198
216
|
setWireApi(currentWireApi);
|
|
199
217
|
setModels(currentEditableModels);
|
|
200
218
|
setModelDraft('');
|
|
201
|
-
|
|
219
|
+
setProviderDisplayName(effectiveDisplayName);
|
|
220
|
+
}, [providerName, currentApiBase, providerConfig?.extraHeaders, currentWireApi, currentEditableModels, effectiveDisplayName]);
|
|
202
221
|
|
|
203
222
|
const hasChanges = useMemo(() => {
|
|
204
223
|
if (!providerName) {
|
|
@@ -209,10 +228,16 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
209
228
|
const headersChanged = !headersEqual(extraHeaders, currentHeaders);
|
|
210
229
|
const wireApiChanged = providerSpec?.supportsWireApi ? wireApi !== currentWireApi : false;
|
|
211
230
|
const modelsChanged = !modelListsEqual(models, currentEditableModels);
|
|
231
|
+
const displayNameChanged = isCustomProvider
|
|
232
|
+
? providerDisplayName.trim() !== effectiveDisplayName
|
|
233
|
+
: false;
|
|
212
234
|
|
|
213
|
-
return apiKeyChanged || apiBaseChanged || headersChanged || wireApiChanged || modelsChanged;
|
|
235
|
+
return apiKeyChanged || apiBaseChanged || headersChanged || wireApiChanged || modelsChanged || displayNameChanged;
|
|
214
236
|
}, [
|
|
215
237
|
providerName,
|
|
238
|
+
isCustomProvider,
|
|
239
|
+
providerDisplayName,
|
|
240
|
+
effectiveDisplayName,
|
|
216
241
|
apiKey,
|
|
217
242
|
apiBase,
|
|
218
243
|
currentApiBase,
|
|
@@ -232,6 +257,7 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
232
257
|
setWireApi((providerSpec?.defaultWireApi || 'auto') as WireApiType);
|
|
233
258
|
setModels(defaultModels);
|
|
234
259
|
setModelDraft('');
|
|
260
|
+
setProviderDisplayName(defaultDisplayName);
|
|
235
261
|
};
|
|
236
262
|
|
|
237
263
|
const handleAddModel = () => {
|
|
@@ -258,6 +284,11 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
258
284
|
const trimmedApiKey = apiKey.trim();
|
|
259
285
|
const trimmedApiBase = apiBase.trim();
|
|
260
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
|
+
}
|
|
261
292
|
|
|
262
293
|
if (trimmedApiKey.length > 0) {
|
|
263
294
|
payload.apiKey = trimmedApiKey;
|
|
@@ -286,10 +317,12 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
286
317
|
return;
|
|
287
318
|
}
|
|
288
319
|
|
|
320
|
+
const preferredModel = models.find((modelName) => modelName.trim().length > 0) ?? '';
|
|
321
|
+
const testModel = toProviderLocalModelId(preferredModel, providerModelAliases);
|
|
289
322
|
const payload: ProviderConnectionTestRequest = {
|
|
290
323
|
apiBase: apiBase.trim(),
|
|
291
324
|
extraHeaders: normalizeHeaders(extraHeaders),
|
|
292
|
-
model:
|
|
325
|
+
model: testModel || null
|
|
293
326
|
};
|
|
294
327
|
if (apiKey.trim().length > 0) {
|
|
295
328
|
payload.apiKey = apiKey.trim();
|
|
@@ -318,6 +351,22 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
318
351
|
}
|
|
319
352
|
};
|
|
320
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
|
+
|
|
321
370
|
if (!providerName || !providerSpec || !providerConfig) {
|
|
322
371
|
return (
|
|
323
372
|
<div className={CONFIG_EMPTY_DETAIL_CARD_CLASS}>
|
|
@@ -345,6 +394,24 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
345
394
|
|
|
346
395
|
<form onSubmit={handleSubmit} className="flex min-h-0 flex-1 flex-col">
|
|
347
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
|
+
|
|
348
415
|
<div className="space-y-2.5">
|
|
349
416
|
<Label htmlFor="apiKey" className="flex items-center gap-2 text-sm font-medium text-gray-900">
|
|
350
417
|
<KeyRound className="h-3.5 w-3.5 text-gray-500" />
|
|
@@ -374,7 +441,8 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
374
441
|
placeholder={defaultApiBase || apiBaseHint?.placeholder || 'https://api.example.com'}
|
|
375
442
|
className="rounded-xl"
|
|
376
443
|
/>
|
|
377
|
-
<p className="text-xs text-gray-500">{
|
|
444
|
+
<p className="text-xs text-gray-500">{apiBaseHelpText}</p>
|
|
445
|
+
{isCustomProvider && <p className="text-xs text-gray-500">{t('providerOpenAICompatHint')}</p>}
|
|
378
446
|
</div>
|
|
379
447
|
|
|
380
448
|
{providerSpec.supportsWireApi && (
|
|
@@ -468,6 +536,17 @@ export function ProviderForm({ providerName }: ProviderFormProps) {
|
|
|
468
536
|
<RotateCcw className="mr-2 h-4 w-4" />
|
|
469
537
|
{t('resetToDefault')}
|
|
470
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
|
+
)}
|
|
471
550
|
<Button type="button" variant="outline" onClick={handleTestConnection} disabled={testProviderConnection.isPending}>
|
|
472
551
|
<CircleDotDashed className="mr-2 h-4 w-4" />
|
|
473
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
|
);
|
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,8 +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.' },
|
|
216
|
+
providerApiBaseHelpMinimax: {
|
|
217
|
+
zh: 'MiniMax 中国区请使用 https://api.minimaxi.com/v1;海外请使用 https://api.minimax.io/v1。',
|
|
218
|
+
en: 'Use https://api.minimaxi.com/v1 for Mainland China accounts, and https://api.minimax.io/v1 for overseas accounts.'
|
|
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
|
+
},
|
|
207
224
|
providerExtraHeadersHelp: { zh: '用于自定义请求头(可选)。', en: 'Optional custom request headers.' },
|
|
208
225
|
providerTestConnection: { zh: '测试连接', en: 'Test Connection' },
|
|
209
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-D8MW2vP-.js";import{C as _}from"./card-Bq6uwDJQ.js";import{I as q,u as ne,a as le,b as oe,c as ie}from"./useConfig-tR_KAfMV.js";import{L as $}from"./label-Cq1vSfWg.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-wB2uPrKu.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 xe,ab as he,a8 as pe,a6 as te,K as ae,a1 as fe,ac as ee,m as ye,ad as ge,ae as je,af as be,ag as V,a0 as we}from"./vendor-DN_iJQc4.js";import{I as D,u as re,a as ne,b as le,g as Ne,h as ve}from"./useConfig-tR_KAfMV.js";import{B as L,P as Ae,a as Ce}from"./page-layout-D8MW2vP-.js";import{L as F}from"./label-Cq1vSfWg.js";import{t as a,c as G,S as Pe,a as Se,b as Ee,d as Le,e as Me}from"./index-wB2uPrKu.js";import{C as Te,a as Ke,S as ie,b as De,c as Be,L as $e,g as ke}from"./logos-BKBMs40Q.js";import{h as _}from"./config-hints-CApS3K_7.js";import{T as Ie}from"./tabs-custom-N4olWJSw.js";function Fe({maskedValue:s,isSet:t,className:n,...i}){const[x,g]=p.useState(!1);return e.jsxs("div",{className:"relative",children:[e.jsx(D,{type:x?"text":"password",className:G("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(xe,{className:"h-4 w-4"}):e.jsx(he,{className:"h-4 w-4"})})})]})}function _e({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:G("space-y-2",n),children:[i.map(([m,f],j)=>e.jsxs("div",{className:"flex gap-2",children:[e.jsx(D,{type:"text",value:m,onChange:y=>x(j,y.target.value,f),placeholder:a("headerName"),className:"flex-1"}),e.jsx(D,{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(pe,{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 Oe(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=Oe(n,x))}return n.trim()}function Q(s,t){return s.length!==t.length?!1:s.every((n,i)=>n===t[i])}function Re(s,t){const n=[...s];for(const i of t)n.includes(i)||n.push(i);return n}function We(s,t){return t.length===0?s:t.every(i=>!s.includes(i))?Re(s,t):t}function ze(s,t){return Q(s,t)?[]:s}function Ge({providerName:s}){const{data:t}=re(),{data:n}=ne(),{data:i}=le(),x=Ne(),g=ve(),[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||"",B=p.useMemo(()=>q([X,s||""]),[X,s]),R=(r==null?void 0:r.defaultApiBase)||"",$=(c==null?void 0:c.apiBase)||R,H=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,B))),[r==null?void 0:r.defaultModels,B]),Y=p.useMemo(()=>q(((c==null?void 0:c.models)??[]).map(l=>U(l,B))),[c==null?void 0:c.models,B]),I=p.useMemo(()=>We(W,Y),[W,Y]);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,H),K=r!=null&&r.supportsWireApi?A!==k:!1,z=!Q(b,I);return l||o||N||K||z},[s,h,f,$,y,H,r==null?void 0:r.supportsWireApi,A,k,b,I]),oe=()=>{m(""),j(R),M(null),T((r==null?void 0:r.defaultWireApi)||"auto"),C(W),u("")},Z=()=>{const l=U(d,B);if(l){if(b.includes(l)){u("");return}C(o=>[...o,l]),u("")}},de=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,H)||(o.extraHeaders=z),r!=null&&r.supportsWireApi&&A!==k&&(o.wireApi=A),Q(b,I)||(o.models=ze(b,W)),x.mutate({provider:s,data:o})},me=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:Te,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 ue=c.apiKeySet?a("statusReady"):a("statusSetup");return e.jsxs("div",{className:Ke,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:ue})]})}),e.jsxs("form",{onSubmit:de,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(Fe,{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(fe,{className:"h-3.5 w-3.5 text-gray-500"}),(v==null?void 0:v.label)??a("apiBase")]}),e.jsx(D,{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:(v==null?void 0:v.help)||a("providerApiBaseHelp")})]}),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(Pe,{value:A,onValueChange:l=>T(l),children:[e.jsx(Se,{className:"rounded-xl",children:e.jsx(Ee,{})}),e.jsx(Le,{children:(r.wireApiOptions||["auto","chat","responses"]).map(l=>e.jsx(Me,{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(_e,{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(ye,{className:"h-3.5 w-3.5 text-gray-500"}),a("providerModelsTitle")]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(D,{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(ge,{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:oe,children:[e.jsx(je,{className:"mr-2 h-4 w-4"}),a("resetToDefault")]}),e.jsxs(L,{type:"button",variant:"outline",onClick:me,disabled:g.isPending,children:[e.jsx(be,{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 He(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 es(){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(Ae,{children:[e.jsx(Ce,{title:a("providersPageTitle"),description:a("providersPageDescription")}),e.jsxs("div",{className:De,children:[e.jsxs("section",{className:Be,children:[e.jsx("div",{className:"border-b border-gray-100 px-4 pt-4",children:e.jsx(Ie,{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(we,{className:"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400"}),e.jsx(D,{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=He(P)||(w==null?void 0:w.help)||a("providersDefaultDescription");return e.jsx("button",{type:"button",onClick:()=>h(d.name),className:G("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($e,{name:d.name,src:ke(d.name),className:G("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{es as ProvidersList};
|