import { and, eq } from "drizzle-orm"; import { NextResponse } from "next/server"; import { fetchVip98kModelPresets, hasVip98kTextToImageMode, type Vip98kModelPreset, } from "@/lib/ai/vip98k-models"; import { db } from "@/lib/db"; import { deleteAiModel, getAiModelsByProviderId, getAiProviderById, } from "@/lib/db/queries"; import { aiModel, type AiModel } from "@/lib/db/schema"; import { requireAdmin } from "@/lib/middleware/admin"; export const runtime = "nodejs"; export const maxDuration = 300; function summarizeByType(presets: Vip98kModelPreset[]) { return presets.reduce>((acc, preset) => { const type = String(preset.type || "model"); acc[type] = (acc[type] || 0) + 1; return acc; }, {}); } export async function GET(request: Request) { try { await requireAdmin(); const { searchParams } = new URL(request.url); const providerId = String(searchParams.get("providerId") || "").trim(); if (!providerId) { return NextResponse.json( { error: "Provider ID is required to fetch VIP98K catalog" }, { status: 400 }, ); } const provider = await getAiProviderById({ id: providerId }); if (!provider || provider.type !== "vip98k") { return NextResponse.json( { error: "Provider must be a VIP98K provider" }, { status: 400 }, ); } const presets = await fetchVip98kModelPresets({ baseURL: provider.baseUrl, apiKey: provider.apiKey, }); return NextResponse.json({ presets, counts: { total: presets.length, byType: summarizeByType(presets), }, }); } catch (error) { console.error("[VIP98K Models API] GET error:", error); return NextResponse.json( { error: error instanceof Error ? error.message : "Failed to fetch VIP98K models", }, { status: 500 }, ); } } export async function POST(request: Request) { try { await requireAdmin(); const body = await request.json(); const providerId = String(body?.providerId || "").trim(); const requestedModelIds = Array.isArray(body?.modelIds) ? new Set( body.modelIds.map((item: unknown) => String(item || "").trim()).filter(Boolean), ) : null; if (!providerId) { return NextResponse.json({ error: "Provider ID is required" }, { status: 400 }); } const provider = await getAiProviderById({ id: providerId }); if (!provider || provider.type !== "vip98k") { return NextResponse.json( { error: "Provider must be a VIP98K provider" }, { status: 400 }, ); } const catalogPresets = await fetchVip98kModelPresets({ baseURL: provider.baseUrl, apiKey: provider.apiKey, }); const presets = requestedModelIds ? catalogPresets.filter((preset) => requestedModelIds.has(preset.modelId)) : catalogPresets; const addedModels: AiModel[] = []; const updatedModels: AiModel[] = []; const removedModels: AiModel[] = []; const errors: Array<{ modelId: string; error: string }> = []; const existingModels = await getAiModelsByProviderId({ providerId }); const textToImageModels = existingModels.filter( (model) => model.supportsImageGen && hasVip98kTextToImageMode({ modelId: model.modelId, capabilities: model.capabilities, defaultParams: model.defaultParams, }), ); for (const model of textToImageModels) { try { const deleted = await deleteAiModel({ id: model.id }); removedModels.push(...deleted); } catch (error) { console.error( `[VIP98K Models API] Failed to remove text-to-image model ${model.modelId}:`, error, ); errors.push({ modelId: model.modelId, error: error instanceof Error ? error.message : "Removal failed", }); } } for (const preset of presets) { try { const existing = await db.query.aiModel.findFirst({ where: and( eq(aiModel.providerId, providerId), eq(aiModel.modelId, preset.modelId), ), }); const values = { displayName: preset.displayName, avatarUrl: null, description: preset.description, supportsVision: preset.supportsVision, supportsImageGen: preset.supportsImageGen, isEnabled: true, isHidden: preset.isHidden, sortOrder: preset.sortOrder, capabilities: preset.capabilities, defaultParams: preset.defaultParams, imageGenConfig: preset.imageGenConfig, updatedAt: new Date(), }; if (existing) { const [updated] = await db .update(aiModel) .set(values) .where( and( eq(aiModel.providerId, providerId), eq(aiModel.modelId, preset.modelId), ), ) .returning(); if (updated) updatedModels.push(updated); } else { const [created] = await db .insert(aiModel) .values({ providerId, modelId: preset.modelId, ...values, isDefault: false, isSubModel: false, parentModelId: null, }) .returning(); if (created) addedModels.push(created); } } catch (error) { console.error(`[VIP98K Models API] Failed to sync ${preset.modelId}:`, error); errors.push({ modelId: preset.modelId, error: error instanceof Error ? error.message : "Sync failed", }); } } return NextResponse.json({ success: true, addedCount: addedModels.length, updatedCount: updatedModels.length, removedCount: removedModels.length, addedModels, updatedModels, removedModels, errors: errors.length > 0 ? errors : undefined, counts: { requested: presets.length, total: catalogPresets.length, byType: summarizeByType(catalogPresets), }, }); } catch (error) { console.error("[VIP98K Models API] POST error:", error); return NextResponse.json( { error: error instanceof Error ? error.message : "Failed to sync VIP98K models", }, { status: 500 }, ); } }