165 lines
4.7 KiB
TypeScript
165 lines
4.7 KiB
TypeScript
import { type ActionFunctionArgs, type LoaderFunctionArgs, json } from "@remix-run/server-runtime";
|
|
import { z } from "zod";
|
|
import { prisma } from "~/db.server";
|
|
import { requireAdminApiRequest } from "~/services/personalAccessToken.server";
|
|
|
|
export async function loader({ request, params }: LoaderFunctionArgs) {
|
|
await requireAdminApiRequest(request);
|
|
|
|
const model = await prisma.llmModel.findUnique({
|
|
where: { id: params.modelId },
|
|
include: {
|
|
pricingTiers: {
|
|
include: { prices: true },
|
|
orderBy: { priority: "asc" },
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!model) {
|
|
return json({ error: "Model not found" }, { status: 404 });
|
|
}
|
|
|
|
return json({ model });
|
|
}
|
|
|
|
const UpdateModelSchema = z.object({
|
|
modelName: z.string().min(1).optional(),
|
|
matchPattern: z.string().min(1).optional(),
|
|
startDate: z.string().nullable().optional(),
|
|
provider: z.string().nullable().optional(),
|
|
description: z.string().nullable().optional(),
|
|
contextWindow: z.number().int().nullable().optional(),
|
|
maxOutputTokens: z.number().int().nullable().optional(),
|
|
capabilities: z.array(z.string()).optional(),
|
|
isHidden: z.boolean().optional(),
|
|
pricingUnit: z.string().nullable().optional(),
|
|
pricingTiers: z
|
|
.array(
|
|
z.object({
|
|
name: z.string().min(1),
|
|
isDefault: z.boolean().default(true),
|
|
priority: z.number().int().default(0),
|
|
conditions: z
|
|
.array(
|
|
z.object({
|
|
usageDetailPattern: z.string(),
|
|
operator: z.enum(["gt", "gte", "lt", "lte", "eq", "neq"]),
|
|
value: z.number(),
|
|
})
|
|
)
|
|
.default([]),
|
|
prices: z.record(z.string(), z.number()),
|
|
})
|
|
)
|
|
.optional(),
|
|
});
|
|
|
|
export async function action({ request, params }: ActionFunctionArgs) {
|
|
await requireAdminApiRequest(request);
|
|
|
|
const modelId = params.modelId!;
|
|
|
|
if (request.method === "DELETE") {
|
|
const existing = await prisma.llmModel.findUnique({ where: { id: modelId } });
|
|
if (!existing) {
|
|
return json({ error: "Model not found" }, { status: 404 });
|
|
}
|
|
|
|
await prisma.llmModel.delete({ where: { id: modelId } });
|
|
return json({ success: true });
|
|
}
|
|
|
|
if (request.method !== "PUT") {
|
|
return json({ error: "Method not allowed" }, { status: 405 });
|
|
}
|
|
|
|
let body: unknown;
|
|
try {
|
|
body = await request.json();
|
|
} catch {
|
|
return json({ error: "Invalid JSON body" }, { status: 400 });
|
|
}
|
|
|
|
const parsed = UpdateModelSchema.safeParse(body);
|
|
|
|
if (!parsed.success) {
|
|
return json({ error: "Invalid request body", details: parsed.error.issues }, { status: 400 });
|
|
}
|
|
|
|
const {
|
|
modelName,
|
|
matchPattern,
|
|
startDate,
|
|
pricingTiers,
|
|
provider,
|
|
description,
|
|
contextWindow,
|
|
maxOutputTokens,
|
|
capabilities,
|
|
isHidden,
|
|
pricingUnit,
|
|
} = parsed.data;
|
|
|
|
// Validate regex if provided — strip (?i) POSIX flag since our registry handles it
|
|
if (matchPattern) {
|
|
try {
|
|
const testPattern = matchPattern.startsWith("(?i)") ? matchPattern.slice(4) : matchPattern;
|
|
new RegExp(testPattern);
|
|
} catch {
|
|
return json({ error: "Invalid regex in matchPattern" }, { status: 400 });
|
|
}
|
|
}
|
|
|
|
// Update model + tiers atomically
|
|
const updated = await prisma.$transaction(async (tx) => {
|
|
await tx.llmModel.update({
|
|
where: { id: modelId },
|
|
data: {
|
|
...(modelName !== undefined && { modelName }),
|
|
...(matchPattern !== undefined && { matchPattern }),
|
|
...(startDate !== undefined && { startDate: startDate ? new Date(startDate) : null }),
|
|
...(provider !== undefined && { provider }),
|
|
...(description !== undefined && { description }),
|
|
...(contextWindow !== undefined && { contextWindow }),
|
|
...(maxOutputTokens !== undefined && { maxOutputTokens }),
|
|
...(capabilities !== undefined && { capabilities }),
|
|
...(isHidden !== undefined && { isHidden }),
|
|
...(pricingUnit !== undefined && { pricingUnit }),
|
|
},
|
|
});
|
|
|
|
if (pricingTiers) {
|
|
await tx.llmPricingTier.deleteMany({ where: { modelId } });
|
|
|
|
for (const tier of pricingTiers) {
|
|
await tx.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,
|
|
})),
|
|
},
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
return tx.llmModel.findUnique({
|
|
where: { id: modelId },
|
|
include: {
|
|
pricingTiers: { include: { prices: true }, orderBy: { priority: "asc" } },
|
|
},
|
|
});
|
|
});
|
|
|
|
return json({ model: updated });
|
|
}
|