import { z } from "zod"; import { requireAdmin } from "@/lib/middleware/admin"; import { createAiModel, createAiProvider, getAiModelById, getAiProviderById, getAllAiModels, getAllSystemSettings, getSystemSetting, upsertSystemSetting, updateAiModel, updateAiProvider, } from "@/lib/db/queries"; import { ChatSDKError } from "@/lib/errors"; import { probeBflModelSupport } from "@/lib/realtime/bfl-capability"; import { probeReplicateModelSupport } from "@/lib/realtime/replicate-capability"; import { normalizeRealtimeCanvasConfigForStorage, resolveRealtimeProviders, } from "@/lib/realtime/config"; import { db } from "@/lib/db"; import { siteSettings } from "@/lib/db/schema"; import { eq } from "drizzle-orm"; const settingSchema = z.object({ key: z.string(), value: z.any(), }); const moonshotDetailAgentConfigSchema = z.object({ providerId: z.string().uuid().optional(), modelEntryId: z.string().uuid().optional(), providerName: z.string().trim().min(1).max(128).optional(), baseUrl: z.string().trim().min(1), apiKey: z.string().optional(), modelId: z.string().trim().min(1).max(128).optional(), displayName: z.string().trim().min(1).max(128).optional(), isEnabled: z.boolean().optional(), }); const MOONSHOT_OFFICIAL_BASE_URL = "https://api.moonshot.cn/v1"; const MOONSHOT_KIMI_K25_MODEL_ID = "kimi-k2.5"; function isMoonshotOfficialBaseUrl(value: string) { return String(value || "").trim().replace(/\/+$/, "") === MOONSHOT_OFFICIAL_BASE_URL; } function isObjectLike(value: unknown): value is Record { return Boolean(value) && typeof value === "object" && !Array.isArray(value); } function mapProbeStatus(code: string) { if (code === "UPSTREAM_ERROR") return 502; return 400; } export async function GET(request: Request) { try { await requireAdmin(); const { searchParams } = new URL(request.url); const key = searchParams.get("key"); if (key) { const setting = await getSystemSetting({ key }); return Response.json({ setting }); } const settings = await getAllSystemSettings(); return Response.json({ settings }); } catch (error) { if (error instanceof ChatSDKError) { return error.toResponse(); } return new ChatSDKError("offline:admin").toResponse(); } } export async function POST(request: Request) { try { const session = await requireAdmin(); const body = await request.json(); const { key, value } = settingSchema.parse(body); let nextValue = value; if (key === "moonshot_detail_agent_config") { const config = moonshotDetailAgentConfigSchema.parse(value || {}); const normalizedBaseUrl = String(config.baseUrl || "").trim() || MOONSHOT_OFFICIAL_BASE_URL; const normalizedModelId = String(config.modelId || "").trim() || MOONSHOT_KIMI_K25_MODEL_ID; const normalizedDisplayName = String(config.displayName || "").trim() || "官方 Kimi K2.5"; const normalizedProviderName = String(config.providerName || "").trim() || "Moonshot AI (Official)"; const normalizedApiKey = String(config.apiKey || "").trim(); const enabled = config.isEnabled !== false; if (!isMoonshotOfficialBaseUrl(normalizedBaseUrl)) { return new ChatSDKError( "bad_request:api", `Moonshot 官方地址必须为 ${MOONSHOT_OFFICIAL_BASE_URL}`, ).toResponse(); } if (!config.providerId && !normalizedApiKey) { return new ChatSDKError("bad_request:api", "缺少 Moonshot 官方 API Key").toResponse(); } const [existingSiteSettings] = await db.select().from(siteSettings).limit(1); const providerRows = config.providerId ? await updateAiProvider({ id: config.providerId, name: normalizedProviderName, type: "openai", baseUrl: normalizedBaseUrl, apiKey: normalizedApiKey || undefined, isEnabled: enabled, updatedBy: session.user.id, }) : await createAiProvider({ name: normalizedProviderName, type: "openai", baseUrl: normalizedBaseUrl, apiKey: normalizedApiKey, isEnabled: enabled, updatedBy: session.user.id, }); const provider = providerRows[0]; if (!provider) { return new ChatSDKError("bad_request:database", "保存 Moonshot 提供商失败").toResponse(); } let existingModel = config.modelEntryId ? await getAiModelById({ id: config.modelEntryId }) : undefined; if (!existingModel) { const allModels = await getAllAiModels(); existingModel = allModels.find( (model) => model.providerId === provider.id && model.modelId === normalizedModelId, ) || undefined; } const modelRows = existingModel ? await updateAiModel({ id: existingModel.id, providerId: provider.id, modelId: normalizedModelId, displayName: normalizedDisplayName, description: "Moonshot 官方 Kimi K2.5,专用于 AI详情生成 的规划与排版复审", supportsVision: true, supportsImageGen: false, isEnabled: enabled, isHidden: false, isDefault: false, isAgentModel: false, isMultimodal: true, }) : await createAiModel({ providerId: provider.id, modelId: normalizedModelId, displayName: normalizedDisplayName, description: "Moonshot 官方 Kimi K2.5,专用于 AI详情生成 的规划与排版复审", supportsVision: true, supportsImageGen: false, isEnabled: enabled, isHidden: false, isDefault: false, isAgentModel: false, isMultimodal: true, }); const model = modelRows[0]; if (!model) { return new ChatSDKError("bad_request:database", "保存 Kimi K2.5 模型失败").toResponse(); } if (existingSiteSettings) { await db .update(siteSettings) .set({ canvasDetailAgentModelId: model.id, updatedAt: new Date(), updatedBy: session.user.id, }) .where(eq(siteSettings.id, existingSiteSettings.id)); } else { await db.insert(siteSettings).values({ siteName: "AI Chatbot", canvasDetailAgentModelId: model.id, updatedBy: session.user.id, }); } return Response.json({ setting: { key: "moonshot_detail_agent_config", value: { providerId: provider.id, modelEntryId: model.id, providerName: provider.name, baseUrl: provider.baseUrl, modelId: model.modelId, displayName: model.displayName, isEnabled: model.isEnabled, canvasDetailAgentModelId: model.id, }, }, }); } if (key === "realtime_canvas_config") { if (!isObjectLike(value)) { return new ChatSDKError("bad_request:api", "realtime_canvas_config value 必须为对象").toResponse(); } const enabled = value.enabled !== false; const normalizedValue = normalizeRealtimeCanvasConfigForStorage(value); const providers = resolveRealtimeProviders(normalizedValue); if (enabled) { if (providers.length === 0) { return new ChatSDKError("bad_request:api", "启用实时画布必须至少配置一个可用服务").toResponse(); } for (const provider of providers) { const probeResult = provider.type === "replicate" ? await probeReplicateModelSupport({ apiKey: provider.apiKey, modelId: provider.model, baseUrl: provider.baseUrl, }) : await probeBflModelSupport({ apiKey: provider.apiKey, modelId: provider.model, baseUrl: provider.baseUrl, }); if (!probeResult.supported) { return Response.json( { code: probeResult.code, message: `${provider.label}校验失败:${probeResult.message}`, }, { status: mapProbeStatus(probeResult.code) } ); } } } nextValue = normalizedValue; } const setting = await upsertSystemSetting({ key, value: nextValue, updatedBy: session.user.id, }); return Response.json({ setting: setting[0] }); } catch (error) { if (error instanceof ChatSDKError) { return error.toResponse(); } if (error instanceof z.ZodError) { return new ChatSDKError("bad_request:api", error.message).toResponse(); } return new ChatSDKError("offline:admin").toResponse(); } }