diff --git a/src/components/provider-pool/GeminiApiKeySection.tsx b/src/components/provider-pool/GeminiApiKeySection.tsx index 9ee5fd97d..a5ddb4604 100644 --- a/src/components/provider-pool/GeminiApiKeySection.tsx +++ b/src/components/provider-pool/GeminiApiKeySection.tsx @@ -3,14 +3,16 @@ import { Plus, Trash2, Key, Globe, Ban, Eye, EyeOff } from "lucide-react"; import type { GeminiApiKeyEntry } from "@/hooks/useTauri"; interface GeminiApiKeySectionProps { - entries: GeminiApiKeyEntry[]; + entries: GeminiApiKeyEntry[] | undefined; onChange: (entries: GeminiApiKeyEntry[]) => void; } export function GeminiApiKeySection({ - entries, + entries = [], onChange, }: GeminiApiKeySectionProps) { + // 确保 entries 是数组 + const safeEntries = Array.isArray(entries) ? entries : []; const [showKeys, setShowKeys] = useState>(new Set()); const [editingExclusions, setEditingExclusions] = useState( null, @@ -36,33 +38,39 @@ export function GeminiApiKeySection({ excluded_models: [], disabled: false, }; - onChange([...entries, newEntry]); + onChange([...safeEntries, newEntry]); }; const updateEntry = (id: string, updates: Partial) => { - onChange(entries.map((e) => (e.id === id ? { ...e, ...updates } : e))); + onChange(safeEntries.map((e) => (e.id === id ? { ...e, ...updates } : e))); }; const removeEntry = (id: string) => { - onChange(entries.filter((e) => e.id !== id)); + onChange(safeEntries.filter((e) => e.id !== id)); }; const addExclusion = (id: string) => { if (!exclusionInput.trim()) return; - const entry = entries.find((e) => e.id === id); + const entry = safeEntries.find((e) => e.id === id); if (entry) { + const currentExcluded = Array.isArray(entry.excluded_models) + ? entry.excluded_models + : []; updateEntry(id, { - excluded_models: [...entry.excluded_models, exclusionInput.trim()], + excluded_models: [...currentExcluded, exclusionInput.trim()], }); setExclusionInput(""); } }; const removeExclusion = (id: string, model: string) => { - const entry = entries.find((e) => e.id === id); + const entry = safeEntries.find((e) => e.id === id); if (entry) { + const currentExcluded = Array.isArray(entry.excluded_models) + ? entry.excluded_models + : []; updateEntry(id, { - excluded_models: entry.excluded_models.filter((m) => m !== model), + excluded_models: currentExcluded.filter((m) => m !== model), }); } }; @@ -88,175 +96,185 @@ export function GeminiApiKeySection({ - {entries.length === 0 ? ( + {safeEntries.length === 0 ? (

暂无 Gemini API Key

点击上方"添加"按钮添加 API Key

) : (
- {entries.map((entry) => ( -
- {/* Header */} -
- - {entry.id} - -
- - -
-
- - {/* API Key */} -
- -
- - updateEntry(entry.id, { api_key: e.target.value }) - } - placeholder="AIzaSy..." - className="w-full px-3 py-1.5 pr-10 rounded border bg-background text-sm font-mono" - /> - -
-
- - {/* Base URL */} -
- - - updateEntry(entry.id, { base_url: e.target.value || null }) - } - placeholder="https://generativelanguage.googleapis.com" - className="w-full px-3 py-1.5 rounded border bg-background text-sm" - /> -
- - {/* Proxy URL */} -
- - - updateEntry(entry.id, { proxy_url: e.target.value || null }) - } - placeholder="socks5://127.0.0.1:1080" - className="w-full px-3 py-1.5 rounded border bg-background text-sm" - /> -
- - {/* Excluded Models */} -
- -
- {entry.excluded_models.map((model) => ( - - {model} - - - ))} -
- {editingExclusions === entry.id ? ( -
- setExclusionInput(e.target.value)} - placeholder="gemini-2.5-pro 或 *-preview" - className="flex-1 px-2 py-1 rounded border bg-background text-sm" - onKeyDown={(e) => { - if (e.key === "Enter") { - addExclusion(entry.id); + {safeEntries.map((entry) => { + // 确保 excluded_models 是数组 + const excludedModels = Array.isArray(entry.excluded_models) + ? entry.excluded_models + : []; + return ( +
+ {/* Header */} +
+ + {entry.id} + +
+ -
- ) : ( - - )} -

- 支持通配符,如 *-preview 匹配所有预览模型 -

+
+ + {/* API Key */} +
+ +
+ + updateEntry(entry.id, { api_key: e.target.value }) + } + placeholder="AIzaSy..." + className="w-full px-3 py-1.5 pr-10 rounded border bg-background text-sm font-mono" + /> + +
+
+ + {/* Base URL */} +
+ + + updateEntry(entry.id, { + base_url: e.target.value || null, + }) + } + placeholder="https://generativelanguage.googleapis.com" + className="w-full px-3 py-1.5 rounded border bg-background text-sm" + /> +
+ + {/* Proxy URL */} +
+ + + updateEntry(entry.id, { + proxy_url: e.target.value || null, + }) + } + placeholder="socks5://127.0.0.1:1080" + className="w-full px-3 py-1.5 rounded border bg-background text-sm" + /> +
+ + {/* Excluded Models */} +
+ +
+ {excludedModels.map((model) => ( + + {model} + + + ))} +
+ {editingExclusions === entry.id ? ( +
+ setExclusionInput(e.target.value)} + placeholder="gemini-2.5-pro 或 *-preview" + className="flex-1 px-2 py-1 rounded border bg-background text-sm" + onKeyDown={(e) => { + if (e.key === "Enter") { + addExclusion(entry.id); + } + }} + /> + + +
+ ) : ( + + )} +

+ 支持通配符,如 *-preview 匹配所有预览模型 +

+
-
- ))} + ); + })}
)}
diff --git a/src/components/provider-pool/VertexAISection.tsx b/src/components/provider-pool/VertexAISection.tsx index 4962a2e9c..3b69faacb 100644 --- a/src/components/provider-pool/VertexAISection.tsx +++ b/src/components/provider-pool/VertexAISection.tsx @@ -11,11 +11,16 @@ import { import type { VertexApiKeyEntry, VertexModelAlias } from "@/hooks/useTauri"; interface VertexAISectionProps { - entries: VertexApiKeyEntry[]; + entries: VertexApiKeyEntry[] | undefined; onChange: (entries: VertexApiKeyEntry[]) => void; } -export function VertexAISection({ entries, onChange }: VertexAISectionProps) { +export function VertexAISection({ + entries = [], + onChange, +}: VertexAISectionProps) { + // 确保 entries 是数组 + const safeEntries = Array.isArray(entries) ? entries : []; const [showKeys, setShowKeys] = useState>(new Set()); const [editingAliases, setEditingAliases] = useState(null); const [aliasName, setAliasName] = useState(""); @@ -40,27 +45,28 @@ export function VertexAISection({ entries, onChange }: VertexAISectionProps) { proxy_url: null, disabled: false, }; - onChange([...entries, newEntry]); + onChange([...safeEntries, newEntry]); }; const updateEntry = (id: string, updates: Partial) => { - onChange(entries.map((e) => (e.id === id ? { ...e, ...updates } : e))); + onChange(safeEntries.map((e) => (e.id === id ? { ...e, ...updates } : e))); }; const removeEntry = (id: string) => { - onChange(entries.filter((e) => e.id !== id)); + onChange(safeEntries.filter((e) => e.id !== id)); }; const addAlias = (id: string) => { if (!aliasName.trim() || !aliasAlias.trim()) return; - const entry = entries.find((e) => e.id === id); + const entry = safeEntries.find((e) => e.id === id); if (entry) { const newAlias: VertexModelAlias = { name: aliasName.trim(), alias: aliasAlias.trim(), }; + const currentModels = Array.isArray(entry.models) ? entry.models : []; updateEntry(id, { - models: [...entry.models, newAlias], + models: [...currentModels, newAlias], }); setAliasName(""); setAliasAlias(""); @@ -68,10 +74,11 @@ export function VertexAISection({ entries, onChange }: VertexAISectionProps) { }; const removeAlias = (id: string, aliasToRemove: string) => { - const entry = entries.find((e) => e.id === id); + const entry = safeEntries.find((e) => e.id === id); if (entry) { + const currentModels = Array.isArray(entry.models) ? entry.models : []; updateEntry(id, { - models: entry.models.filter((m) => m.alias !== aliasToRemove), + models: currentModels.filter((m) => m.alias !== aliasToRemove), }); } }; @@ -97,186 +104,194 @@ export function VertexAISection({ entries, onChange }: VertexAISectionProps) {
- {entries.length === 0 ? ( + {safeEntries.length === 0 ? (

暂无 Vertex AI 凭证

点击上方"添加"按钮添加凭证

) : (
- {entries.map((entry) => ( -
- {/* Header */} -
- - {entry.id} - -
- - -
-
- - {/* API Key */} -
- -
- - updateEntry(entry.id, { api_key: e.target.value }) - } - placeholder="vk-..." - className="w-full px-3 py-1.5 pr-10 rounded border bg-background text-sm font-mono" - /> - -
-
- - {/* Base URL */} -
- - - updateEntry(entry.id, { base_url: e.target.value || null }) - } - placeholder="https://example.com/api" - className="w-full px-3 py-1.5 rounded border bg-background text-sm" - /> -
- - {/* Proxy URL */} -
- - - updateEntry(entry.id, { proxy_url: e.target.value || null }) - } - placeholder="socks5://127.0.0.1:1080" - className="w-full px-3 py-1.5 rounded border bg-background text-sm" - /> -
- - {/* Model Aliases */} -
- -
- {entry.models.map((model) => ( -
{ + // 确保 models 是数组 + const models = Array.isArray(entry.models) ? entry.models : []; + return ( +
+ {/* Header */} +
+ + {entry.id} + +
+ + -
- ))} -
- {editingAliases === entry.id ? ( -
-
- setAliasAlias(e.target.value)} - placeholder="客户端别名" - className="flex-1 px-2 py-1 rounded border bg-background text-sm" - /> - - setAliasName(e.target.value)} - placeholder="上游模型名" - className="flex-1 px-2 py-1 rounded border bg-background text-sm" - /> -
-
- - -
+ +
- ) : ( - - )} -

- 将客户端请求的模型名映射到上游实际模型名 -

+
+ + {/* API Key */} +
+ +
+ + updateEntry(entry.id, { api_key: e.target.value }) + } + placeholder="vk-..." + className="w-full px-3 py-1.5 pr-10 rounded border bg-background text-sm font-mono" + /> + +
+
+ + {/* Base URL */} +
+ + + updateEntry(entry.id, { + base_url: e.target.value || null, + }) + } + placeholder="https://example.com/api" + className="w-full px-3 py-1.5 rounded border bg-background text-sm" + /> +
+ + {/* Proxy URL */} +
+ + + updateEntry(entry.id, { + proxy_url: e.target.value || null, + }) + } + placeholder="socks5://127.0.0.1:1080" + className="w-full px-3 py-1.5 rounded border bg-background text-sm" + /> +
+ + {/* Model Aliases */} +
+ +
+ {models.map((model) => ( +
+ {model.alias} + + + {model.name} + + +
+ ))} +
+ {editingAliases === entry.id ? ( +
+
+ setAliasAlias(e.target.value)} + placeholder="客户端别名" + className="flex-1 px-2 py-1 rounded border bg-background text-sm" + /> + + setAliasName(e.target.value)} + placeholder="上游模型名" + className="flex-1 px-2 py-1 rounded border bg-background text-sm" + /> +
+
+ + +
+
+ ) : ( + + )} +

+ 将客户端请求的模型名映射到上游实际模型名 +

+
-
- ))} + ); + })}
)}