import { useEffect, useId, useMemo, useState } from "react"; import { Cpu } from "lucide-react"; import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select"; import { cn } from "@/lib/utils"; import { modelOptionLabel, modelOptionName, selectableModelsByCapability, type AiConfig, type ModelCapability } from "@/stores/use-config-store"; type ModelPickerProps = { config: AiConfig; value?: string; onChange: (model: string) => void; capability?: ModelCapability; className?: string; fullWidth?: boolean; placeholder?: string; onMissingConfig?: () => void; }; export function ModelPicker({ config, value, onChange, capability, className, fullWidth = false, placeholder = "选择模型", onMissingConfig }: ModelPickerProps) { const pickerId = useId(); const [open, setOpen] = useState(false); const options = useMemo(() => Array.from(new Set([...(config.channelMode === "local" && !capability ? [value] : []), ...selectableModelsByCapability(config, capability)].filter((model): model is string => Boolean(model)))), [capability, config, value]); const current = value || ""; useEffect(() => { const closeOtherPicker = (event: Event) => { if ((event as CustomEvent).detail !== pickerId) setOpen(false); }; window.addEventListener("model-picker-open", closeOtherPicker); return () => window.removeEventListener("model-picker-open", closeOtherPicker); }, [pickerId]); return ( ); } function emptyModelLabel(config: AiConfig, capability?: ModelCapability) { const label = capability === "image" ? "生图" : capability === "video" ? "视频" : capability === "text" ? "文本" : capability === "audio" ? "音频" : ""; if (capability && config.models.length) return `请先在渠道里为${label}指定模型`; return config.models.length ? `暂无匹配的${label}模型` : "请先到配置里添加渠道和模型"; } function ModelLabel({ config, model }: { config: AiConfig; model: string }) { return ( {modelOptionLabel(config, model)} ); } function ModelIcon({ model }: { model: string }) { const icon = resolveModelIcon(modelOptionName(model)); return icon ? : ; } function resolveModelIcon(model: string) { const name = model.toLowerCase(); if (name.includes("claude") || name.includes("anthropic")) return "/icons/claude.svg"; if (name.includes("gemini") || name.includes("google")) return "/icons/gemini.svg"; if (name.includes("gpt") || name.includes("openai")) return "/icons/openai.svg"; if (name.includes("grok") || name.includes("grok")) return "/icons/grok.svg"; if (name.includes("deepseek") || name.includes("deepseek")) return "/icons/deepseek.svg"; if (name.includes("glm") || name.includes("glm")) return "/icons/glm.svg"; return ""; }