import { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Button } from "@/components/ui/button"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { useModelPricing, useDeleteModelPricing } from "@/lib/query/usage"; import { PricingEditModal } from "./PricingEditModal"; import { isNonNegativeDecimalString, type ModelPricing } from "@/types/usage"; import { Plus, Pencil, Trash2, Loader2 } from "lucide-react"; import { toast } from "sonner"; import { proxyApi } from "@/lib/api/proxy"; const PRICING_APPS = ["claude", "codex", "gemini"] as const; type PricingApp = (typeof PRICING_APPS)[number]; type PricingModelSource = "request" | "response"; interface AppConfig { multiplier: string; source: PricingModelSource; } type AppConfigState = Record; export function PricingConfigPanel() { const { t } = useTranslation(); const { data: pricing, isLoading, error } = useModelPricing(); const deleteMutation = useDeleteModelPricing(); const [editingModel, setEditingModel] = useState(null); const [isAddingNew, setIsAddingNew] = useState(false); const [deleteConfirm, setDeleteConfirm] = useState(null); // 三个应用的配置状态 const [appConfigs, setAppConfigs] = useState({ claude: { multiplier: "1", source: "response" }, codex: { multiplier: "1", source: "response" }, gemini: { multiplier: "1", source: "response" }, }); const [originalConfigs, setOriginalConfigs] = useState( null, ); const [isConfigLoading, setIsConfigLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); // 检查是否有改动 const isDirty = originalConfigs !== null && PRICING_APPS.some( (app) => appConfigs[app].multiplier !== originalConfigs[app].multiplier || appConfigs[app].source !== originalConfigs[app].source, ); // 加载所有应用的配置 useEffect(() => { let isMounted = true; const loadAllConfigs = async () => { setIsConfigLoading(true); try { const results = await Promise.all( PRICING_APPS.map(async (app) => { const [multiplier, source] = await Promise.all([ proxyApi.getDefaultCostMultiplier(app), proxyApi.getPricingModelSource(app), ]); return { app, multiplier, source: (source === "request" ? "request" : "response") as PricingModelSource, }; }), ); if (!isMounted) return; const newState: AppConfigState = { claude: { multiplier: "1", source: "response" }, codex: { multiplier: "1", source: "response" }, gemini: { multiplier: "1", source: "response" }, }; for (const result of results) { newState[result.app] = { multiplier: result.multiplier, source: result.source, }; } setAppConfigs(newState); setOriginalConfigs(newState); } catch (error) { const message = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error"; toast.error( t("settings.globalProxy.pricingLoadFailed", { error: message }), ); } finally { if (isMounted) setIsConfigLoading(false); } }; loadAllConfigs(); return () => { isMounted = false; }; }, [t]); // 保存所有配置 const handleSaveAll = async () => { // 验证所有倍率 for (const app of PRICING_APPS) { const trimmed = appConfigs[app].multiplier.trim(); if (!trimmed) { toast.error( `${t(`apps.${app}`)}: ${t("settings.globalProxy.defaultCostMultiplierRequired")}`, ); return; } if (!isNonNegativeDecimalString(trimmed)) { toast.error( `${t(`apps.${app}`)}: ${t("settings.globalProxy.defaultCostMultiplierInvalid")}`, ); return; } } setIsSaving(true); try { await Promise.all( PRICING_APPS.flatMap((app) => [ proxyApi.setDefaultCostMultiplier( app, appConfigs[app].multiplier.trim(), ), proxyApi.setPricingModelSource(app, appConfigs[app].source), ]), ); toast.success(t("settings.globalProxy.pricingSaved")); setOriginalConfigs({ ...appConfigs }); } catch (error) { const message = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error"; toast.error( t("settings.globalProxy.pricingSaveFailed", { error: message }), ); } finally { setIsSaving(false); } }; const handleDelete = (modelId: string) => { deleteMutation.mutate(modelId, { onSuccess: () => setDeleteConfirm(null), }); }; const handleAddNew = () => { setIsAddingNew(true); setEditingModel({ modelId: "", displayName: "", inputCostPerMillion: "0", outputCostPerMillion: "0", cacheReadCostPerMillion: "0", cacheCreationCostPerMillion: "0", }); }; if (isLoading) { return (
); } if (error) { return ( {t("usage.loadPricingError")}: {String(error)} ); } return (
{/* 全局计费默认配置 - 紧凑表格布局 */}

{t("settings.globalProxy.pricingDefaultsTitle")}

{t("settings.globalProxy.pricingDefaultsDescription")}

{isConfigLoading ? (
) : (
{PRICING_APPS.map((app, idx) => ( ))}
{t("settings.globalProxy.pricingAppLabel")} {t("settings.globalProxy.defaultCostMultiplierLabel")} {t("settings.globalProxy.pricingModelSourceLabel")}
{t(`apps.${app}`)} setAppConfigs((prev) => ({ ...prev, [app]: { ...prev[app], multiplier: e.target.value }, })) } disabled={isSaving} placeholder="1" className="h-7 w-24" />
)}
{/* 分隔线 */}
{/* 模型定价配置 */}

{t("usage.modelPricingDesc")} {t("usage.perMillion")}

{!pricing || pricing.length === 0 ? ( {t("usage.noPricingData")} ) : (
{t("usage.model")} {t("usage.displayName")} {t("usage.inputCost")} {t("usage.outputCost")} {t("usage.cacheReadCost")} {t("usage.cacheWriteCost")} {t("common.actions")} {pricing.map((model) => ( {model.modelId} {model.displayName} ${model.inputCostPerMillion} ${model.outputCostPerMillion} ${model.cacheReadCostPerMillion} ${model.cacheCreationCostPerMillion}
))}
)}
{editingModel && ( { setEditingModel(null); setIsAddingNew(false); }} /> )} setDeleteConfirm(null)} > {t("usage.deleteConfirmTitle")} {t("usage.deleteConfirmDesc")}
); }