"use client"; import { useEffect, useMemo, useState } from "react"; import { useTranslations } from "next-intl"; import { Badge, Button, Input, Modal, Select } from "@/shared/components"; type CompatibleMode = "openai" | "anthropic" | "cc"; type CompatibleProviderNode = { id: string } & Record; interface AddCompatibleProviderModalProps { isOpen: boolean; mode: CompatibleMode; title?: string; onClose: () => void; onCreated: (node: CompatibleProviderNode) => void; } interface CompatibleFormState { name: string; prefix: string; apiType: string; baseUrl: string; chatPath: string; modelsPath: string; } const CC_DEFAULT_CHAT_PATH = "/v1/messages?beta=true"; const MODE_DEFAULTS: Record< CompatibleMode, { baseUrl: string; type: "openai-compatible" | "anthropic-compatible"; compatMode?: "cc"; chatPath: string; hasApiType: boolean; hasModelsPath: boolean; hasWarning: boolean; } > = { openai: { baseUrl: "https://api.openai.com/v1", type: "openai-compatible", chatPath: "", hasApiType: true, hasModelsPath: true, hasWarning: false, }, anthropic: { baseUrl: "https://api.anthropic.com/v1", type: "anthropic-compatible", chatPath: "", hasApiType: false, hasModelsPath: true, hasWarning: false, }, cc: { baseUrl: "", type: "anthropic-compatible", compatMode: "cc", chatPath: CC_DEFAULT_CHAT_PATH, hasApiType: false, hasModelsPath: false, hasWarning: true, }, }; function createInitialForm(mode: CompatibleMode): CompatibleFormState { const defaults = MODE_DEFAULTS[mode]; return { name: "", prefix: "", apiType: "chat", baseUrl: defaults.baseUrl, chatPath: defaults.chatPath, modelsPath: "", }; } export default function AddCompatibleProviderModal({ isOpen, mode, title, onClose, onCreated, }: AddCompatibleProviderModalProps) { const t = useTranslations("providers"); const defaults = MODE_DEFAULTS[mode]; const [formData, setFormData] = useState(() => createInitialForm(mode)); const [submitting, setSubmitting] = useState(false); const [checkKey, setCheckKey] = useState(""); const [checkModelId, setCheckModelId] = useState(""); const [validating, setValidating] = useState(false); const [validationResult, setValidationResult] = useState< null | { valid: boolean; error?: string | null; method?: string | null } >(null); const [showAdvanced, setShowAdvanced] = useState(false); const apiTypeOptions = useMemo( () => [ { value: "chat", label: t("chatCompletions") }, { value: "responses", label: t("responsesApi") }, { value: "embeddings", label: t("embeddings") }, { value: "audio-transcriptions", label: t("audioTranscriptions") }, { value: "audio-speech", label: t("audioSpeech") }, { value: "images-generations", label: t("imagesGenerations") }, ], [t] ); useEffect(() => { if (!isOpen) return; setFormData(createInitialForm(mode)); setValidationResult(null); setCheckKey(""); setShowAdvanced(false); }, [isOpen, mode]); const modalTitle = title || (mode === "openai" ? t("addOpenAICompatible") : mode === "anthropic" ? t("addAnthropicCompatible") : t("addCcCompatible")); const namePlaceholder = mode === "cc" ? t("ccCompatibleNamePlaceholder") : t("compatibleProdPlaceholder", { type: mode === "openai" ? t("openai") : t("anthropic"), }); const nameHint = mode === "cc" ? t("ccCompatibleNameHint") : t("nameHint"); const prefixPlaceholder = mode === "openai" ? t("openaiPrefixPlaceholder") : mode === "cc" ? t("ccCompatiblePrefixPlaceholder") : t("anthropicPrefixPlaceholder"); const prefixHint = mode === "cc" ? t("ccCompatiblePrefixHint") : t("prefixHint"); const baseUrlPlaceholder = mode === "openai" ? t("openaiBaseUrlPlaceholder") : mode === "cc" ? t("ccCompatibleBaseUrlPlaceholder") : t("anthropicBaseUrlPlaceholder"); const baseUrlHint = mode === "cc" ? t("ccCompatibleBaseUrlHint") : t("compatibleBaseUrlHint", { type: mode === "openai" ? t("openai") : t("anthropic"), }); const chatPathPlaceholder = mode === "openai" ? "/v1/chat/completions" : mode === "cc" ? CC_DEFAULT_CHAT_PATH : "/messages"; const chatPathHint = mode === "cc" ? t("ccCompatibleChatPathHint") : t("chatPathHint"); const advancedId = `advanced-settings-${mode}`; const hasRequiredFields = Boolean( formData.name.trim() && formData.prefix.trim() && formData.baseUrl.trim() ); const canValidate = Boolean(checkKey.trim() && formData.baseUrl.trim()); const resetAfterCreate = () => { setFormData(createInitialForm(mode)); setCheckKey(""); setValidationResult(null); setShowAdvanced(false); }; const handleSubmit = async () => { if (!hasRequiredFields) return; setSubmitting(true); try { const body: Record = { name: formData.name, prefix: formData.prefix, baseUrl: formData.baseUrl, type: defaults.type, chatPath: formData.chatPath || (mode === "cc" ? CC_DEFAULT_CHAT_PATH : ""), }; if (defaults.hasApiType) body.apiType = formData.apiType; if (defaults.hasModelsPath) body.modelsPath = formData.modelsPath || ""; if (defaults.compatMode) body.compatMode = defaults.compatMode; const res = await fetch("/api/provider-nodes", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const data = (await res.json()) as { node: CompatibleProviderNode }; if (res.ok) { onCreated(data.node); resetAfterCreate(); } } catch (error) { console.log(`Error creating ${mode} compatible node:`, error); } finally { setSubmitting(false); } }; const handleValidate = async () => { setValidating(true); try { const body: Record = { baseUrl: formData.baseUrl, apiKey: checkKey, type: defaults.type, }; if (defaults.hasModelsPath) body.modelsPath = formData.modelsPath || ""; if (defaults.compatMode) { body.compatMode = defaults.compatMode; body.chatPath = formData.chatPath || CC_DEFAULT_CHAT_PATH; } const trimmedModelId = checkModelId.trim(); if (trimmedModelId) body.modelId = trimmedModelId; const res = await fetch("/api/provider-nodes/validate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const data = await res.json(); setValidationResult({ valid: !!data.valid, error: data.error ?? null, method: data.method ?? null, }); } catch { setValidationResult({ valid: false, error: "Network error" }); } finally { setValidating(false); } }; return (
{defaults.hasWarning && (
warning

{t("ccCompatibleValidationHint")}

)} setFormData({ ...formData, name: e.target.value })} placeholder={namePlaceholder} hint={nameHint} /> setFormData({ ...formData, prefix: e.target.value })} placeholder={prefixPlaceholder} hint={prefixHint} /> {defaults.hasApiType && ( setFormData({ ...formData, baseUrl: e.target.value })} placeholder={baseUrlPlaceholder} hint={baseUrlHint} /> {showAdvanced && (
setFormData({ ...formData, chatPath: e.target.value })} placeholder={chatPathPlaceholder} hint={chatPathHint} /> {defaults.hasModelsPath && ( setFormData({ ...formData, modelsPath: e.target.value })} placeholder={t("modelsPathPlaceholder")} hint={t("modelsPathHint")} /> )}
)}
setCheckKey(e.target.value)} className="flex-1" />
setCheckModelId(e.target.value)} placeholder={t("testModelIdPlaceholder")} hint={t("testModelIdHint")} /> {validationResult && (
{validationResult.valid ? t("valid") : t("invalid")} {validationResult.error && ( {validationResult.error} )}
)}
); }