import { useState } from "react"; import { App, Button, InputNumber, Segmented } from "antd"; import { FlaskConical } from "lucide-react"; import { testChannelModelConnection } from "@/lib/model-connection-test"; import { ModelCapabilityEditor } from "@/components/model-capability-editor"; import { ModelCapabilityProtocolModal } from "@/components/model-protocol-picker"; import { defaultModelCapabilityConfig } from "@/lib/model-capabilities"; import { modelProtocolCapability, modelProtocolDefinition, type ModelProtocol } from "@/lib/model-protocols"; import { modelMatchesCapability, modelOptionName, type ModelChannel } from "@/stores/use-config-store"; type ModelCost = NonNullable[number]; export function ChannelModelSettings({ channel, onChange }: { channel: ModelChannel; onChange: (costs: ModelCost[]) => void }) { const { message } = App.useApp(); const [testingModel, setTestingModel] = useState(""); if (!channel.models.length) return null; const updateCost = (model: string, patch: Partial) => { const current = channel.modelCosts?.find((item) => item.model === model) || { model, capability: modelProtocolCapability(defaultProtocolForModel(channel, model)) || "text", protocol: defaultProtocolForModel(channel, model), billingMode: "fixed_request" as const, unitPriceMicrocredits: 0, capabilityConfig: defaultModelCapabilityConfig(defaultProtocolForModel(channel, model)), }; const next = [...(channel.modelCosts || []).filter((item) => item.model !== model), { ...current, ...patch, model }]; onChange(next.filter((item) => channel.models.includes(item.model))); }; const testModel = async (model: string, capability: ModelCost["capability"], protocol: ModelProtocol) => { setTestingModel(model); try { const detail = await testChannelModelConnection(channel, model, capability, protocol); message.success(`模型测试通过:${detail}`); } catch (error) { message.error(error instanceof Error ? error.message : "模型测试失败"); } finally { setTestingModel(""); } }; return (
模型能力与请求协议
与运营后台使用同一能力目录;测试会发起真实请求并可能产生供应商费用
{channel.models.length} 个模型
{channel.models.map((rawModel) => { const model = modelOptionName(rawModel); const cost = channel.modelCosts?.find((item) => item.model === model); const protocol = cost?.protocol || defaultProtocolForModel(channel, model); const capability = cost?.capability || modelProtocolCapability(protocol) || "text"; const billingMode = cost?.billingMode || "fixed_request"; return (
{model}
{modelProtocolDefinition(protocol)?.create}
updateCost(model, { protocol: nextProtocol, capability: nextCapability, billingMode: nextCapability === "video" ? billingMode : "fixed_request", capabilityConfig: nextCapability === "video" ? defaultModelCapabilityConfig(nextProtocol) : undefined })} /> {capability === "video" ?
updateCost(model, { billingMode: value as ModelCost["billingMode"] })} /> updateCost(model, { unitPriceMicrocredits: Math.round(Number(value || 0) * 1_000_000) })} />
: null} {capability === "video" ? updateCost(model, { capabilityConfig })} /> : null}
); })}
); } function defaultProtocolForModel(channel: ModelChannel, model: string): ModelProtocol { if (channel.interfaceType) return channel.interfaceType; if (channel.apiFormat === "gemini" && modelMatchesCapability(model, "video")) return "gemini-veo"; if (modelMatchesCapability(model, "video")) return "newapi"; if (modelMatchesCapability(model, "image")) return "openai-image"; return "chat-completion"; }