import { useState } from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { createProvider, updateProvider, setApiKey } from '@/services/llmProviderService'; import type { ProviderDto, ProviderType } from '@/services/llmProviderService'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { toast } from 'sonner'; import { ModelCombobox } from './ModelCombobox'; interface ProviderDialogProps { open: boolean; onOpenChange: (open: boolean) => void; provider?: ProviderDto; } const TYPE_OPTIONS: { value: ProviderType; label: string; description: string }[] = [ { value: 'openai_compatible', label: 'OpenAI 兼容', description: '兼容 OpenAI 接口的第三方服务' }, { value: 'openai_responses', label: 'OpenAI Responses', description: 'OpenAI 官方 Responses API' }, { value: 'anthropic', label: 'Anthropic', description: 'Anthropic Messages API' }, { value: 'gemini', label: 'Gemini', description: 'Google Gemini API' }, ]; export function ProviderDialog({ open, onOpenChange, provider }: ProviderDialogProps) { if (!open) return null; // Inner component mounts fresh each time dialog opens, // so useState initializers read directly from provider props. return ; } function ProviderDialogInner({ onOpenChange, provider }: Omit) { const queryClient = useQueryClient(); const isEdit = !!provider; const [name, setName] = useState(provider?.name ?? ''); const [type, setType] = useState(provider?.type ?? 'openai_compatible'); const [baseUrl, setBaseUrl] = useState(provider?.baseUrl ?? ''); const [defaultModel, setDefaultModel] = useState(provider?.defaultModel ?? ''); const [apiKey, setApiKeyInput] = useState(''); const saveMutation = useMutation({ mutationFn: async () => { let savedProvider: ProviderDto; const payload: Partial & { apiKey?: string } = { name, type, baseUrl: baseUrl || null, defaultModel, }; if (!isEdit) { if (apiKey) payload.apiKey = apiKey; savedProvider = await createProvider(payload); } else { savedProvider = await updateProvider(provider.id, { name, type, baseUrl: baseUrl || null, defaultModel, }); if (apiKey) { await setApiKey(provider.id, apiKey); } } return savedProvider; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['llm-providers'] }); toast.success(isEdit ? '提供商已更新' : '提供商已创建'); onOpenChange(false); }, onError: (error: unknown) => { const err = error as { response?: { data?: { error?: string } }; message?: string }; toast.error(`保存失败: ${err?.response?.data?.error || err.message}`); } }); const isBaseUrlRequired = type === 'openai_compatible'; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!name) return toast.error('请输入名称'); if (!defaultModel) return toast.error('请输入默认模型'); if (isBaseUrlRequired && !baseUrl) return toast.error('该类型必须填写 Base URL'); if (!isEdit && !apiKey) return toast.error('创建提供商时必须提供 API Key'); saveMutation.mutate(); }; return (

{isEdit ? '编辑提供商' : '添加提供商'}

setName(e.target.value)} placeholder="如: DeepSeek" autoComplete="off" className="bg-muted/50 border-border text-foreground" />
setBaseUrl(e.target.value)} placeholder={isBaseUrlRequired ? "https://api.openai.com/v1" : "留空以使用默认地址"} autoComplete="off" className="bg-muted/50 border-border text-foreground" />
setApiKeyInput(e.target.value)} placeholder={isEdit && provider?.hasKey ? '•••••••• (输入以覆盖)' : 'sk-...'} autoComplete="off" className="bg-muted/50 border-border text-foreground" />
); }