import { Button, Drawer, Input, Segmented, Select, Space } from "antd"; import { ListPlus, Trash2 } from "lucide-react"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { defaultBaseUrlForApiFormat, guessCapability, normalizeChannelModels, type ApiCallFormat, type ChannelModel, type ModelCapability, type ModelChannel } from "@/stores/use-config-store"; import { ModelScriptEditor } from "./model-script-editor"; import { ModelSelectModal } from "./model-select-modal"; type ScriptTarget = { name: string; capability: ModelCapability; value: string }; export function ChannelEditorDrawer({ open, channel, onSave, onClose }: { open: boolean; channel: ModelChannel | null; onSave: (channel: ModelChannel) => void; onClose: () => void }) { const { t } = useTranslation(); const [draft, setDraft] = useState(channel); const [selectOpen, setSelectOpen] = useState(false); const [scriptTarget, setScriptTarget] = useState(null); const apiFormatOptions: Array<{ label: string; value: ApiCallFormat }> = [ { label: "OpenAI", value: "openai" }, { label: "Gemini", value: "gemini" }, { label: t("config.protocols.ark"), value: "ark" }, ]; const capabilityOptions: Array<{ label: string; value: ModelCapability }> = ["image", "video", "text", "audio"].map((value) => ({ label: t(`config.channelEditor.capabilities.${value}`), value: value as ModelCapability })); useEffect(() => { if (open && channel) setDraft(channel); }, [open, channel]); if (!draft) return null; const patch = (value: Partial) => setDraft((current) => (current ? { ...current, ...value } : current)); const setModels = (models: ChannelModel[]) => patch({ models }); const changeApiFormat = (apiFormat: ApiCallFormat) => { const baseUrl = !draft.baseUrl.trim() || draft.baseUrl.trim() === defaultBaseUrlForApiFormat(draft.apiFormat) ? defaultBaseUrlForApiFormat(apiFormat) : draft.baseUrl; patch({ apiFormat, baseUrl }); }; const applySelection = (names: string[]) => { const map = new Map(draft.models.map((model) => [model.name, model])); setModels(names.map((name) => map.get(name) || { name, capability: guessCapability(name) })); }; const setCapability = (name: string, capability: ModelCapability) => setModels(draft.models.map((model) => (model.name === name ? { ...model, capability } : model))); const setScript = (name: string, script: string) => setModels(draft.models.map((model) => (model.name === name ? { ...model, script: script || undefined } : model))); const removeModel = (name: string) => setModels(draft.models.filter((model) => model.name !== name)); const save = () => { onSave({ ...draft, name: draft.name.trim() || t("config.channels.unnamed"), models: normalizeChannelModels(draft.models) }); onClose(); }; return ( } >
{t("config.channelEditor.models")}
{t("config.channelEditor.modelDescription", { count: draft.models.length })}
{draft.models.length ? ( draft.models.map((model) => (
{model.name}
setCapability(model.name, value as ModelCapability)} />
)) ) : (
{t("config.channelEditor.empty")}
)}
model.name)} onConfirm={applySelection} onClose={() => setSelectOpen(false)} /> scriptTarget && setScript(scriptTarget.name, script)} onClose={() => setScriptTarget(null)} />
); }