import { z } from "zod"; import { requireAdmin } from "@/lib/middleware/admin"; import { getAllSystemSettings, getSystemSetting, upsertSystemSetting, } from "@/lib/db/queries"; import { ChatSDKError } from "@/lib/errors"; import { probeBflModelSupport } from "@/lib/realtime/bfl-capability"; import { probeFalModelSupport } from "@/lib/realtime/fal-capability"; import { probeReplicateModelSupport } from "@/lib/realtime/replicate-capability"; import { normalizeRealtimeCanvasConfigForStorage, resolveRealtimeProviders, } from "@/lib/realtime/config"; import { DETAIL_AGENT_LOCAL_MOONSHOT_BASE_URL, DETAIL_AGENT_LOCAL_MOONSHOT_DISPLAY_NAME, DETAIL_AGENT_LOCAL_MOONSHOT_MODEL_ID, DETAIL_AGENT_LOCAL_MOONSHOT_SETTING_KEY, } from "@/lib/ai/detail-agent-model-config"; import { normalizeProductPlanningApiConfig, PRODUCT_PLANNING_API_CONFIG_KEY, } from "@/lib/product-planning/config"; import { normalizeProductPlanningAiModelConfig, PRODUCT_PLANNING_AI_MODEL_CONFIG_KEY, } from "@/lib/product-planning/model-config"; import { normalizeThirdPartySqlServerConfig, THIRD_PARTY_SQLSERVER_CONFIG_KEY, } from "@/lib/sqlserver/config"; import { ENHANCE_PRO_KREA_SETTING_KEY, normalizeEnhanceProKreaConfig, } from "@/lib/enhance-pro/config"; import { normalizeWorldLabsKeyPoolConfig, WORLDLABS_KEY_POOL_CONFIG_KEY, } from "@/lib/worldlabs/key-pool-config-shared"; import { normalizeThreeDModelAliasConfig, THREED_MODEL_ALIAS_CONFIG_SETTING_KEY, } from "@/lib/model3d/model-alias-config"; import { normalizeVideoCanvasFunctionConfig, VIDEO_CANVAS_FUNCTION_CONFIG_KEY, } from "@/lib/video-canvas/config"; import { CANVAS_IMAGE_TASK_QUEUE_CONFIG_KEY, normalizeCanvasImageTaskQueueConfig, } from "@/lib/canvas/image-task-config"; import { DINGTALK_ROBOT_NOTIFICATION_CONFIG_KEY, normalizeDingTalkRobotNotificationConfig, } from "@/lib/notifications/dingtalk-robot-config"; import { DINGTALK_NOTIFICATION_DRAFT_CONFIG_KEY, normalizeDingTalkNotificationDraftConfig, } from "@/lib/notifications/dingtalk-notification-draft-config"; const settingSchema = z.object({ key: z.string(), value: z.any(), }); const moonshotDetailAgentConfigSchema = z.object({ 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 = DETAIL_AGENT_LOCAL_MOONSHOT_BASE_URL; 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 }); if (key === CANVAS_IMAGE_TASK_QUEUE_CONFIG_KEY) { const value = normalizeCanvasImageTaskQueueConfig(setting?.value, { envConcurrency: process.env.CANVAS_IMAGE_TASK_CONCURRENCY, }); return Response.json({ setting: setting ? { ...setting, value } : { key, value }, }); } if (key === DINGTALK_ROBOT_NOTIFICATION_CONFIG_KEY) { const value = normalizeDingTalkRobotNotificationConfig(setting?.value); return Response.json({ setting: setting ? { ...setting, value } : { key, value }, }); } if (key === DINGTALK_NOTIFICATION_DRAFT_CONFIG_KEY) { const value = normalizeDingTalkNotificationDraftConfig(setting?.value); return Response.json({ setting: setting ? { ...setting, value } : { key, value }, }); } 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 === DETAIL_AGENT_LOCAL_MOONSHOT_SETTING_KEY) { const existingSetting = await getSystemSetting({ key: DETAIL_AGENT_LOCAL_MOONSHOT_SETTING_KEY, }).catch(() => null); const existingValue = existingSetting?.value && typeof existingSetting.value === "object" ? (existingSetting.value as Record) : {}; const config = moonshotDetailAgentConfigSchema.parse(value || {}); const normalizedBaseUrl = String(config.baseUrl || "").trim() || MOONSHOT_OFFICIAL_BASE_URL; const rawModelId = String(config.modelId || "").trim(); const rawDisplayName = String(config.displayName || "").trim(); const normalizedModelId = !rawModelId || rawModelId === "kimi-k2.5" ? DETAIL_AGENT_LOCAL_MOONSHOT_MODEL_ID : rawModelId; const normalizedDisplayName = !rawDisplayName || rawDisplayName === "官方 Kimi K2.5" ? DETAIL_AGENT_LOCAL_MOONSHOT_DISPLAY_NAME : rawDisplayName; const normalizedProviderName = String(config.providerName || "").trim() || "Moonshot AI (Official)"; const normalizedApiKey = String(config.apiKey || "").trim() || String(existingValue.apiKey || "").trim(); const enabled = config.isEnabled !== false; if (!isMoonshotOfficialBaseUrl(normalizedBaseUrl)) { return new ChatSDKError( "bad_request:api", `Moonshot 官方地址必须为 ${MOONSHOT_OFFICIAL_BASE_URL}`, ).toResponse(); } if (!normalizedApiKey) { return new ChatSDKError("bad_request:api", "缺少 Moonshot 官方 API Key").toResponse(); } const saved = await upsertSystemSetting({ key: DETAIL_AGENT_LOCAL_MOONSHOT_SETTING_KEY, value: { enabled, providerName: normalizedProviderName, baseUrl: normalizedBaseUrl, apiKey: normalizedApiKey, modelId: normalizedModelId, displayName: normalizedDisplayName, }, updatedBy: session.user.id, }); return Response.json({ setting: saved[0], }); } 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, }) : provider.type === "fal" ? await probeFalModelSupport({ apiKey: provider.apiKey, modelId: provider.model, }) : 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; } if (key === VIDEO_CANVAS_FUNCTION_CONFIG_KEY) { nextValue = normalizeVideoCanvasFunctionConfig(value); } if (key === CANVAS_IMAGE_TASK_QUEUE_CONFIG_KEY) { nextValue = normalizeCanvasImageTaskQueueConfig(value); } if (key === DINGTALK_ROBOT_NOTIFICATION_CONFIG_KEY) { nextValue = normalizeDingTalkRobotNotificationConfig(value); } if (key === DINGTALK_NOTIFICATION_DRAFT_CONFIG_KEY) { nextValue = normalizeDingTalkNotificationDraftConfig(value); } if (key === THIRD_PARTY_SQLSERVER_CONFIG_KEY) { const existingSetting = await getSystemSetting({ key: THIRD_PARTY_SQLSERVER_CONFIG_KEY, }).catch(() => null); const existingValue = existingSetting?.value && typeof existingSetting.value === "object" ? (existingSetting.value as Record) : {}; const normalizedValue = normalizeThirdPartySqlServerConfig( value, String(existingValue.password || "").trim(), ); if (normalizedValue.enabled) { if (!normalizedValue.host) { return new ChatSDKError("bad_request:api", "请填写 SQL Server 主机地址").toResponse(); } if (!normalizedValue.database) { return new ChatSDKError("bad_request:api", "请填写数据库名称").toResponse(); } if (!normalizedValue.username) { return new ChatSDKError("bad_request:api", "请填写数据库账号").toResponse(); } if (!normalizedValue.password) { return new ChatSDKError("bad_request:api", "请填写数据库密码").toResponse(); } } nextValue = normalizedValue; } if (key === PRODUCT_PLANNING_API_CONFIG_KEY) { const existingSetting = await getSystemSetting({ key: PRODUCT_PLANNING_API_CONFIG_KEY, }).catch(() => null); const existingValue = existingSetting?.value && typeof existingSetting.value === "object" ? (existingSetting.value as Record) : {}; nextValue = normalizeProductPlanningApiConfig(value, { taobaoApiKey: String(existingValue.taobaoApiKey || "").trim(), taobaoApiSecret: String(existingValue.taobaoApiSecret || "").trim(), detailPlusApiKey: String(existingValue.detailPlusApiKey || "").trim(), qaToken: String(existingValue.qaToken || "").trim(), saleToken: String(existingValue.saleToken || "").trim(), planningDbPassword: String(existingValue.planningDbPassword || "").trim(), }); } if (key === PRODUCT_PLANNING_AI_MODEL_CONFIG_KEY) { nextValue = normalizeProductPlanningAiModelConfig(value); } if (key === ENHANCE_PRO_KREA_SETTING_KEY) { const existingSetting = await getSystemSetting({ key: ENHANCE_PRO_KREA_SETTING_KEY, }).catch(() => null); const existingValue = existingSetting?.value && typeof existingSetting.value === "object" ? (existingSetting.value as Record) : {}; const normalizedValue = normalizeEnhanceProKreaConfig( value, String(existingValue.apiKey || "").trim(), ); if (normalizedValue.enabled) { if (!normalizedValue.baseUrl) { return new ChatSDKError("bad_request:api", "请填写 Krea API 地址").toResponse(); } if (!normalizedValue.apiKey && normalizedValue.apiKeys.length === 0) { return new ChatSDKError("bad_request:api", "请填写 Krea API Key 或 Key 池").toResponse(); } } nextValue = normalizedValue; } if (key === WORLDLABS_KEY_POOL_CONFIG_KEY) { nextValue = normalizeWorldLabsKeyPoolConfig(value); } if (key === THREED_MODEL_ALIAS_CONFIG_SETTING_KEY) { nextValue = normalizeThreeDModelAliasConfig(value); } 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(); } }