import { Form, useActionData, useNavigate } from "@remix-run/react"; import { redirect } from "@remix-run/server-runtime"; import { useState } from "react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; import { Button, LinkButton } from "~/components/primitives/Buttons"; import { Input } from "~/components/primitives/Input"; import { prisma } from "~/db.server"; import { dashboardAction, dashboardLoader } from "~/services/routeBuilders/dashboardBuilder"; import { llmPricingRegistry } from "~/v3/llmPricingRegistry.server"; const ParamsSchema = z.object({ modelId: z.string(), }); export const loader = dashboardLoader( { authorization: { requireSuper: true }, params: ParamsSchema }, async ({ params }) => { const model = await prisma.llmModel.findUnique({ where: { friendlyId: params.modelId }, include: { pricingTiers: { include: { prices: true }, orderBy: { priority: "asc" } }, }, }); if (!model) throw new Response("Model not found", { status: 404 }); // Convert Prisma Decimal to plain numbers for serialization const serialized = { ...model, pricingTiers: model.pricingTiers.map((t) => ({ ...t, prices: t.prices.map((p) => ({ ...p, price: Number(p.price) })), })), }; return typedjson({ model: serialized }); } ); const SaveSchema = z.object({ modelName: z.string().min(1), matchPattern: z.string().min(1), pricingTiersJson: z.string(), provider: z.string().optional(), description: z.string().optional(), contextWindow: z.string().optional(), maxOutputTokens: z.string().optional(), capabilities: z.string().optional(), isHidden: z.string().optional(), pricingUnit: z.string().optional(), }); export const action = dashboardAction( { authorization: { requireSuper: true }, params: ParamsSchema }, async ({ params, request }) => { const friendlyId = params.modelId; const existing = await prisma.llmModel.findUnique({ where: { friendlyId } }); if (!existing) throw new Response("Model not found", { status: 404 }); const modelId = existing.id; const formData = await request.formData(); const _action = formData.get("_action"); if (_action === "delete") { await prisma.llmModel.delete({ where: { id: modelId } }); await llmPricingRegistry?.reload(); return redirect("/admin/llm-models"); } if (_action === "save") { const raw = Object.fromEntries(formData); const parsed = SaveSchema.safeParse(raw); if (!parsed.success) { return typedjson( { error: "Invalid form data", details: parsed.error.issues }, { status: 400 } ); } const { modelName, matchPattern, pricingTiersJson } = parsed.data; // Validate regex — strip (?i) POSIX flag since our registry handles it try { const testPattern = matchPattern.startsWith("(?i)") ? matchPattern.slice(4) : matchPattern; new RegExp(testPattern); } catch { return typedjson({ error: "Invalid regex in matchPattern" }, { status: 400 }); } // Parse tiers let pricingTiers: Array<{ name: string; isDefault: boolean; priority: number; conditions: Array<{ usageDetailPattern: string; operator: string; value: number }>; prices: Record; }>; try { pricingTiers = JSON.parse(pricingTiersJson) as typeof pricingTiers; } catch { return typedjson({ error: "Invalid pricing tiers JSON" }, { status: 400 }); } // Update model const { provider, description, contextWindow, maxOutputTokens, capabilities, isHidden, pricingUnit, } = parsed.data; await prisma.llmModel.update({ where: { id: modelId }, data: { modelName, matchPattern, provider: provider || null, description: description || null, contextWindow: contextWindow ? parseInt(contextWindow) || null : null, maxOutputTokens: maxOutputTokens ? parseInt(maxOutputTokens) || null : null, capabilities: capabilities ? capabilities .split(",") .map((s) => s.trim()) .filter(Boolean) : [], isHidden: isHidden === "on", pricingUnit: pricingUnit || null, }, }); // Replace tiers await prisma.llmPricingTier.deleteMany({ where: { modelId } }); for (const tier of pricingTiers) { await prisma.llmPricingTier.create({ data: { modelId, name: tier.name, isDefault: tier.isDefault, priority: tier.priority, conditions: tier.conditions, prices: { create: Object.entries(tier.prices).map(([usageType, price]) => ({ modelId, usageType, price, })), }, }, }); } await llmPricingRegistry?.reload(); return typedjson({ success: true }); } return typedjson({ error: "Unknown action" }, { status: 400 }); } ); export default function AdminLlmModelDetailRoute() { const { model } = useTypedLoaderData(); const actionData = useActionData<{ success?: boolean; error?: string; details?: unknown[] }>(); const _navigate = useNavigate(); const [modelName, setModelName] = useState(model.modelName); const [matchPattern, setMatchPattern] = useState(model.matchPattern); const [provider, setProvider] = useState(model.provider ?? ""); const [description, setDescription] = useState(model.description ?? ""); const [contextWindow, setContextWindow] = useState(model.contextWindow?.toString() ?? ""); const [maxOutputTokens, setMaxOutputTokens] = useState(model.maxOutputTokens?.toString() ?? ""); const [capabilities, setCapabilities] = useState(model.capabilities?.join(", ") ?? ""); const [isHidden, setIsHidden] = useState(model.isHidden ?? false); const [pricingUnit, setPricingUnit] = useState(model.pricingUnit ?? ""); const [testInput, setTestInput] = useState(""); const [tiers, setTiers] = useState(() => model.pricingTiers.map((t) => ({ name: t.name, isDefault: t.isDefault, priority: t.priority, conditions: (t.conditions ?? []) as Array<{ usageDetailPattern: string; operator: string; value: number; }>, prices: Object.fromEntries(t.prices.map((p) => [p.usageType, p.price])), })) ); // Test regex match let testResult: boolean | null = null; if (testInput) { try { const pattern = matchPattern.startsWith("(?i)") ? matchPattern.slice(4) : matchPattern; testResult = new RegExp(pattern, "i").test(testInput); } catch { testResult = null; } } return (

{model.modelName}

{model.source ?? "default"} Back to list
{/* Model fields */}
setModelName(e.target.value)} variant="medium" fullWidth />
setMatchPattern(e.target.value)} variant="medium" fullWidth className="font-mono text-xs" />
{/* Test pattern */}
setTestInput(e.target.value)} placeholder="Type a model name to test..." variant="medium" fullWidth /> {testInput && ( {testResult ? "Match" : "No match"} )}
{/* Catalog metadata */}
setProvider(e.target.value)} variant="medium" fullWidth placeholder="openai, anthropic, google" />
setContextWindow(e.target.value)} variant="medium" fullWidth placeholder="128000" />
setDescription(e.target.value)} variant="medium" fullWidth placeholder="Brief model description" />
setMaxOutputTokens(e.target.value)} variant="medium" fullWidth placeholder="16384" />
setCapabilities(e.target.value)} variant="medium" fullWidth placeholder="vision, tool_use, streaming, json_mode" />
{/* Pricing tiers */}
{tiers.map((tier, tierIdx) => ( { const next = [...tiers]; next[tierIdx] = updated; setTiers(next); }} onRemove={() => setTiers(tiers.filter((_, i) => i !== tierIdx))} /> ))}
{actionData?.error && (
{actionData.error} {actionData.details && (
                    {JSON.stringify(actionData.details, null, 2)}
                  
)}
)} {/* Actions */}
Cancel
{/* Delete section */}
{ if (!confirm(`Delete model "${model.modelName}"?`)) e.preventDefault(); }} >
); } // --------------------------------------------------------------------------- // Tier editor sub-component // --------------------------------------------------------------------------- type TierData = { name: string; isDefault: boolean; priority: number; conditions: Array<{ usageDetailPattern: string; operator: string; value: number }>; prices: Record; }; const PRICING_UNITS = [ "tokens", "characters", "images", "minutes", "requests", "free", "not_findable", ]; const COMMON_USAGE_TYPES = [ "input", "output", "input_cached_tokens", "cache_creation_input_tokens", "reasoning_tokens", ]; function TierEditor({ tier, onChange, onRemove, }: { tier: TierData; onChange: (t: TierData) => void; onRemove: () => void; }) { const [newUsageType, setNewUsageType] = useState(""); return (
onChange({ ...tier, name: e.target.value })} placeholder="Tier name" />
{/* Prices */}
Prices (per token)
{Object.entries(tier.prices).map(([usageType, price]) => (
{usageType} { const val = parseFloat(e.target.value); if (!isNaN(val)) { onChange({ ...tier, prices: { ...tier.prices, [usageType]: val }, }); } }} />
))}
{/* Add price */}
{newUsageType && ( )}
); }