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, isDetailAgentLocalMoonshotBaseUrl, normalizeDetailAgentLocalMoonshotBaseUrl, } 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 { CANVAS_LOCAL_EDIT_CONFIG_KEY, normalizeCanvasLocalEditConfig, resolveCanvasLocalEditModelRef, } from "@/lib/canvas/local-edit-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"; import { STYLE_PRO_CONFIG_KEY, normalizeStyleProConfig, } from "@/lib/style-pro/config"; import { getStyleProImageModelEntries } from "@/lib/style-pro/model-options"; import { EXTERNAL_IMAGE_API_CONFIG_KEY, normalizeExternalImageApiConfig, } from "@/lib/external-image-api/config"; import { AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY, normalizeAgentWorkflowMaterialApiConfig, } from "@/lib/agents/workflow-material-config"; import { normalizeOpenClawChatConfig, OPENCLAW_CHAT_CONFIG_KEY, toAdminOpenClawChatConfig, } from "@/lib/openclaw-chat/config"; import { clearScopedSiteSettingsCache } from "@/lib/site-settings/public-cache"; 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 isDetailAgentLocalMoonshotBaseUrl(value); } 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 === CANVAS_LOCAL_EDIT_CONFIG_KEY) { const value = normalizeCanvasLocalEditConfig(setting?.value); 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 }, }); } if (key === STYLE_PRO_CONFIG_KEY) { const value = normalizeStyleProConfig(setting?.value); return Response.json({ setting: setting ? { ...setting, value } : { key, value }, }); } if (key === EXTERNAL_IMAGE_API_CONFIG_KEY) { const value = normalizeExternalImageApiConfig(setting?.value); return Response.json({ setting: setting ? { ...setting, value } : { key, value }, }); } if (key === AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY) { const value = normalizeAgentWorkflowMaterialApiConfig(setting?.value); return Response.json({ setting: setting ? { ...setting, value } : { key, value }, }); } if (key === OPENCLAW_CHAT_CONFIG_KEY) { const value = toAdminOpenClawChatConfig(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 = normalizeDetailAgentLocalMoonshotBaseUrl(config.baseUrl); const rawModelId = String(config.modelId || "").trim(); const rawDisplayName = String(config.displayName || "").trim(); const normalizedModelId = !rawModelId || rawModelId === "kimi-k2.5" || rawModelId === "kimi-k2.6" || rawModelId === "kimi-k2.7-code" || rawModelId === "kimi-k2.7-code-highspeed" ? DETAIL_AGENT_LOCAL_MOONSHOT_MODEL_ID : rawModelId; const normalizedDisplayName = !rawDisplayName || rawDisplayName === "官方 Kimi K2.5" || rawDisplayName === "官方 Kimi K2.6" || rawDisplayName === "官方 Kimi K2.7 Code Highspeed" || rawDisplayName === "Kimi Code 编程套餐 K2.7 Highspeed" ? DETAIL_AGENT_LOCAL_MOONSHOT_DISPLAY_NAME : rawDisplayName; const normalizedProviderName = String(config.providerName || "").trim() || "Kimi Code 编程套餐"; const normalizedApiKey = String(config.apiKey || "").trim() || String(existingValue.apiKey || "").trim(); const enabled = config.isEnabled !== false; if (!isMoonshotOfficialBaseUrl(normalizedBaseUrl)) { return new ChatSDKError( "bad_request:api", `Kimi Code 编程套餐专用地址必须为 ${MOONSHOT_OFFICIAL_BASE_URL}`, ).toResponse(); } if (!normalizedApiKey) { return new ChatSDKError("bad_request:api", "缺少 Kimi Code 控制台 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 === EXTERNAL_IMAGE_API_CONFIG_KEY) { const saved = await upsertSystemSetting({ key: EXTERNAL_IMAGE_API_CONFIG_KEY, value: normalizeExternalImageApiConfig(value), updatedBy: session.user.id, }); clearScopedSiteSettingsCache(); return Response.json({ setting: saved[0], }); } if (key === AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY) { const saved = await upsertSystemSetting({ key: AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY, value: normalizeAgentWorkflowMaterialApiConfig(value), updatedBy: session.user.id, }); clearScopedSiteSettingsCache(); return Response.json({ setting: saved[0], }); } if (key === OPENCLAW_CHAT_CONFIG_KEY) { const existingSetting = await getSystemSetting({ key: OPENCLAW_CHAT_CONFIG_KEY, }).catch(() => null); const existingValue = existingSetting?.value && typeof existingSetting.value === "object" ? (existingSetting.value as Record) : {}; const normalizedValue = normalizeOpenClawChatConfig( value, String(existingValue.token || "").trim() ); if (normalizedValue.enabled) { if (!normalizedValue.baseUrl) { return new ChatSDKError("bad_request:api", "请填写大龙虾聊天接口地址").toResponse(); } if (!normalizedValue.token) { return new ChatSDKError("bad_request:api", "请填写大龙虾聊天接口 Token").toResponse(); } } const saved = await upsertSystemSetting({ key: OPENCLAW_CHAT_CONFIG_KEY, value: normalizedValue, updatedBy: session.user.id, }); clearScopedSiteSettingsCache(); return Response.json({ setting: saved[0] ? { ...saved[0], value: toAdminOpenClawChatConfig(saved[0].value), } : undefined, }); } if (key === VIDEO_CANVAS_FUNCTION_CONFIG_KEY) { nextValue = normalizeVideoCanvasFunctionConfig(value); } if (key === CANVAS_IMAGE_TASK_QUEUE_CONFIG_KEY) { nextValue = normalizeCanvasImageTaskQueueConfig(value); } if (key === CANVAS_LOCAL_EDIT_CONFIG_KEY) { const normalizedValue = normalizeCanvasLocalEditConfig(value); const imageModels = ( await getStyleProImageModelEntries({ includeHidden: true }) ).map(({ model }) => ({ id: model.id, modelId: model.modelId })); const localEditModel = resolveCanvasLocalEditModelRef( imageModels, normalizedValue.modelId ); const scaleAdjustModel = resolveCanvasLocalEditModelRef( imageModels, normalizedValue.scaleAdjustModelId ); if (normalizedValue.modelId && !localEditModel) { return new ChatSDKError( "bad_request:api", "普通局部编辑模型不存在、已停用或不支持生图" ).toResponse(); } if (normalizedValue.scaleAdjustModelId && !scaleAdjustModel) { return new ChatSDKError( "bad_request:api", "比例调整模型不存在、已停用或不支持生图" ).toResponse(); } nextValue = { modelId: localEditModel?.id || "", scaleAdjustModelId: scaleAdjustModel?.id || "", }; } if (key === DINGTALK_ROBOT_NOTIFICATION_CONFIG_KEY) { nextValue = normalizeDingTalkRobotNotificationConfig(value); } if (key === DINGTALK_NOTIFICATION_DRAFT_CONFIG_KEY) { nextValue = normalizeDingTalkNotificationDraftConfig(value); } if (key === STYLE_PRO_CONFIG_KEY) { nextValue = normalizeStyleProConfig(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, }); clearScopedSiteSettingsCache(); 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(); } }