@nextclaw/ui 0.5.41 → 0.5.43
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 +16 -0
- package/dist/assets/{ChannelsList-C2Qd0t9b.js → ChannelsList-DhDnNnNj.js} +1 -1
- package/dist/assets/{ChatPage-BqlsPWdy.js → ChatPage-BDOAFhwZ.js} +12 -12
- package/dist/assets/{CronConfig-CdvpKuSy.js → CronConfig-BZ5BLWnK.js} +1 -1
- package/dist/assets/{DocBrowser-Bgmeukiz.js → DocBrowser-Cx6JqAqa.js} +1 -1
- package/dist/assets/{MarketplacePage-t3Sib5LO.js → MarketplacePage-DSeYw-SZ.js} +1 -1
- package/dist/assets/{ModelConfig-DZUg-4N3.js → ModelConfig-CG7bFj_f.js} +1 -1
- package/dist/assets/ProvidersList-Dk95S5TU.js +1 -0
- package/dist/assets/{RuntimeConfig-Dv-06ffH.js → RuntimeConfig-Bc8HKXWd.js} +1 -1
- package/dist/assets/{SecretsConfig-Djc-_j3o.js → SecretsConfig-DV_eZgHa.js} +1 -1
- package/dist/assets/{SessionsConfig-BY8rxQ7m.js → SessionsConfig-V9wA7zfR.js} +1 -1
- package/dist/assets/{card-ITZ1xl10.js → card-AxFsy7YF.js} +1 -1
- package/dist/assets/index-CT15pXtn.js +2 -0
- package/dist/assets/index-c0vY8dDm.css +1 -0
- package/dist/assets/{label-BowGMNYa.js → label-Chdg0Pzh.js} +1 -1
- package/dist/assets/{logos-BlD3kGKz.js → logos-csBmIIFU.js} +1 -1
- package/dist/assets/{page-layout-C62p_dlG.js → page-layout-COKsFqhi.js} +1 -1
- package/dist/assets/{switch-CpKwqv79.js → switch-BVQzjvsB.js} +1 -1
- package/dist/assets/{tabs-custom-D1fYWpL_.js → tabs-custom-BYQWSGw3.js} +1 -1
- package/dist/assets/{useConfig-0trWsjx9.js → useConfig-C9yQu_4f.js} +1 -1
- package/dist/assets/{useConfirmDialog-B_7sVz-j.js → useConfirmDialog-8zI-gypg.js} +1 -1
- package/dist/assets/{vendor-DN_iJQc4.js → vendor-CHxMoJ93.js} +26 -26
- package/dist/index.html +3 -3
- package/package.json +1 -1
- package/src/components/chat/ChatPage.tsx +27 -2
- package/src/components/common/MaskedInput.tsx +34 -9
- package/src/components/config/ProviderForm.tsx +132 -98
- package/src/components/ui/input.tsx +1 -1
- package/src/lib/i18n.ts +6 -0
- package/dist/assets/ProvidersList-BvtEBIdN.js +0 -1
- package/dist/assets/index-C8UPX5Q7.js +0 -2
- package/dist/assets/index-DMEuanmd.css +0 -1
package/dist/index.html
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
8
|
<title>NextClaw - 系统配置</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-CT15pXtn.js"></script>
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-CHxMoJ93.js">
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-c0vY8dDm.css">
|
|
12
12
|
</head>
|
|
13
13
|
|
|
14
14
|
<body>
|
package/package.json
CHANGED
|
@@ -104,6 +104,7 @@ export function ChatPage() {
|
|
|
104
104
|
|
|
105
105
|
const { confirm, ConfirmDialog } = useConfirmDialog();
|
|
106
106
|
const threadRef = useRef<HTMLDivElement | null>(null);
|
|
107
|
+
const isUserScrollingRef = useRef(false);
|
|
107
108
|
const streamRunIdRef = useRef(0);
|
|
108
109
|
const queueIdRef = useRef(0);
|
|
109
110
|
const selectedSessionKeyRef = useRef<string | null>(selectedSessionKey);
|
|
@@ -200,15 +201,39 @@ export function ChatPage() {
|
|
|
200
201
|
|
|
201
202
|
useEffect(() => {
|
|
202
203
|
selectedSessionKeyRef.current = selectedSessionKey;
|
|
204
|
+
// Reset scroll state when switching sessions
|
|
205
|
+
isUserScrollingRef.current = false;
|
|
203
206
|
}, [selectedSessionKey]);
|
|
204
207
|
|
|
208
|
+
// Check if user is near bottom (within 50px)
|
|
209
|
+
const isNearBottom = useCallback(() => {
|
|
210
|
+
const element = threadRef.current;
|
|
211
|
+
if (!element) return true;
|
|
212
|
+
const threshold = 50;
|
|
213
|
+
return element.scrollHeight - element.scrollTop - element.clientHeight < threshold;
|
|
214
|
+
}, []);
|
|
215
|
+
|
|
216
|
+
// Handle scroll events to detect user scrolling up
|
|
217
|
+
const handleScroll = useCallback(() => {
|
|
218
|
+
if (isNearBottom()) {
|
|
219
|
+
isUserScrollingRef.current = false;
|
|
220
|
+
} else {
|
|
221
|
+
isUserScrollingRef.current = true;
|
|
222
|
+
}
|
|
223
|
+
}, [isNearBottom]);
|
|
224
|
+
|
|
225
|
+
// Auto-scroll to bottom only if user hasn't scrolled up
|
|
205
226
|
useEffect(() => {
|
|
206
227
|
const element = threadRef.current;
|
|
207
228
|
if (!element) {
|
|
208
229
|
return;
|
|
209
230
|
}
|
|
231
|
+
// Don't auto-scroll if user has scrolled up
|
|
232
|
+
if (isUserScrollingRef.current) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
210
235
|
element.scrollTop = element.scrollHeight;
|
|
211
|
-
}, [mergedEvents, isSending
|
|
236
|
+
}, [mergedEvents, isSending]);
|
|
212
237
|
|
|
213
238
|
useEffect(() => {
|
|
214
239
|
return () => {
|
|
@@ -532,7 +557,7 @@ export function ChatPage() {
|
|
|
532
557
|
</div>
|
|
533
558
|
</div>
|
|
534
559
|
|
|
535
|
-
<div ref={threadRef} className="flex-1 min-h-0 overflow-y-auto custom-scrollbar px-5 py-5">
|
|
560
|
+
<div ref={threadRef} onScroll={handleScroll} className="flex-1 min-h-0 overflow-y-auto custom-scrollbar px-5 py-5">
|
|
536
561
|
{!selectedSessionKey ? (
|
|
537
562
|
<div className="h-full flex items-center justify-center">
|
|
538
563
|
<div className="text-center text-gray-500">
|
|
@@ -3,26 +3,51 @@ import { Input } from '@/components/ui/input';
|
|
|
3
3
|
import { Button } from '@/components/ui/button';
|
|
4
4
|
import { Eye, EyeOff } from 'lucide-react';
|
|
5
5
|
import { cn } from '@/lib/utils';
|
|
6
|
-
import { t } from '@/lib/i18n';
|
|
7
6
|
|
|
8
7
|
interface MaskedInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
9
8
|
maskedValue?: string;
|
|
10
9
|
isSet?: boolean;
|
|
11
10
|
}
|
|
12
11
|
|
|
13
|
-
export function MaskedInput({ maskedValue, isSet, className, ...props }: MaskedInputProps) {
|
|
12
|
+
export function MaskedInput({ maskedValue, isSet, className, value, onChange, placeholder, ...props }: MaskedInputProps) {
|
|
14
13
|
const [showKey, setShowKey] = useState(false);
|
|
14
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
15
|
+
const hasUserInput = typeof value === 'string' && value.length > 0;
|
|
16
|
+
|
|
17
|
+
// Determine what to display
|
|
18
|
+
const showMaskedDots = isSet && !hasUserInput && !isEditing;
|
|
15
19
|
|
|
16
20
|
return (
|
|
17
21
|
<div className="relative">
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
{showMaskedDots ? (
|
|
23
|
+
// Show masked state - clickable to edit
|
|
24
|
+
<div
|
|
25
|
+
onClick={() => setIsEditing(true)}
|
|
26
|
+
className={cn(
|
|
27
|
+
'flex h-9 w-full rounded-xl border border-gray-200/80 bg-white px-3.5 py-2 text-sm text-gray-500 cursor-text items-center pr-12',
|
|
28
|
+
className
|
|
29
|
+
)}
|
|
30
|
+
>
|
|
31
|
+
••••••••••••
|
|
32
|
+
</div>
|
|
33
|
+
) : (
|
|
34
|
+
<Input
|
|
35
|
+
type={showKey ? 'text' : 'password'}
|
|
36
|
+
className={cn('pr-12', className)}
|
|
37
|
+
value={value}
|
|
38
|
+
onChange={onChange}
|
|
39
|
+
onBlur={() => {
|
|
40
|
+
if (!hasUserInput) {
|
|
41
|
+
setIsEditing(false);
|
|
42
|
+
}
|
|
43
|
+
}}
|
|
44
|
+
placeholder={placeholder}
|
|
45
|
+
autoFocus={isEditing}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
)}
|
|
24
49
|
<div className="absolute right-2 top-1/2 -translate-y-1/2 flex gap-1">
|
|
25
|
-
{(isSet ||
|
|
50
|
+
{(isSet || hasUserInput) && (
|
|
26
51
|
<Button
|
|
27
52
|
type="button"
|
|
28
53
|
variant="ghost"
|
|
@@ -17,7 +17,7 @@ import { StatusDot } from '@/components/ui/status-dot';
|
|
|
17
17
|
import { t } from '@/lib/i18n';
|
|
18
18
|
import { hintForPath } from '@/lib/config-hints';
|
|
19
19
|
import type { ProviderConfigUpdate, ProviderConnectionTestRequest } from '@/api/types';
|
|
20
|
-
import {
|
|
20
|
+
import { CircleDotDashed, Plus, X, Trash2, ChevronDown, Settings2 } from 'lucide-react';
|
|
21
21
|
import { toast } from 'sonner';
|
|
22
22
|
import { CONFIG_DETAIL_CARD_CLASS, CONFIG_EMPTY_DETAIL_CARD_CLASS } from './config-layout';
|
|
23
23
|
|
|
@@ -152,6 +152,8 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
|
|
|
152
152
|
const [models, setModels] = useState<string[]>([]);
|
|
153
153
|
const [modelDraft, setModelDraft] = useState('');
|
|
154
154
|
const [providerDisplayName, setProviderDisplayName] = useState('');
|
|
155
|
+
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
156
|
+
const [showModelInput, setShowModelInput] = useState(false);
|
|
155
157
|
|
|
156
158
|
const providerSpec = meta?.providers.find((p) => p.name === providerName);
|
|
157
159
|
const providerConfig = providerName ? config?.providers[providerName] : null;
|
|
@@ -382,22 +384,31 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
|
|
|
382
384
|
|
|
383
385
|
return (
|
|
384
386
|
<div className={CONFIG_DETAIL_CARD_CLASS}>
|
|
385
|
-
<div className="border-b border-gray-100 px-6 py-
|
|
386
|
-
<div className="flex
|
|
387
|
-
<
|
|
388
|
-
|
|
389
|
-
|
|
387
|
+
<div className="border-b border-gray-100 px-6 py-4">
|
|
388
|
+
<div className="flex items-center justify-between">
|
|
389
|
+
<h3 className="truncate text-lg font-semibold text-gray-900">{providerTitle}</h3>
|
|
390
|
+
<div className="flex items-center gap-3">
|
|
391
|
+
{isCustomProvider && (
|
|
392
|
+
<button
|
|
393
|
+
type="button"
|
|
394
|
+
onClick={handleDeleteProvider}
|
|
395
|
+
disabled={deleteProvider.isPending}
|
|
396
|
+
className="text-gray-400 hover:text-red-500 transition-colors"
|
|
397
|
+
title={t('providerDelete')}
|
|
398
|
+
>
|
|
399
|
+
<Trash2 className="h-4 w-4" />
|
|
400
|
+
</button>
|
|
401
|
+
)}
|
|
402
|
+
<StatusDot status={providerConfig.apiKeySet ? 'ready' : 'setup'} label={statusLabel} />
|
|
390
403
|
</div>
|
|
391
|
-
<StatusDot status={providerConfig.apiKeySet ? 'ready' : 'setup'} label={statusLabel} />
|
|
392
404
|
</div>
|
|
393
405
|
</div>
|
|
394
406
|
|
|
395
407
|
<form onSubmit={handleSubmit} className="flex min-h-0 flex-1 flex-col">
|
|
396
|
-
<div className="min-h-0 flex-1 space-y-
|
|
408
|
+
<div className="min-h-0 flex-1 space-y-5 overflow-y-auto px-6 py-5">
|
|
397
409
|
{isCustomProvider && (
|
|
398
|
-
<div className="space-y-2
|
|
399
|
-
<Label htmlFor="providerDisplayName" className="
|
|
400
|
-
<Hash className="h-3.5 w-3.5 text-gray-500" />
|
|
410
|
+
<div className="space-y-2">
|
|
411
|
+
<Label htmlFor="providerDisplayName" className="text-sm font-medium text-gray-900">
|
|
401
412
|
{t('providerDisplayName')}
|
|
402
413
|
</Label>
|
|
403
414
|
<Input
|
|
@@ -408,13 +419,12 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
|
|
|
408
419
|
placeholder={defaultDisplayName || t('providerDisplayNamePlaceholder')}
|
|
409
420
|
className="rounded-xl"
|
|
410
421
|
/>
|
|
411
|
-
<p className="text-xs text-gray-500">{t('
|
|
422
|
+
<p className="text-xs text-gray-500">{t('providerDisplayNameHelpShort')}</p>
|
|
412
423
|
</div>
|
|
413
424
|
)}
|
|
414
425
|
|
|
415
|
-
<div className="space-y-2
|
|
416
|
-
<Label htmlFor="apiKey" className="
|
|
417
|
-
<KeyRound className="h-3.5 w-3.5 text-gray-500" />
|
|
426
|
+
<div className="space-y-2">
|
|
427
|
+
<Label htmlFor="apiKey" className="text-sm font-medium text-gray-900">
|
|
418
428
|
{apiKeyHint?.label ?? t('apiKey')}
|
|
419
429
|
</Label>
|
|
420
430
|
<MaskedInput
|
|
@@ -422,15 +432,14 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
|
|
|
422
432
|
value={apiKey}
|
|
423
433
|
isSet={providerConfig.apiKeySet}
|
|
424
434
|
onChange={(e) => setApiKey(e.target.value)}
|
|
425
|
-
placeholder={
|
|
435
|
+
placeholder={apiKeyHint?.placeholder ?? t('enterApiKey')}
|
|
426
436
|
className="rounded-xl"
|
|
427
437
|
/>
|
|
428
438
|
<p className="text-xs text-gray-500">{t('leaveBlankToKeepUnchanged')}</p>
|
|
429
439
|
</div>
|
|
430
440
|
|
|
431
|
-
<div className="space-y-2
|
|
432
|
-
<Label htmlFor="apiBase" className="
|
|
433
|
-
<Globe className="h-3.5 w-3.5 text-gray-500" />
|
|
441
|
+
<div className="space-y-2">
|
|
442
|
+
<Label htmlFor="apiBase" className="text-sm font-medium text-gray-900">
|
|
434
443
|
{apiBaseHint?.label ?? t('apiBase')}
|
|
435
444
|
</Label>
|
|
436
445
|
<Input
|
|
@@ -441,70 +450,66 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
|
|
|
441
450
|
placeholder={defaultApiBase || apiBaseHint?.placeholder || 'https://api.example.com'}
|
|
442
451
|
className="rounded-xl"
|
|
443
452
|
/>
|
|
444
|
-
<p className="text-xs text-gray-500">{
|
|
445
|
-
{isCustomProvider && <p className="text-xs text-gray-500">{t('providerOpenAICompatHint')}</p>}
|
|
453
|
+
<p className="text-xs text-gray-500">{t('providerApiBaseHelpShort')}</p>
|
|
446
454
|
</div>
|
|
447
455
|
|
|
448
|
-
|
|
449
|
-
<div className="
|
|
450
|
-
<Label
|
|
451
|
-
|
|
452
|
-
{wireApiHint?.label ?? t('wireApi')}
|
|
456
|
+
<div className="space-y-2">
|
|
457
|
+
<div className="flex items-center justify-between">
|
|
458
|
+
<Label className="text-sm font-medium text-gray-900">
|
|
459
|
+
{t('providerModelsTitle')}
|
|
453
460
|
</Label>
|
|
454
|
-
|
|
455
|
-
<
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
</SelectContent>
|
|
465
|
-
</Select>
|
|
466
|
-
{wireApiHint?.help && <p className="text-xs text-gray-500">{wireApiHint.help}</p>}
|
|
461
|
+
{!showModelInput && (
|
|
462
|
+
<button
|
|
463
|
+
type="button"
|
|
464
|
+
onClick={() => setShowModelInput(true)}
|
|
465
|
+
className="text-xs text-primary hover:text-primary/80 font-medium flex items-center gap-1"
|
|
466
|
+
>
|
|
467
|
+
<Plus className="h-3 w-3" />
|
|
468
|
+
{t('providerAddModel')}
|
|
469
|
+
</button>
|
|
470
|
+
)}
|
|
467
471
|
</div>
|
|
468
|
-
)}
|
|
469
472
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
/>
|
|
498
|
-
<Button type="button" variant="outline" onClick={handleAddModel} disabled={modelDraft.trim().length === 0}>
|
|
499
|
-
<Plus className="mr-1.5 h-4 w-4" />
|
|
500
|
-
{t('providerAddModel')}
|
|
501
|
-
</Button>
|
|
502
|
-
</div>
|
|
503
|
-
<p className="text-xs text-gray-500">{t('providerModelInputHint')}</p>
|
|
473
|
+
{showModelInput && (
|
|
474
|
+
<div className="flex items-center gap-2">
|
|
475
|
+
<Input
|
|
476
|
+
value={modelDraft}
|
|
477
|
+
onChange={(event) => setModelDraft(event.target.value)}
|
|
478
|
+
onKeyDown={(event) => {
|
|
479
|
+
if (event.key === 'Enter') {
|
|
480
|
+
event.preventDefault();
|
|
481
|
+
handleAddModel();
|
|
482
|
+
}
|
|
483
|
+
if (event.key === 'Escape') {
|
|
484
|
+
setShowModelInput(false);
|
|
485
|
+
setModelDraft('');
|
|
486
|
+
}
|
|
487
|
+
}}
|
|
488
|
+
placeholder={t('providerModelInputPlaceholder')}
|
|
489
|
+
className="flex-1 rounded-xl"
|
|
490
|
+
autoFocus
|
|
491
|
+
/>
|
|
492
|
+
<Button type="button" size="sm" onClick={handleAddModel} disabled={modelDraft.trim().length === 0}>
|
|
493
|
+
{t('add')}
|
|
494
|
+
</Button>
|
|
495
|
+
<Button type="button" size="sm" variant="ghost" onClick={() => { setShowModelInput(false); setModelDraft(''); }}>
|
|
496
|
+
<X className="h-4 w-4" />
|
|
497
|
+
</Button>
|
|
498
|
+
</div>
|
|
499
|
+
)}
|
|
504
500
|
|
|
505
501
|
{models.length === 0 ? (
|
|
506
|
-
<div className="rounded-xl border border-dashed border-gray-200 bg-gray-50 px-
|
|
507
|
-
{t('
|
|
502
|
+
<div className="rounded-xl border border-dashed border-gray-200 bg-gray-50 px-4 py-6 text-center">
|
|
503
|
+
<p className="text-sm text-gray-500">{t('providerModelsEmptyShort')}</p>
|
|
504
|
+
{!showModelInput && (
|
|
505
|
+
<button
|
|
506
|
+
type="button"
|
|
507
|
+
onClick={() => setShowModelInput(true)}
|
|
508
|
+
className="mt-2 text-sm text-primary hover:text-primary/80 font-medium"
|
|
509
|
+
>
|
|
510
|
+
{t('providerAddFirstModel')}
|
|
511
|
+
</button>
|
|
512
|
+
)}
|
|
508
513
|
</div>
|
|
509
514
|
) : (
|
|
510
515
|
<div className="flex flex-wrap gap-2">
|
|
@@ -526,32 +531,61 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
|
|
|
526
531
|
))}
|
|
527
532
|
</div>
|
|
528
533
|
)}
|
|
529
|
-
<p className="text-xs text-gray-500">{t('providerModelsHelp')}</p>
|
|
530
534
|
</div>
|
|
531
|
-
</div>
|
|
532
535
|
|
|
533
|
-
|
|
534
|
-
<div className="
|
|
535
|
-
<
|
|
536
|
-
|
|
537
|
-
{
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
<
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
536
|
+
{/* Advanced Settings - Collapsible */}
|
|
537
|
+
<div className="border-t border-gray-100 pt-4">
|
|
538
|
+
<button
|
|
539
|
+
type="button"
|
|
540
|
+
onClick={() => setShowAdvanced(!showAdvanced)}
|
|
541
|
+
className="flex w-full items-center justify-between text-sm text-gray-600 hover:text-gray-900 transition-colors"
|
|
542
|
+
>
|
|
543
|
+
<span className="flex items-center gap-1.5">
|
|
544
|
+
<Settings2 className="h-3.5 w-3.5" />
|
|
545
|
+
{t('providerAdvancedSettings')}
|
|
546
|
+
</span>
|
|
547
|
+
<ChevronDown className={`h-4 w-4 transition-transform ${showAdvanced ? 'rotate-180' : ''}`} />
|
|
548
|
+
</button>
|
|
549
|
+
|
|
550
|
+
{showAdvanced && (
|
|
551
|
+
<div className="mt-4 space-y-5">
|
|
552
|
+
{providerSpec.supportsWireApi && (
|
|
553
|
+
<div className="space-y-2">
|
|
554
|
+
<Label htmlFor="wireApi" className="text-sm font-medium text-gray-900">
|
|
555
|
+
{wireApiHint?.label ?? t('wireApi')}
|
|
556
|
+
</Label>
|
|
557
|
+
<Select value={wireApi} onValueChange={(v) => setWireApi(v as WireApiType)}>
|
|
558
|
+
<SelectTrigger className="rounded-xl">
|
|
559
|
+
<SelectValue />
|
|
560
|
+
</SelectTrigger>
|
|
561
|
+
<SelectContent>
|
|
562
|
+
{(providerSpec.wireApiOptions || ['auto', 'chat', 'responses']).map((option) => (
|
|
563
|
+
<SelectItem key={option} value={option}>
|
|
564
|
+
{option === 'chat' ? t('wireApiChat') : option === 'responses' ? t('wireApiResponses') : t('wireApiAuto')}
|
|
565
|
+
</SelectItem>
|
|
566
|
+
))}
|
|
567
|
+
</SelectContent>
|
|
568
|
+
</Select>
|
|
569
|
+
</div>
|
|
570
|
+
)}
|
|
571
|
+
|
|
572
|
+
<div className="space-y-2">
|
|
573
|
+
<Label className="text-sm font-medium text-gray-900">
|
|
574
|
+
{extraHeadersHint?.label ?? t('extraHeaders')}
|
|
575
|
+
</Label>
|
|
576
|
+
<KeyValueEditor value={extraHeaders} onChange={setExtraHeaders} />
|
|
577
|
+
<p className="text-xs text-gray-500">{t('providerExtraHeadersHelpShort')}</p>
|
|
578
|
+
</div>
|
|
579
|
+
</div>
|
|
549
580
|
)}
|
|
550
|
-
<Button type="button" variant="outline" onClick={handleTestConnection} disabled={testProviderConnection.isPending}>
|
|
551
|
-
<CircleDotDashed className="mr-2 h-4 w-4" />
|
|
552
|
-
{testProviderConnection.isPending ? t('providerTestingConnection') : t('providerTestConnection')}
|
|
553
|
-
</Button>
|
|
554
581
|
</div>
|
|
582
|
+
</div>
|
|
583
|
+
|
|
584
|
+
<div className="flex items-center justify-between border-t border-gray-100 px-6 py-4">
|
|
585
|
+
<Button type="button" variant="outline" size="sm" onClick={handleTestConnection} disabled={testProviderConnection.isPending}>
|
|
586
|
+
<CircleDotDashed className="mr-1.5 h-4 w-4" />
|
|
587
|
+
{testProviderConnection.isPending ? t('providerTestingConnection') : t('providerTestConnection')}
|
|
588
|
+
</Button>
|
|
555
589
|
<Button type="submit" disabled={updateProvider.isPending || !hasChanges}>
|
|
556
590
|
{updateProvider.isPending ? t('saving') : hasChanges ? t('save') : t('unchanged')}
|
|
557
591
|
</Button>
|
|
@@ -9,7 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
9
9
|
<input
|
|
10
10
|
type={type}
|
|
11
11
|
className={cn(
|
|
12
|
-
'flex h-9 w-full rounded-xl border border-gray-200/80 bg-white px-3.5 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-
|
|
12
|
+
'flex h-9 w-full rounded-xl border border-gray-200/80 bg-white px-3.5 py-2 text-sm text-gray-900 file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-300 placeholder:font-normal focus:outline-none focus:ring-1 focus:ring-primary/40 focus:border-primary/40 transition-colors disabled:cursor-not-allowed disabled:opacity-50',
|
|
13
13
|
className
|
|
14
14
|
)}
|
|
15
15
|
ref={ref}
|
package/src/lib/i18n.ts
CHANGED
|
@@ -243,6 +243,12 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
243
243
|
zh: '系统会先填充预置模型;你可以在这里新增或删除。请填写当前提供商自己的模型 ID(不带当前 provider 前缀);若输入带当前 provider 前缀会自动去除,但会保留后续路径(如 openai/gpt-5)。',
|
|
244
244
|
en: 'Built-in models are prefilled and can be added or removed here. Enter provider-local model ids without the current provider prefix; if prefixed input is entered, only the current provider prefix is removed while the remaining path is preserved (for example, openai/gpt-5).'
|
|
245
245
|
},
|
|
246
|
+
providerModelsEmptyShort: { zh: '暂无可用模型', en: 'No models available' },
|
|
247
|
+
providerAddFirstModel: { zh: '添加第一个模型', en: 'Add first model' },
|
|
248
|
+
providerDisplayNameHelpShort: { zh: '便于区分多个自定义提供商', en: 'Helps distinguish multiple custom providers' },
|
|
249
|
+
providerApiBaseHelpShort: { zh: '一般只需填写域名,系统自动补全路径', en: 'Usually just the domain; path auto-appended' },
|
|
250
|
+
providerExtraHeadersHelpShort: { zh: '可选,用于自定义鉴权等场景', en: 'Optional, for custom auth etc.' },
|
|
251
|
+
providerAdvancedSettings: { zh: '高级设置', en: 'Advanced Settings' },
|
|
246
252
|
resetToDefault: { zh: '恢复默认', en: 'Reset to Default' },
|
|
247
253
|
leaveBlankToKeepUnchanged: { zh: '留空则保持不变', en: 'Leave blank to keep unchanged' },
|
|
248
254
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as p,j as e,aa as Ae,ab as Ce,a8 as me,a6 as ne,ac as se,K as ue,a1 as Pe,m as De,ad as Le,ae as Me,af as Se,ag as te,a0 as Ee}from"./vendor-DN_iJQc4.js";import{I,u as xe,a as he,b as pe,g as Te,h as Be,i as Ke,j as $e}from"./useConfig-0trWsjx9.js";import{B as S,P as ke,a as Ie}from"./page-layout-C62p_dlG.js";import{L as W}from"./label-BowGMNYa.js";import{t,c as J,S as Fe,a as Oe,b as _e,d as Re,e as We}from"./index-C8UPX5Q7.js";import{C as He,a as ze,S as ye,b as Ge,c as Ve,L as qe,g as Ue}from"./logos-BlD3kGKz.js";import{h as q}from"./config-hints-CApS3K_7.js";import{T as Qe}from"./tabs-custom-D1fYWpL_.js";function Xe({maskedValue:s,isSet:a,className:n,...i}){const[u,b]=p.useState(!1);return e.jsxs("div",{className:"relative",children:[e.jsx(I,{type:u?"text":"password",className:J("pr-20",n),placeholder:a?`${t("apiKeySet")} (${t("unchanged")})`:"",...i}),e.jsx("div",{className:"absolute right-2 top-1/2 -translate-y-1/2 flex gap-1",children:(a||s)&&e.jsx(S,{type:"button",variant:"ghost",size:"icon",className:"h-7 w-7",onClick:()=>b(!u),children:u?e.jsx(Ae,{className:"h-4 w-4"}):e.jsx(Ce,{className:"h-4 w-4"})})})]})}function Ye({value:s,onChange:a,className:n}){const i=s?Object.entries(s):[],u=(m,x,g)=>{const f=[...i];f[m]=[x,g],a(Object.fromEntries(f))},b=()=>{a({...s,"":""})},y=m=>{const x=i.filter((g,f)=>f!==m);a(Object.fromEntries(x))};return e.jsxs("div",{className:J("space-y-2",n),children:[i.map(([m,x],g)=>e.jsxs("div",{className:"flex gap-2",children:[e.jsx(I,{type:"text",value:m,onChange:f=>u(g,f.target.value,x),placeholder:t("headerName"),className:"flex-1"}),e.jsx(I,{type:"text",value:x,onChange:f=>u(g,m,f.target.value),placeholder:t("headerValue"),className:"flex-1"}),e.jsx(S,{type:"button",variant:"ghost",size:"icon",onClick:()=>y(g),children:e.jsx(me,{className:"h-4 w-4 text-red-500"})})]},g)),e.jsxs(S,{type:"button",variant:"outline",size:"sm",onClick:b,children:[e.jsx(ne,{className:"h-4 w-4 mr-2"}),t("add")]})]})}function U(s){if(!s)return null;const a=Object.entries(s).map(([n,i])=>[n.trim(),i]).filter(([n])=>n.length>0);return a.length===0?null:Object.fromEntries(a)}function de(s,a){const n=U(s),i=U(a);if(n===null&&i===null)return!0;if(!n||!i)return!1;const u=Object.entries(n).sort(([y],[m])=>y.localeCompare(m)),b=Object.entries(i).sort(([y],[m])=>y.localeCompare(m));return u.length!==b.length?!1:u.every(([y,m],x)=>y===b[x][0]&&m===b[x][1])}function ae(s){if(!s||s.length===0)return[];const a=new Set;for(const n of s){const i=n.trim();i&&a.add(i)}return[...a]}function Je(s,a){const n=s.trim();if(!n||!a.trim())return n;const i=`${a.trim()}/`;return n.startsWith(i)?n.slice(i.length):n}function Y(s,a){let n=s.trim();if(!n)return"";for(const i of a){const u=i.trim();u&&(n=Je(n,u))}return n.trim()}function re(s,a){return s.length!==a.length?!1:s.every((n,i)=>n===a[i])}function Ze(s,a){const n=[...s];for(const i of a)n.includes(i)||n.push(i);return n}function es(s,a){return a.length===0?s:a.every(i=>!s.includes(i))?Ze(s,a):a}function ss(s,a){return re(s,a)?[]:s}function ts({providerName:s,onProviderDeleted:a}){const{data:n}=xe(),{data:i}=he(),{data:u}=pe(),b=Te(),y=Be(),m=Ke(),[x,g]=p.useState(""),[f,B]=p.useState(""),[K,F]=p.useState(null),[E,A]=p.useState("auto"),[C,$]=p.useState([]),[o,d]=p.useState(""),[N,v]=p.useState(""),r=i==null?void 0:i.providers.find(l=>l.name===s),c=s?n==null?void 0:n.providers[s]:null,L=u==null?void 0:u.uiHints,P=!!(r!=null&&r.isCustom),T=s?q(`providers.${s}.apiKey`,L):void 0,w=s?q(`providers.${s}.apiBase`,L):void 0,O=s?q(`providers.${s}.extraHeaders`,L):void 0,k=s?q(`providers.${s}.wireApi`,L):void 0,Z=(r==null?void 0:r.displayName)||s||"",_=((c==null?void 0:c.displayName)||"").trim()||Z,fe=N.trim()||_||s||t("providersSelectPlaceholder"),le=(r==null?void 0:r.modelPrefix)||s||"",R=p.useMemo(()=>ae([le,s||""]),[le,s]),Q=(r==null?void 0:r.defaultApiBase)||"",H=(c==null?void 0:c.apiBase)||Q,ee=U((c==null?void 0:c.extraHeaders)||null),z=(c==null?void 0:c.wireApi)||(r==null?void 0:r.defaultWireApi)||"auto",X=p.useMemo(()=>ae(((r==null?void 0:r.defaultModels)??[]).map(l=>Y(l,R))),[r==null?void 0:r.defaultModels,R]),ie=p.useMemo(()=>ae(((c==null?void 0:c.models)??[]).map(l=>Y(l,R))),[c==null?void 0:c.models,R]),G=p.useMemo(()=>es(X,ie),[X,ie]),ge=s==="minimax"?t("providerApiBaseHelpMinimax"):(w==null?void 0:w.help)||t("providerApiBaseHelp");p.useEffect(()=>{if(!s){g(""),B(""),F(null),A("auto"),$([]),d(""),v("");return}g(""),B(H),F((c==null?void 0:c.extraHeaders)||null),A(z),$(G),d(""),v(_)},[s,H,c==null?void 0:c.extraHeaders,z,G,_]);const ce=p.useMemo(()=>{if(!s)return!1;const l=x.trim().length>0,j=f.trim()!==H.trim(),D=!de(K,ee),h=r!=null&&r.supportsWireApi?E!==z:!1,M=!re(C,G),V=P?N.trim()!==_:!1;return l||j||D||h||M||V},[s,P,N,_,x,f,H,K,ee,r==null?void 0:r.supportsWireApi,E,z,C,G]),je=()=>{g(""),B(Q),F(null),A((r==null?void 0:r.defaultWireApi)||"auto"),$(X),d(""),v(Z)},oe=()=>{const l=Y(o,R);if(l){if(C.includes(l)){d("");return}$(j=>[...j,l]),d("")}},be=l=>{if(l.preventDefault(),!s)return;const j={},D=x.trim(),h=f.trim(),M=U(K),V=N.trim();P&&V!==_&&(j.displayName=V.length>0?V:null),D.length>0&&(j.apiKey=D),h!==H.trim()&&(j.apiBase=h.length>0&&h!==Q?h:null),de(M,ee)||(j.extraHeaders=M),r!=null&&r.supportsWireApi&&E!==z&&(j.wireApi=E),re(C,G)||(j.models=ss(C,X)),b.mutate({provider:s,data:j})},Ne=async()=>{if(!s)return;const l=C.find(h=>h.trim().length>0)??"",j=Y(l,R),D={apiBase:f.trim(),extraHeaders:U(K),model:j||null};x.trim().length>0&&(D.apiKey=x.trim()),r!=null&&r.supportsWireApi&&(D.wireApi=E);try{const h=await m.mutateAsync({provider:s,data:D});if(h.success){te.success(`${t("providerTestConnectionSuccess")} (${h.latencyMs}ms)`);return}const M=[`provider=${h.provider}`,`latency=${h.latencyMs}ms`];h.model&&M.push(`model=${h.model}`),te.error(`${t("providerTestConnectionFailed")}: ${h.message} | ${M.join(" | ")}`)}catch(h){const M=h instanceof Error?h.message:String(h);te.error(`${t("providerTestConnectionFailed")}: ${M}`)}},ve=async()=>{if(!(!s||!P||!window.confirm(t("providerDeleteConfirm"))))try{await y.mutateAsync({provider:s}),a==null||a(s)}catch{}};if(!s||!r||!c)return e.jsx("div",{className:He,children:e.jsxs("div",{children:[e.jsx("h3",{className:"text-base font-semibold text-gray-900",children:t("providersSelectTitle")}),e.jsx("p",{className:"mt-2 text-sm text-gray-500",children:t("providersSelectDescription")})]})});const we=c.apiKeySet?t("statusReady"):t("statusSetup");return e.jsxs("div",{className:ze,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:fe}),e.jsx("p",{className:"mt-1 text-sm text-gray-500",children:t("providerFormDescription")})]}),e.jsx(ye,{status:c.apiKeySet?"ready":"setup",label:we})]})}),e.jsxs("form",{onSubmit:be,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:[P&&e.jsxs("div",{className:"space-y-2.5",children:[e.jsxs(W,{htmlFor:"providerDisplayName",className:"flex items-center gap-2 text-sm font-medium text-gray-900",children:[e.jsx(se,{className:"h-3.5 w-3.5 text-gray-500"}),t("providerDisplayName")]}),e.jsx(I,{id:"providerDisplayName",type:"text",value:N,onChange:l=>v(l.target.value),placeholder:Z||t("providerDisplayNamePlaceholder"),className:"rounded-xl"}),e.jsx("p",{className:"text-xs text-gray-500",children:t("providerDisplayNameHelp")})]}),e.jsxs("div",{className:"space-y-2.5",children:[e.jsxs(W,{htmlFor:"apiKey",className:"flex items-center gap-2 text-sm font-medium text-gray-900",children:[e.jsx(ue,{className:"h-3.5 w-3.5 text-gray-500"}),(T==null?void 0:T.label)??t("apiKey")]}),e.jsx(Xe,{id:"apiKey",value:x,isSet:c.apiKeySet,onChange:l=>g(l.target.value),placeholder:c.apiKeySet?t("apiKeySet"):(T==null?void 0:T.placeholder)??t("enterApiKey"),className:"rounded-xl"}),e.jsx("p",{className:"text-xs text-gray-500",children:t("leaveBlankToKeepUnchanged")})]}),e.jsxs("div",{className:"space-y-2.5",children:[e.jsxs(W,{htmlFor:"apiBase",className:"flex items-center gap-2 text-sm font-medium text-gray-900",children:[e.jsx(Pe,{className:"h-3.5 w-3.5 text-gray-500"}),(w==null?void 0:w.label)??t("apiBase")]}),e.jsx(I,{id:"apiBase",type:"text",value:f,onChange:l=>B(l.target.value),placeholder:Q||(w==null?void 0:w.placeholder)||"https://api.example.com",className:"rounded-xl"}),e.jsx("p",{className:"text-xs text-gray-500",children:ge}),P&&e.jsx("p",{className:"text-xs text-gray-500",children:t("providerOpenAICompatHint")})]}),r.supportsWireApi&&e.jsxs("div",{className:"space-y-2.5",children:[e.jsxs(W,{htmlFor:"wireApi",className:"flex items-center gap-2 text-sm font-medium text-gray-900",children:[e.jsx(se,{className:"h-3.5 w-3.5 text-gray-500"}),(k==null?void 0:k.label)??t("wireApi")]}),e.jsxs(Fe,{value:E,onValueChange:l=>A(l),children:[e.jsx(Oe,{className:"rounded-xl",children:e.jsx(_e,{})}),e.jsx(Re,{children:(r.wireApiOptions||["auto","chat","responses"]).map(l=>e.jsx(We,{value:l,children:l==="chat"?t("wireApiChat"):l==="responses"?t("wireApiResponses"):t("wireApiAuto")},l))})]}),(k==null?void 0:k.help)&&e.jsx("p",{className:"text-xs text-gray-500",children:k.help})]}),e.jsxs("div",{className:"space-y-2.5",children:[e.jsxs(W,{className:"flex items-center gap-2 text-sm font-medium text-gray-900",children:[e.jsx(se,{className:"h-3.5 w-3.5 text-gray-500"}),(O==null?void 0:O.label)??t("extraHeaders")]}),e.jsx(Ye,{value:K,onChange:F}),e.jsx("p",{className:"text-xs text-gray-500",children:(O==null?void 0:O.help)||t("providerExtraHeadersHelp")})]}),e.jsxs("div",{className:"space-y-2.5",children:[e.jsxs(W,{className:"flex items-center gap-2 text-sm font-medium text-gray-900",children:[e.jsx(De,{className:"h-3.5 w-3.5 text-gray-500"}),t("providerModelsTitle")]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(I,{value:o,onChange:l=>d(l.target.value),onKeyDown:l=>{l.key==="Enter"&&(l.preventDefault(),oe())},placeholder:t("providerModelInputPlaceholder"),className:"flex-1"}),e.jsxs(S,{type:"button",variant:"outline",onClick:oe,disabled:o.trim().length===0,children:[e.jsx(ne,{className:"mr-1.5 h-4 w-4"}),t("providerAddModel")]})]}),e.jsx("p",{className:"text-xs text-gray-500",children:t("providerModelInputHint")}),C.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:t("providerModelsEmpty")}):e.jsx("div",{className:"flex flex-wrap gap-2",children:C.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:()=>$(j=>j.filter(D=>D!==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":t("remove"),children:e.jsx(Le,{className:"h-3 w-3"})})]},l))}),e.jsx("p",{className:"text-xs text-gray-500",children:t("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(S,{type:"button",variant:"outline",onClick:je,children:[e.jsx(Me,{className:"mr-2 h-4 w-4"}),t("resetToDefault")]}),P&&e.jsxs(S,{type:"button",variant:"outline",onClick:ve,disabled:y.isPending,children:[e.jsx(me,{className:"mr-2 h-4 w-4"}),y.isPending?t("saving"):t("providerDelete")]}),e.jsxs(S,{type:"button",variant:"outline",onClick:Ne,disabled:m.isPending,children:[e.jsx(Se,{className:"mr-2 h-4 w-4"}),m.isPending?t("providerTestingConnection"):t("providerTestConnection")]})]}),e.jsx(S,{type:"submit",disabled:b.isPending||!ce,children:b.isPending?t("saving"):ce?t("save"):t("unchanged")})]})]})]})}function as(s){if(!s)return null;try{const a=new URL(s),n=a.pathname&&a.pathname!=="/"?a.pathname:"";return`${a.host}${n}`}catch{return s.replace(/^https?:\/\//,"")}}function xs(){const{data:s}=xe(),{data:a}=he(),{data:n}=pe(),i=$e(),[u,b]=p.useState("installed"),[y,m]=p.useState(),[x,g]=p.useState(""),f=n==null?void 0:n.uiHints,B=(a==null?void 0:a.providers)??[],K=(s==null?void 0:s.providers)??{},F=B.filter(o=>{var d;return(d=K[o.name])==null?void 0:d.apiKeySet}).length,E=[{id:"installed",label:t("providersTabConfigured"),count:F},{id:"all",label:t("providersTabAll"),count:B.length}],A=p.useMemo(()=>{const o=(a==null?void 0:a.providers)??[],d=(s==null?void 0:s.providers)??{},N=x.trim().toLowerCase();return o.filter(v=>{var r;return u==="installed"?!!((r=d[v.name])!=null&&r.apiKeySet):!0}).filter(v=>{var L,P;return N?(((P=(L=d[v.name])==null?void 0:L.displayName)==null?void 0:P.trim())||v.displayName||v.name).toLowerCase().includes(N)||v.name.toLowerCase().includes(N):!0})},[a,s,u,x]);p.useEffect(()=>{if(A.length===0){m(void 0);return}A.some(d=>d.name===y)||m(A[0].name)},[A,y]);const C=y,$=async()=>{try{const o=await i.mutateAsync({data:{}});b("all"),g(""),m(o.name)}catch{}};return!s||!a?e.jsx("div",{className:"p-8",children:t("providersLoading")}):e.jsxs(ke,{children:[e.jsx(Ie,{title:t("providersPageTitle"),description:t("providersPageDescription")}),e.jsxs("div",{className:Ge,children:[e.jsxs("section",{className:Ve,children:[e.jsxs("div",{className:"border-b border-gray-100 px-4 pt-4 pb-3 space-y-3",children:[e.jsx(Qe,{tabs:E,activeTab:u,onChange:b,className:"mb-0"}),e.jsxs(S,{type:"button",variant:"outline",className:"w-full justify-center",onClick:$,disabled:i.isPending,children:[e.jsx(ne,{className:"mr-2 h-4 w-4"}),i.isPending?t("saving"):t("providerAddCustom")]})]}),e.jsx("div",{className:"border-b border-gray-100 px-4 py-3",children:e.jsxs("div",{className:"relative",children:[e.jsx(Ee,{className:"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400"}),e.jsx(I,{value:x,onChange:o=>g(o.target.value),placeholder:t("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:[A.map(o=>{var w;const d=s.providers[o.name],N=!!(d!=null&&d.apiKeySet),v=C===o.name,r=((w=d==null?void 0:d.displayName)==null?void 0:w.trim())||o.displayName||o.name,c=q(`providers.${o.name}`,f),L=(d==null?void 0:d.apiBase)||o.defaultApiBase||"",T=as(L)||(c==null?void 0:c.help)||t("providersDefaultDescription");return e.jsx("button",{type:"button",onClick:()=>m(o.name),className:J("w-full rounded-xl border p-2.5 text-left transition-all",v?"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(qe,{name:o.name,src:Ue(o.name),className:J("h-10 w-10 rounded-lg border",N?"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:o.name[0]})}),e.jsxs("div",{className:"min-w-0",children:[e.jsx("p",{className:"truncate text-sm font-semibold text-gray-900",children:r}),e.jsx("p",{className:"line-clamp-1 text-[11px] text-gray-500",children:T})]})]}),e.jsx(ye,{status:N?"ready":"setup",label:N?t("statusReady"):t("statusSetup"),className:"min-w-[56px] justify-center"})]})},o.name)}),A.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(ue,{className:"h-5 w-5 text-gray-300"})}),e.jsx("p",{className:"text-sm font-medium text-gray-700",children:t("providersNoMatch")})]})]})]}),e.jsx(ts,{providerName:C,onProviderDeleted:o=>{o===y&&m(void 0)}})]})]})}export{xs as ProvidersList};
|