import { NextRequest, NextResponse } from "next/server"; import { createHash } from "node:crypto"; import { z } from "zod"; import { db } from "@/lib/db"; import { aiProvider, siteSettings, systemSettingHistory, systemSettingSnapshot, systemSettings, } from "@/lib/db/schema"; import { and, eq, inArray } from "drizzle-orm"; import { auth } from "@/app/(auth)/auth"; import { isAgentWorkflowAdminType, isSiteAdminType } from "@/lib/middleware/admin"; import { defaultMembershipConfig, defaultMembershipUserOverrides, normalizeMembershipConfig, normalizeMembershipUserOverrides, } from "@/lib/membership/config"; import { defaultPointsPackages, defaultPointsSystemConfig, normalizePointsPackages, normalizePointsSystemConfig, POINTS_PACKAGES_KEY, POINTS_SYSTEM_CONFIG_KEY, } from "@/lib/points/config"; import { defaultDulupayConfig, DULUPAY_CONFIG_KEY, normalizeDulupayConfig, normalizeDulupayForPublic, } from "@/lib/payment/dulupay-config"; import { AGENT_SECONDARY_TYPE_CONFIG_KEY, defaultAgentSecondaryTypeConfig, normalizeAgentSecondaryTypeConfig, } from "@/lib/agents/secondary-type-config"; import { AGENT_WORKFLOW_BLOCKED_OPTION_KEYS_MAX, AGENT_WORKFLOW_UI_CONFIG_KEY, AGENT_WORKFLOW_PROMPT_FRAGMENT_MAX_LENGTH, defaultAgentWorkflowUiConfig, isAgentWorkflowDestructiveChangeSignificant, normalizeAgentWorkflowStepOrdering, normalizeAgentWorkflowUiConfig, summarizeAgentWorkflowDestructiveChange, } from "@/lib/agents/workflow-ui-config"; import { AGENT_WORKFLOW_BRAND_MODEL_CONFIG_KEY, defaultAgentWorkflowBrandModelConfig, normalizeAgentWorkflowBrandModelConfig, } from "@/lib/agents/workflow-brand-model-config"; import { AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY, defaultAgentWorkflowMaterialApiConfig, normalizeAgentWorkflowMaterialApiConfig, normalizeAgentWorkflowMaterialApiConfigForPublic, } from "@/lib/agents/workflow-material-config"; import { AGENT_WORKFLOW_GOODS_INFO_API_CONFIG_KEY, defaultAgentWorkflowGoodsInfoApiConfig, normalizeAgentWorkflowGoodsInfoApiConfig, normalizeAgentWorkflowGoodsInfoApiConfigForPublic, } from "@/lib/agents/workflow-goods-info-config"; import { AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY, defaultAgentWorkflowShowcaseConfig, normalizeAgentWorkflowShowcaseConfig, } from "@/lib/agents/workflow-showcase-config"; import { AGENT_PSD_TEXT_COLOR_CONFIG_KEY, defaultAgentPsdTextColorConfig, normalizeAgentPsdTextColorConfig, } from "@/lib/agents/psd-text-color-config"; import { AGENT_PSD_TEMPLATE_SECONDARY_CATEGORY_CONFIG_KEY, defaultAgentPsdTemplateSecondaryCategoryConfig, normalizeAgentPsdTemplateSecondaryCategoryConfig, } from "@/lib/agents/psd-template-secondary-category-config"; import { AGENT_PSD_TEMPLATE_CATEGORY_TAG_CONFIG_KEY, buildAgentPsdTemplateCategoryTagIndex, defaultAgentPsdTemplateCategoryTagConfig, defaultAgentPsdTemplateCategoryTagIndex, normalizeAgentPsdTemplateCategoryTagConfig, } from "@/lib/agents/psd-template-category-tag-config"; import { readAgentPsdTemplates } from "@/lib/agents/psd-template-settings"; import { AGENT_PRESET_LIBRARY_CONFIG_KEY, defaultAgentPresetLibraryConfig, normalizeAgentPresetLibraryConfig, } from "@/lib/agents/preset-library-config"; import { CANVAS_TOOLBAR_CONFIG_SETTING_KEY, defaultCanvasToolbarConfig, normalizeCanvasToolbarConfig, } from "@/lib/canvas-toolbar-config"; import { CANVAS_AGENT_ANNOTATION_EDIT_ENABLED_KEY, CANVAS_AGENT_FEATURE_SETTING_KEYS, CANVAS_AGENT_OPERATION_PROTOCOL_ENABLED_KEY, CANVAS_AGENT_SLOT_ENABLED_KEY, CANVAS_AGENT_VIEW_OPERATION_ENABLED_KEY, CANVAS_AGENT_WRITEBACK_MODE_KEY, canvasAgentFeatureFlagsToSettings, canvasAgentFeatureSettingsToSystemSettings, getCanvasAgentFeatureFlags, type CanvasAgentFeatureSettings, type CanvasAgentFeatureSettingsLike, } from "@/lib/canvas/agent-feature-flags"; import { LOVART_HOME_SIDEBAR_CONFIG_KEY, defaultLovartHomeSidebarConfig, normalizeLovartHomeSidebarConfig, } from "@/lib/home/lovart-sidebar-config"; import { defaultOssImageMigrationConfig, normalizeOssImageMigrationConfig, OSS_MIGRATION_CONFIG_KEY, } from "@/lib/storage/migration-config"; import { clearScopedSiteSettingsCache, deleteScopedSiteSettingsCache, getScopedSiteSettingsCache, PUBLIC_SITE_SETTINGS_CACHE_TTL_MS, setScopedSiteSettingsCache, } from "@/lib/site-settings/public-cache"; import { AGENT_WORKFLOW_SNAPSHOT_SCOPE, createSystemSettingSnapshotData, summarizeSystemSettingSnapshotData, } from "@/lib/system-settings/snapshot"; import { getHomeChatAnimationHtmlBytes, HOME_CHAT_ANIMATION_CONFIG_KEY, HOME_CHAT_ANIMATION_MAX_BYTES, normalizeHomeChatAnimationConfig, } from "@/lib/home/home-animation-config"; import { normalizePublicOpenClawChatConfig, OPENCLAW_CHAT_CONFIG_KEY, } from "@/lib/openclaw-chat/config"; import { ENHANCE_PRO_KREA_SETTING_KEY, normalizeEnhanceProKreaConfig, } from "@/lib/enhance-pro/config"; import { AI_WASH_CONFIG_KEY, normalizeAiWashConfig, } from "@/lib/ai-wash/config"; import { STYLE_PRO_CONFIG_KEY, normalizeStyleProConfig } from "@/lib/style-pro/config"; import { withTimeout } from "@/lib/public-api/performance"; import type { AgentCategoryTreeConfig } from "@/types/agent"; // Historically stored as "home_surprise_categories" but used for the featured gallery categories. const HOME_SURPRISE_CATEGORIES_KEY = "home_surprise_categories"; const AI_DETAIL_GENERATOR_ENABLED_KEY = "ai_detail_generator_enabled"; const LOVART_COMPOSER_PREVIEW_TEXTS_KEY = "lovart_composer_preview_texts"; const LOVART_COMPOSER_PREFIX_KEY = "lovart_composer_prefix"; const LOVART_PROMPT_TAG_VISIBILITY_KEY = "lovart_prompt_tag_visibility"; const CONTACT_CONFIG_KEY = "contact_config"; const MEMBERSHIP_CONFIG_KEY = "membership_config"; const MEMBERSHIP_USER_OVERRIDES_KEY = "membership_user_plan_overrides"; const STORAGE_FAILOVER_CONFIG_KEY = "storage_failover_config"; const REALTIME_CANVAS_CONFIG_KEY = "realtime_canvas_config"; const REALTIME_CANVAS_MODE_ICON_CONFIG_KEY = "realtime_canvas_mode_icon_config"; const LAYER_SPLIT_CONFIG_KEY = "layer_split_config"; const AGENT_CATEGORY_TREE_CONFIG_KEY = "agent_category_tree_config"; const CANVAS_AGENT_MODE_ENABLED_KEY = "canvas_agent_mode_enabled"; const VIDEO_CANVAS_ENABLED_KEY = "video_canvas_enabled"; const VIDEO_CANVAS_SIDEBAR_ENABLED_KEY = "video_canvas_sidebar_enabled"; const AGENT_WORKFLOW_SETTINGS_CONFLICT_KEYS = [ AGENT_PRESET_LIBRARY_CONFIG_KEY, AGENT_WORKFLOW_UI_CONFIG_KEY, AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY, AGENT_WORKFLOW_GOODS_INFO_API_CONFIG_KEY, AGENT_WORKFLOW_BRAND_MODEL_CONFIG_KEY, AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY, AGENT_PSD_TEXT_COLOR_CONFIG_KEY, AGENT_PSD_TEMPLATE_SECONDARY_CATEGORY_CONFIG_KEY, ] as const; const AGENT_WORKFLOW_SETTINGS_FIELD_KEY_MAP = { agentPresetLibraryConfig: AGENT_PRESET_LIBRARY_CONFIG_KEY, agentWorkflowUiConfig: AGENT_WORKFLOW_UI_CONFIG_KEY, agentWorkflowMaterialApiConfig: AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY, agentWorkflowGoodsInfoApiConfig: AGENT_WORKFLOW_GOODS_INFO_API_CONFIG_KEY, agentWorkflowBrandModelConfig: AGENT_WORKFLOW_BRAND_MODEL_CONFIG_KEY, agentWorkflowShowcaseConfig: AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY, agentPsdTextColorConfig: AGENT_PSD_TEXT_COLOR_CONFIG_KEY, agentPsdTemplateSecondaryCategoryConfig: AGENT_PSD_TEMPLATE_SECONDARY_CATEGORY_CONFIG_KEY, } as const; type AgentWorkflowSettingsField = keyof typeof AGENT_WORKFLOW_SETTINGS_FIELD_KEY_MAP; type RealtimeCanvasModeIconConfig = { realtimeIconUrl: string; enhanceIconUrl: string; realtimeLabel: string; enhanceLabel: string; }; const DEFAULT_REALTIME_CANVAS_MODE_LABEL = "实时画布模式"; const DEFAULT_ENHANCE_CANVAS_MODE_LABEL = "图像增强模式"; const ANGLE_EDIT_CONFIG_KEY = "angle_edit_config"; const defaultRealtimeCanvasModeIconConfig: RealtimeCanvasModeIconConfig = { realtimeIconUrl: "", enhanceIconUrl: "", realtimeLabel: DEFAULT_REALTIME_CANVAS_MODE_LABEL, enhanceLabel: DEFAULT_ENHANCE_CANVAS_MODE_LABEL, }; async function readCanvasFeatureAvailability(valueByKey: Map) { const [enabledTripoProviders, enabledWorldlabsProviders] = await Promise.all([ db .select({ id: aiProvider.id }) .from(aiProvider) .where(and(eq(aiProvider.type, "tripo-3d"), eq(aiProvider.isEnabled, true))) .limit(1), db .select({ id: aiProvider.id }) .from(aiProvider) .where(and(eq(aiProvider.type, "worldlabs-3d"), eq(aiProvider.isEnabled, true))) .limit(1), ]); return { aiWashIconUrl: normalizeAiWashConfig(valueByKey.get(AI_WASH_CONFIG_KEY)) .modeIconUrl, angleEditEnabled: normalizeAngleEditEnabled( valueByKey.get(ANGLE_EDIT_CONFIG_KEY), ), enhanceProEnabled: normalizeEnhanceProKreaConfig( valueByKey.get(ENHANCE_PRO_KREA_SETTING_KEY), ).enabled, aiWashEnabled: normalizeAiWashConfig(valueByKey.get(AI_WASH_CONFIG_KEY)).enabled, aiWashLabel: normalizeAiWashConfig(valueByKey.get(AI_WASH_CONFIG_KEY)).modeLabel, tripo3dEnabled: enabledTripoProviders.length > 0, worldlabs3dEnabled: enabledWorldlabsProviders.length > 0, }; } function normalizeRealtimeCanvasModeIconConfig( value: unknown ): RealtimeCanvasModeIconConfig { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; const normalizeUrl = (input: unknown) => String(input || "").trim().slice(0, 2000); const normalizeLabel = (input: unknown, fallback: string) => { const label = String(input || "").trim().slice(0, 32); return label || fallback; }; return { realtimeIconUrl: normalizeUrl(raw.realtimeIconUrl), enhanceIconUrl: normalizeUrl(raw.enhanceIconUrl), realtimeLabel: normalizeLabel( raw.realtimeLabel, DEFAULT_REALTIME_CANVAS_MODE_LABEL ), enhanceLabel: normalizeLabel( raw.enhanceLabel, DEFAULT_ENHANCE_CANVAS_MODE_LABEL ), }; } function stableStringifySettingsValue(value: unknown): string { if (value === null || typeof value !== "object") { return JSON.stringify(value) ?? "null"; } if (Array.isArray(value)) { return `[${value.map((item) => stableStringifySettingsValue(item)).join(",")}]`; } const entries = Object.entries(value as Record) .filter(([, item]) => item !== undefined) .sort(([left], [right]) => left.localeCompare(right)); return `{${entries .map(([key, item]) => `${JSON.stringify(key)}:${stableStringifySettingsValue(item)}`) .join(",")}}`; } function createSettingsValueSignature(value: unknown): string { const serialized = stableStringifySettingsValue(value); let hashA = 0xdeadbeef ^ serialized.length; let hashB = 0x41c6ce57 ^ serialized.length; for (let index = 0; index < serialized.length; index += 1) { const code = serialized.charCodeAt(index); hashA = Math.imul(hashA ^ code, 2654435761); hashB = Math.imul(hashB ^ code, 1597334677); } hashA = Math.imul(hashA ^ (hashA >>> 16), 2246822507) ^ Math.imul(hashB ^ (hashB >>> 13), 3266489909); hashB = Math.imul(hashB ^ (hashB >>> 16), 2246822507) ^ Math.imul(hashA ^ (hashA >>> 13), 3266489909); return `${serialized.length}:${(hashB >>> 0).toString(16)}${(hashA >>> 0).toString(16)}`; } function createAgentWorkflowSettingsStateFromValues(valueByKey: Map) { const workflowUiConfig = normalizeAgentWorkflowUiConfig( valueByKey.get(AGENT_WORKFLOW_UI_CONFIG_KEY) ); return { agentPresetLibraryConfig: normalizeAgentPresetLibraryConfig( valueByKey.get(AGENT_PRESET_LIBRARY_CONFIG_KEY) ), agentWorkflowUiConfig: { ...workflowUiConfig, steps: normalizeAgentWorkflowStepOrdering(workflowUiConfig.steps), }, agentWorkflowMaterialApiConfig: normalizeAgentWorkflowMaterialApiConfig( valueByKey.get(AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY) ), agentWorkflowGoodsInfoApiConfig: normalizeAgentWorkflowGoodsInfoApiConfig( valueByKey.get(AGENT_WORKFLOW_GOODS_INFO_API_CONFIG_KEY) ), agentWorkflowBrandModelConfig: normalizeAgentWorkflowBrandModelConfig( valueByKey.get(AGENT_WORKFLOW_BRAND_MODEL_CONFIG_KEY) ), agentWorkflowShowcaseConfig: normalizeAgentWorkflowShowcaseConfig( valueByKey.get(AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY) ), agentPsdTextColorConfig: normalizeAgentPsdTextColorConfig( valueByKey.get(AGENT_PSD_TEXT_COLOR_CONFIG_KEY) ), agentPsdTemplateSecondaryCategoryConfig: normalizeAgentPsdTemplateSecondaryCategoryConfig( valueByKey.get(AGENT_PSD_TEMPLATE_SECONDARY_CATEGORY_CONFIG_KEY) ), } satisfies Record; } function createAgentWorkflowSettingsSectionSignatures(valueByKey: Map) { const state = createAgentWorkflowSettingsStateFromValues(valueByKey); return Object.fromEntries( Object.entries(state).map(([field, value]) => [field, createSettingsValueSignature(value)]) ) as Record; } function createAgentWorkflowSettingsSignature(valueByKey: Map): string { return createSettingsValueSignature(createAgentWorkflowSettingsStateFromValues(valueByKey)); } function normalizeSurpriseCategories(value: unknown): string[] { if (!Array.isArray(value)) { return ["全部", "精选"]; } const normalized = value .map((item) => String(item || "").trim()) .filter(Boolean) // legacy typo compatibility: treat "惊喜" as "精选" .map((item) => (item === "惊喜" ? "精选" : item)) .slice(0, 12); if (normalized.length === 0) { return ["全部", "精选"]; } const unique = Array.from(new Set(normalized)); if (!unique.includes("全部")) { unique.unshift("全部"); } return unique; } function normalizeLovartComposerPreviewTexts(value: unknown): string[] { const defaults = [ "打造引人注目的社交媒体视觉", "帮我把这张产品图做成高级电商海报", "基于参考图延展 3 套同风格方案", ]; if (!Array.isArray(value)) { return defaults; } const normalized = value .map((item) => String(item || "").trim()) .filter(Boolean) .slice(0, 20); return normalized.length > 0 ? normalized : defaults; } function normalizeLovartComposerPrefix(value: unknown): string { const normalized = String(value || "").trim(); if (!normalized) { return "让「𝗕𝘂𝗬𝗶」"; } return normalized.slice(0, 40); } function normalizeLovartPromptTagVisibility(value: unknown): { nanoBananaPro: boolean; design: boolean; branding: boolean; illustration: boolean; ecommerce: boolean; video: boolean; } { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; return { nanoBananaPro: raw.nanoBananaPro !== false, design: raw.design !== false, branding: raw.branding !== false, illustration: raw.illustration !== false, ecommerce: raw.ecommerce !== false, video: raw.video !== false, }; } function normalizeContactConfig(value: unknown): { phone: string; wechatQrUrl: string; } { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; const phone = String(raw.phone || "").trim().slice(0, 64); const wechatQrUrl = String(raw.wechatQrUrl || "").trim().slice(0, 1000); return { phone, wechatQrUrl, }; } function normalizeStorageFailoverConfig(value: unknown): { enabled: boolean; backupStorageConfigId: string; } { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; const backupStorageConfigId = String(raw.backupStorageConfigId || "").trim(); return { enabled: raw.enabled === true, backupStorageConfigId, }; } function normalizeCanvasAgentModeEnabled(value: unknown): boolean { return value !== false; } function normalizeCanvasAgentFeatureSettings( settings?: CanvasAgentFeatureSettingsLike | null ): CanvasAgentFeatureSettings { return canvasAgentFeatureFlagsToSettings( getCanvasAgentFeatureFlags(process.env, settings) ); } function buildCanvasAgentFeatureSettings( valueByKey: Map ): CanvasAgentFeatureSettings { return normalizeCanvasAgentFeatureSettings({ [CANVAS_AGENT_OPERATION_PROTOCOL_ENABLED_KEY]: valueByKey.get( CANVAS_AGENT_OPERATION_PROTOCOL_ENABLED_KEY ), [CANVAS_AGENT_WRITEBACK_MODE_KEY]: valueByKey.get( CANVAS_AGENT_WRITEBACK_MODE_KEY ), [CANVAS_AGENT_SLOT_ENABLED_KEY]: valueByKey.get(CANVAS_AGENT_SLOT_ENABLED_KEY), [CANVAS_AGENT_VIEW_OPERATION_ENABLED_KEY]: valueByKey.get( CANVAS_AGENT_VIEW_OPERATION_ENABLED_KEY ), [CANVAS_AGENT_ANNOTATION_EDIT_ENABLED_KEY]: valueByKey.get( CANVAS_AGENT_ANNOTATION_EDIT_ENABLED_KEY ), }); } function normalizeVideoCanvasEnabled( value: unknown, fallbackValue: unknown = true ): boolean { if (value !== undefined && value !== null) { return value !== false; } return fallbackValue !== false; } function normalizeVideoCanvasSidebarEnabled(value: unknown): boolean { return value !== false; } function normalizeLayerSplitEnabled(value: unknown): boolean { if (!value || typeof value !== "object" || Array.isArray(value)) { return false; } return (value as { enabled?: unknown }).enabled === true; } function normalizeAngleEditEnabled(value: unknown): boolean { if (!value || typeof value !== "object" || Array.isArray(value)) { return false; } return (value as { enabled?: unknown }).enabled === true; } function normalizeAgentCategoryTreeConfig(value: unknown): AgentCategoryTreeConfig { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; const normalizeList = (input: unknown) => { if (!Array.isArray(input)) return [] as string[]; return Array.from( new Set( input .map((item) => String(item || "").trim()) .filter(Boolean) .slice(0, 200) ) ); }; return { productCategories: normalizeList(raw.productCategories), audienceTags: normalizeList(raw.audienceTags), usageScenarios: normalizeList(raw.usageScenarios), brands: normalizeList(raw.brands), }; } type PublicSiteSettingsScope = | "agent-brand-model" | "agent-canvas" | "agent-presets" | "auth" | "branding" | "canvas" | "canvas-chat" | "chat" | "home"; const PUBLIC_SITE_SETTINGS_SCOPES = new Set([ "agent-brand-model", "agent-canvas", "agent-presets", "auth", "branding", "canvas", "canvas-chat", "chat", "home", ]); const HOME_SITE_SETTINGS_FALLBACK: Record = { siteName: "𝗕𝘂𝗬𝗶", siteLogo: null, siteLogoDark: null, favicon: null, lovartPromoText: "现在升级,立享 Kling 3.0 与 Nano Banana Pro,最高 5 折优惠", lovartPromoActionText: "立即升级", bannerEnabled: true, bannerText: "限时特惠:升级会员最高立享 45% OFF!", bannerLink: null, bannerActionText: "立即查看", lovartHeroTitle: "𝗕𝘂𝗬𝗶 让设计触手可及", lovartHeroSubtitle: "下达指令,即刻呈现", surpriseCategories: ["全部", "精选"], canvasDefaultModelId: null, lovartComposerPrefix: "让「𝗕𝘂𝗬𝗶」", lovartComposerPreviewTexts: [], lovartPromptTagVisibility: {}, contactConfig: normalizeContactConfig(undefined), membershipConfig: normalizeMembershipConfig(undefined), toolbox1Name: "工具箱", toolbox1Enabled: true, toolbox2Name: "工具箱2", toolbox2Enabled: false, productPlanningEnabled: true, agentEnabled: true, videoCanvasEnabled: true, tutorialEnabled: false, tutorialVideoUrl: null, pointsPackages: normalizePointsPackages(undefined), dulupayConfig: normalizeDulupayForPublic(undefined), lovartHomeSidebarConfig: normalizeLovartHomeSidebarConfig(undefined), }; function createScopedSiteSettingsResponse( scope: PublicSiteSettingsScope, data: Record, cacheStatus: "HIT" | "MISS" | "STALE" | "FALLBACK" = "MISS", cacheKey: string = scope, options: { fallback?: boolean; startedAt?: number } = {} ) { if (cacheStatus === "MISS") { setScopedSiteSettingsCache( cacheKey, data, PUBLIC_SITE_SETTINGS_CACHE_TTL_MS ); } const response = NextResponse.json(data); // Lightweight public settings change rarely; keep them browser-local briefly // so page opens do not repeatedly wait on the large full settings endpoint. response.headers.set( "Cache-Control", "private, max-age=30, stale-while-revalidate=120" ); response.headers.set("X-Site-Settings-Scope", scope); response.headers.set("X-Site-Settings-Cache", cacheStatus); response.headers.set("X-Cache", cacheStatus); response.headers.set("X-Fallback", options.fallback ? "1" : "0"); if (options.startedAt) { response.headers.set( "Server-Timing", `app;dur=${Math.max(0, Date.now() - options.startedAt)}` ); } return response; } async function readScopedSiteSettings(keys: string[]) { const [settings, settingRows] = await Promise.all([ db .select() .from(siteSettings) .limit(1) .then((rows) => rows[0]), keys.length > 0 ? db .select() .from(systemSettings) .where(inArray(systemSettings.key, keys)) : Promise.resolve([]), ]); return { settings, valueByKey: new Map(settingRows.map((row) => [row.key, row.value])), }; } function createBrandingSettings(settings: Record | undefined) { return { siteName: String(settings?.siteName || "𝗕𝘂𝗬𝗶"), siteLogo: settings?.siteLogo || null, siteLogoDark: settings?.siteLogoDark || null, favicon: settings?.favicon || null, }; } async function getScopedPublicSiteSettings( scope: PublicSiteSettingsScope, options: { brandName?: string; cacheKey?: string; } = {} ) { if (scope === "auth") { const { settings, valueByKey } = await readScopedSiteSettings([ "login_notification", ]); return createScopedSiteSettingsResponse(scope, { ...createBrandingSettings(settings as Record | undefined), notification: valueByKey.get("login_notification") || { enabled: false, title: "", content: "", }, }); } if (scope === "agent-brand-model") { const { valueByKey } = await readScopedSiteSettings([ AGENT_WORKFLOW_BRAND_MODEL_CONFIG_KEY, ]); const brandModelConfig = normalizeAgentWorkflowBrandModelConfig( valueByKey.get(AGENT_WORKFLOW_BRAND_MODEL_CONFIG_KEY) ); const normalizedBrandName = String(options.brandName || "") .trim() .toLowerCase(); const scopedBrandModelConfig = normalizedBrandName ? { ...brandModelConfig, entries: brandModelConfig.entries.filter( (entry) => entry.brandName.trim().toLowerCase() === normalizedBrandName ), } : brandModelConfig; return createScopedSiteSettingsResponse( scope, { agentWorkflowBrandModelConfig: scopedBrandModelConfig, }, "MISS", options.cacheKey ); } if (scope === "agent-presets") { const { valueByKey } = await readScopedSiteSettings([ AGENT_PRESET_LIBRARY_CONFIG_KEY, AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY, ]); return createScopedSiteSettingsResponse(scope, { agentPresetLibraryConfig: normalizeAgentPresetLibraryConfig( valueByKey.get(AGENT_PRESET_LIBRARY_CONFIG_KEY) ), agentWorkflowShowcaseConfig: normalizeAgentWorkflowShowcaseConfig( valueByKey.get(AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY) ), }); } if (scope === "agent-canvas") { const { settings, valueByKey } = await readScopedSiteSettings([ REALTIME_CANVAS_CONFIG_KEY, REALTIME_CANVAS_MODE_ICON_CONFIG_KEY, CANVAS_TOOLBAR_CONFIG_SETTING_KEY, LAYER_SPLIT_CONFIG_KEY, ANGLE_EDIT_CONFIG_KEY, CANVAS_AGENT_MODE_ENABLED_KEY, ...CANVAS_AGENT_FEATURE_SETTING_KEYS, VIDEO_CANVAS_ENABLED_KEY, VIDEO_CANVAS_SIDEBAR_ENABLED_KEY, ENHANCE_PRO_KREA_SETTING_KEY, AI_WASH_CONFIG_KEY, STYLE_PRO_CONFIG_KEY, AGENT_WORKFLOW_UI_CONFIG_KEY, AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY, ]); const canvasFeatureAvailability = await readCanvasFeatureAvailability( valueByKey ); const typedSettings = settings as Record | undefined; const videoCanvasSidebarEnabled = normalizeVideoCanvasSidebarEnabled( valueByKey.get(VIDEO_CANVAS_SIDEBAR_ENABLED_KEY) ); return createScopedSiteSettingsResponse(scope, { ...createBrandingSettings(typedSettings), agentEnabled: typedSettings?.agentEnabled ?? true, canvasAgentModeEnabled: normalizeCanvasAgentModeEnabled( valueByKey.get(CANVAS_AGENT_MODE_ENABLED_KEY) ), canvasAgentFeatureSettings: buildCanvasAgentFeatureSettings(valueByKey), videoCanvasEnabled: normalizeVideoCanvasEnabled( valueByKey.get(VIDEO_CANVAS_ENABLED_KEY), valueByKey.get(VIDEO_CANVAS_SIDEBAR_ENABLED_KEY) ), videoCanvasSidebarEnabled, realtimeCanvasEnabled: true, realtimeCanvasServiceEnabled: (valueByKey.get(REALTIME_CANVAS_CONFIG_KEY) as | { enabled?: boolean } | undefined)?.enabled !== false, realtimeCanvasModeIconConfig: normalizeRealtimeCanvasModeIconConfig( valueByKey.get(REALTIME_CANVAS_MODE_ICON_CONFIG_KEY) ), canvasToolbarConfig: normalizeCanvasToolbarConfig( valueByKey.get(CANVAS_TOOLBAR_CONFIG_SETTING_KEY) ), styleProEnabled: normalizeStyleProConfig(valueByKey.get(STYLE_PRO_CONFIG_KEY)).enabled, layerSplitEnabled: normalizeLayerSplitEnabled( valueByKey.get(LAYER_SPLIT_CONFIG_KEY) ), angleEditEnabled: canvasFeatureAvailability.angleEditEnabled, enhanceProEnabled: canvasFeatureAvailability.enhanceProEnabled, aiWashEnabled: canvasFeatureAvailability.aiWashEnabled, aiWashLabel: canvasFeatureAvailability.aiWashLabel, aiWashIconUrl: canvasFeatureAvailability.aiWashIconUrl, tripo3dEnabled: canvasFeatureAvailability.tripo3dEnabled, worldlabs3dEnabled: canvasFeatureAvailability.worldlabs3dEnabled, agentWorkflowUiConfig: normalizeAgentWorkflowUiConfig( valueByKey.get(AGENT_WORKFLOW_UI_CONFIG_KEY) ), agentWorkflowShowcaseConfig: normalizeAgentWorkflowShowcaseConfig( valueByKey.get(AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY) ), }); } if (scope === "branding") { const { settings } = await readScopedSiteSettings([]); return createScopedSiteSettingsResponse( scope, createBrandingSettings(settings as Record | undefined) ); } if (scope === "chat") { const { settings } = await readScopedSiteSettings([]); const typedSettings = settings as Record | undefined; return createScopedSiteSettingsResponse(scope, { ...createBrandingSettings(typedSettings), showAutoModel: typedSettings?.showAutoModel ?? true, usageTimeEnabled: typedSettings?.usageTimeEnabled ?? false, usageTimeStart: typedSettings?.usageTimeStart || "08:00", usageTimeEnd: typedSettings?.usageTimeEnd || "20:00", usageTimeMessage: typedSettings?.usageTimeMessage || "BuYi 累了也是需要休息的", }); } if (scope === "canvas") { const { settings, valueByKey } = await readScopedSiteSettings([ REALTIME_CANVAS_CONFIG_KEY, REALTIME_CANVAS_MODE_ICON_CONFIG_KEY, CANVAS_TOOLBAR_CONFIG_SETTING_KEY, LAYER_SPLIT_CONFIG_KEY, ANGLE_EDIT_CONFIG_KEY, CANVAS_AGENT_MODE_ENABLED_KEY, ...CANVAS_AGENT_FEATURE_SETTING_KEYS, VIDEO_CANVAS_ENABLED_KEY, VIDEO_CANVAS_SIDEBAR_ENABLED_KEY, ENHANCE_PRO_KREA_SETTING_KEY, AI_WASH_CONFIG_KEY, STYLE_PRO_CONFIG_KEY, ]); const canvasFeatureAvailability = await readCanvasFeatureAvailability(valueByKey); const typedSettings = settings as Record | undefined; const videoCanvasSidebarEnabled = normalizeVideoCanvasSidebarEnabled( valueByKey.get(VIDEO_CANVAS_SIDEBAR_ENABLED_KEY) ); return createScopedSiteSettingsResponse(scope, { ...createBrandingSettings(typedSettings), agentEnabled: typedSettings?.agentEnabled ?? true, canvasAgentModeEnabled: normalizeCanvasAgentModeEnabled( valueByKey.get(CANVAS_AGENT_MODE_ENABLED_KEY) ), canvasAgentFeatureSettings: buildCanvasAgentFeatureSettings(valueByKey), videoCanvasEnabled: normalizeVideoCanvasEnabled( valueByKey.get(VIDEO_CANVAS_ENABLED_KEY), valueByKey.get(VIDEO_CANVAS_SIDEBAR_ENABLED_KEY) ), videoCanvasSidebarEnabled, realtimeCanvasEnabled: true, realtimeCanvasServiceEnabled: (valueByKey.get(REALTIME_CANVAS_CONFIG_KEY) as | { enabled?: boolean } | undefined)?.enabled !== false, realtimeCanvasModeIconConfig: normalizeRealtimeCanvasModeIconConfig( valueByKey.get(REALTIME_CANVAS_MODE_ICON_CONFIG_KEY) ), canvasToolbarConfig: normalizeCanvasToolbarConfig( valueByKey.get(CANVAS_TOOLBAR_CONFIG_SETTING_KEY) ), styleProEnabled: normalizeStyleProConfig(valueByKey.get(STYLE_PRO_CONFIG_KEY)).enabled, layerSplitEnabled: normalizeLayerSplitEnabled( valueByKey.get(LAYER_SPLIT_CONFIG_KEY) ), angleEditEnabled: canvasFeatureAvailability.angleEditEnabled, enhanceProEnabled: canvasFeatureAvailability.enhanceProEnabled, aiWashEnabled: canvasFeatureAvailability.aiWashEnabled, aiWashLabel: canvasFeatureAvailability.aiWashLabel, aiWashIconUrl: canvasFeatureAvailability.aiWashIconUrl, tripo3dEnabled: canvasFeatureAvailability.tripo3dEnabled, worldlabs3dEnabled: canvasFeatureAvailability.worldlabs3dEnabled, }); } if (scope === "canvas-chat") { const { settings, valueByKey } = await readScopedSiteSettings([ AI_DETAIL_GENERATOR_ENABLED_KEY, LOVART_COMPOSER_PREVIEW_TEXTS_KEY, LOVART_COMPOSER_PREFIX_KEY, CANVAS_TOOLBAR_CONFIG_SETTING_KEY, ...CANVAS_AGENT_FEATURE_SETTING_KEYS, OPENCLAW_CHAT_CONFIG_KEY, ]); const typedSettings = settings as Record | undefined; return createScopedSiteSettingsResponse(scope, { ...createBrandingSettings(typedSettings), bannerEnabled: typedSettings?.bannerEnabled ?? true, bannerText: typedSettings?.bannerText || "限时特惠:升级会员最高立享 45% OFF!", bannerLink: typedSettings?.bannerLink || null, bannerActionText: typedSettings?.bannerActionText || "立即查看", lovartComposerPrefix: normalizeLovartComposerPrefix( valueByKey.get(LOVART_COMPOSER_PREFIX_KEY) ), lovartComposerPreviewTexts: normalizeLovartComposerPreviewTexts( valueByKey.get(LOVART_COMPOSER_PREVIEW_TEXTS_KEY) ), canvasEnabled: typedSettings?.canvasEnabled ?? true, canvasAgentFeatureSettings: buildCanvasAgentFeatureSettings(valueByKey), aiDetailGenerationEnabled: Boolean( valueByKey.get(AI_DETAIL_GENERATOR_ENABLED_KEY) ), canvasDefaultModelId: typedSettings?.canvasDefaultModelId || null, canvasAutoModeDefaultModelId: typedSettings?.canvasAutoModeDefaultModelId || null, canvasDetailDefaultModelId: typedSettings?.canvasDetailDefaultModelId || null, canvasToolbarConfig: normalizeCanvasToolbarConfig( valueByKey.get(CANVAS_TOOLBAR_CONFIG_SETTING_KEY) ), openClawChatConfig: normalizePublicOpenClawChatConfig( valueByKey.get(OPENCLAW_CHAT_CONFIG_KEY) ), }); } const { settings, valueByKey } = await readScopedSiteSettings([ HOME_SURPRISE_CATEGORIES_KEY, LOVART_COMPOSER_PREVIEW_TEXTS_KEY, LOVART_COMPOSER_PREFIX_KEY, LOVART_PROMPT_TAG_VISIBILITY_KEY, CONTACT_CONFIG_KEY, MEMBERSHIP_CONFIG_KEY, POINTS_PACKAGES_KEY, DULUPAY_CONFIG_KEY, LOVART_HOME_SIDEBAR_CONFIG_KEY, VIDEO_CANVAS_ENABLED_KEY, VIDEO_CANVAS_SIDEBAR_ENABLED_KEY, ]); const typedSettings = settings as Record | undefined; return createScopedSiteSettingsResponse(scope, { ...createBrandingSettings(typedSettings), bannerEnabled: typedSettings?.bannerEnabled ?? true, bannerText: typedSettings?.bannerText || "限时特惠:升级会员最高立享 45% OFF!", bannerLink: typedSettings?.bannerLink || null, bannerActionText: typedSettings?.bannerActionText || "立即查看", lovartPromoText: typedSettings?.lovartPromoText || "现在升级,立享 Kling 3.0 与 Nano Banana Pro,最高 5 折优惠", lovartPromoActionText: typedSettings?.lovartPromoActionText || "立即升级", lovartHeroTitle: typedSettings?.lovartHeroTitle || "𝗕𝘂𝗬𝗶 让设计触手可及", lovartHeroSubtitle: typedSettings?.lovartHeroSubtitle || "下达指令,即刻呈现", surpriseCategories: normalizeSurpriseCategories( valueByKey.get(HOME_SURPRISE_CATEGORIES_KEY) ), canvasDefaultModelId: typedSettings?.canvasDefaultModelId || null, lovartComposerPrefix: normalizeLovartComposerPrefix( valueByKey.get(LOVART_COMPOSER_PREFIX_KEY) ), lovartComposerPreviewTexts: normalizeLovartComposerPreviewTexts( valueByKey.get(LOVART_COMPOSER_PREVIEW_TEXTS_KEY) ), lovartPromptTagVisibility: normalizeLovartPromptTagVisibility( valueByKey.get(LOVART_PROMPT_TAG_VISIBILITY_KEY) ), contactConfig: normalizeContactConfig(valueByKey.get(CONTACT_CONFIG_KEY)), membershipConfig: normalizeMembershipConfig( valueByKey.get(MEMBERSHIP_CONFIG_KEY) ), toolbox1Name: typedSettings?.toolbox1Name || "工具箱", toolbox1Enabled: typedSettings?.toolbox1Enabled ?? true, toolbox2Name: typedSettings?.toolbox2Name || "工具箱2", toolbox2Enabled: typedSettings?.toolbox2Enabled ?? false, productPlanningEnabled: typedSettings?.productPlanningEnabled ?? true, agentEnabled: typedSettings?.agentEnabled ?? true, videoCanvasEnabled: normalizeVideoCanvasEnabled( valueByKey.get(VIDEO_CANVAS_ENABLED_KEY), valueByKey.get(VIDEO_CANVAS_SIDEBAR_ENABLED_KEY) ), tutorialEnabled: typedSettings?.tutorialEnabled ?? false, tutorialVideoUrl: typedSettings?.tutorialVideoUrl || null, pointsPackages: normalizePointsPackages( valueByKey.get(POINTS_PACKAGES_KEY) ), dulupayConfig: normalizeDulupayForPublic(valueByKey.get(DULUPAY_CONFIG_KEY)), lovartHomeSidebarConfig: normalizeLovartHomeSidebarConfig( valueByKey.get(LOVART_HOME_SIDEBAR_CONFIG_KEY) ), }); } async function getAgentWorkflowAdminSettingsResponse() { const { settings, valueByKey } = await readScopedSiteSettings([ ...AGENT_WORKFLOW_SETTINGS_CONFLICT_KEYS, ]); const workflowSettings = createAgentWorkflowSettingsStateFromValues(valueByKey); const response = NextResponse.json({ ...workflowSettings, agentWorkflowDefaultModelId: String( (settings as { agentWorkflowDefaultModelId?: string | null } | undefined) ?.agentWorkflowDefaultModelId || "" ).trim() || null, }); response.headers.set("Cache-Control", "no-store"); response.headers.set("X-Site-Settings-Scope", "agent-workflow-admin"); return response; } // GET /api/site-settings - 获取网站设置 (公开) export async function GET(request: NextRequest) { try { const startedAt = Date.now(); const scope = request.nextUrl.searchParams.get("scope") || ""; const isLocalCanvasE2E = process.env.CANVAS_E2E_LOCAL === "true"; if ( isLocalCanvasE2E && PUBLIC_SITE_SETTINGS_SCOPES.has(scope) ) { const scopedScope = scope as PublicSiteSettingsScope; const data = scopedScope === "branding" ? createBrandingSettings(undefined) : { ...HOME_SITE_SETTINGS_FALLBACK, canvasEnabled: true, realtimeCanvasEnabled: true, videoCanvasEnabled: true, }; return createScopedSiteSettingsResponse( scopedScope, data, "FALLBACK", `local-e2e:${scopedScope}`, { fallback: true, startedAt } ); } if (PUBLIC_SITE_SETTINGS_SCOPES.has(scope)) { const scopedScope = scope as PublicSiteSettingsScope; const brandName = scopedScope === "agent-brand-model" ? request.nextUrl.searchParams.get("brand") || "" : ""; const cacheKey = brandName.trim() ? `${scopedScope}:${brandName.trim().toLowerCase()}` : scopedScope; const cached = getScopedSiteSettingsCache(cacheKey); if (cached && cached.expiresAt > Date.now()) { return createScopedSiteSettingsResponse( scopedScope, cached.data, "HIT", cacheKey ); } if (cached && scopedScope !== "home") { deleteScopedSiteSettingsCache(cacheKey); } try { const responsePromise = getScopedPublicSiteSettings(scopedScope, { brandName, cacheKey, }); return scopedScope === "home" ? await withTimeout(responsePromise, 1600, "site-settings:home") : await responsePromise; } catch (error) { if (scopedScope !== "home") { throw error; } const message = error instanceof Error ? error.message : String(error); console.warn(`[Site Settings API] Home fallback: ${message}`); return createScopedSiteSettingsResponse( scopedScope, cached?.data ?? HOME_SITE_SETTINGS_FALLBACK, cached ? "STALE" : "FALLBACK", cacheKey, { fallback: true, startedAt } ); } } const session = await auth().catch(() => null); const isAdmin = isSiteAdminType(session?.user?.type); const canManageAgentWorkflow = isAgentWorkflowAdminType(session?.user?.type); if (scope === "agent-workflow-admin") { if (!session?.user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } if (!canManageAgentWorkflow) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } return getAgentWorkflowAdminSettingsResponse(); } if (!session?.user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } if (!isAdmin && !canManageAgentWorkflow) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } const [ settings, notificationSetting, surpriseCategoriesSetting, aiDetailGeneratorEnabledSetting, lovartComposerPreviewTextsSetting, lovartComposerPrefixSetting, lovartPromptTagVisibilitySetting, contactConfigSetting, membershipConfigSetting, membershipOverridesSetting, pointsSystemConfigSetting, pointsPackagesSetting, dulupayConfigSetting, realtimeCanvasConfigSetting, realtimeCanvasModeIconConfigSetting, canvasToolbarConfigSetting, lovartHomeSidebarConfigSetting, layerSplitConfigSetting, canvasAgentModeEnabledSetting, canvasAgentFeatureSettingsRows, videoCanvasEnabledSetting, videoCanvasSidebarEnabledSetting, storageFailoverConfigSetting, ossImageMigrationConfigSetting, agentPresetLibraryConfigSetting, agentCategoryTreeConfigSetting, agentSecondaryTypeConfigSetting, agentWorkflowUiConfigSetting, agentWorkflowMaterialApiConfigSetting, agentWorkflowGoodsInfoApiConfigSetting, agentWorkflowBrandModelConfigSetting, agentWorkflowShowcaseConfigSetting, agentPsdTextColorConfigSetting, agentPsdTemplateSecondaryCategoryConfigSetting, agentPsdTemplateCategoryTagConfigSetting, agentPsdTemplates, ] = await Promise.all([ db.select().from(siteSettings).limit(1).then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, "login_notification")) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, HOME_SURPRISE_CATEGORIES_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, AI_DETAIL_GENERATOR_ENABLED_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, LOVART_COMPOSER_PREVIEW_TEXTS_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, LOVART_COMPOSER_PREFIX_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, LOVART_PROMPT_TAG_VISIBILITY_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, CONTACT_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, MEMBERSHIP_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, MEMBERSHIP_USER_OVERRIDES_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, POINTS_SYSTEM_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, POINTS_PACKAGES_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, DULUPAY_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, REALTIME_CANVAS_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, REALTIME_CANVAS_MODE_ICON_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, CANVAS_TOOLBAR_CONFIG_SETTING_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, LOVART_HOME_SIDEBAR_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, LAYER_SPLIT_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, CANVAS_AGENT_MODE_ENABLED_KEY)) .limit(1) .then(rows => rows[0]), db .select({ key: systemSettings.key, value: systemSettings.value }) .from(systemSettings) .where(inArray(systemSettings.key, [...CANVAS_AGENT_FEATURE_SETTING_KEYS])), db .select() .from(systemSettings) .where(eq(systemSettings.key, VIDEO_CANVAS_ENABLED_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, VIDEO_CANVAS_SIDEBAR_ENABLED_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, STORAGE_FAILOVER_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, OSS_MIGRATION_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, AGENT_PRESET_LIBRARY_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, AGENT_CATEGORY_TREE_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, AGENT_SECONDARY_TYPE_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, AGENT_WORKFLOW_UI_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, AGENT_WORKFLOW_GOODS_INFO_API_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, AGENT_WORKFLOW_BRAND_MODEL_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, AGENT_PSD_TEXT_COLOR_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, AGENT_PSD_TEMPLATE_SECONDARY_CATEGORY_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, AGENT_PSD_TEMPLATE_CATEGORY_TAG_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), readAgentPsdTemplates().catch(() => []), ]); const notification = notificationSetting?.value || { enabled: false, title: "", content: "" }; const surpriseCategories = normalizeSurpriseCategories(surpriseCategoriesSetting?.value); const aiDetailGenerationEnabled = Boolean(aiDetailGeneratorEnabledSetting?.value); const lovartComposerPreviewTexts = normalizeLovartComposerPreviewTexts( lovartComposerPreviewTextsSetting?.value ); const lovartComposerPrefix = normalizeLovartComposerPrefix( lovartComposerPrefixSetting?.value ); const lovartPromptTagVisibility = normalizeLovartPromptTagVisibility( lovartPromptTagVisibilitySetting?.value ); const contactConfig = normalizeContactConfig(contactConfigSetting?.value); const membershipConfig = normalizeMembershipConfig(membershipConfigSetting?.value); const membershipUserOverrides = normalizeMembershipUserOverrides( membershipOverridesSetting?.value ); const pointsSystemConfig = normalizePointsSystemConfig( pointsSystemConfigSetting?.value ); const pointsPackages = normalizePointsPackages(pointsPackagesSetting?.value); const dulupayConfig = isAdmin ? normalizeDulupayConfig(dulupayConfigSetting?.value) : normalizeDulupayForPublic(dulupayConfigSetting?.value); const realtimeCanvasServiceEnabled = (realtimeCanvasConfigSetting?.value as { enabled?: boolean } | undefined)?.enabled !== false; const realtimeCanvasModeIconConfig = normalizeRealtimeCanvasModeIconConfig( realtimeCanvasModeIconConfigSetting?.value ); const canvasToolbarConfig = normalizeCanvasToolbarConfig( canvasToolbarConfigSetting?.value ); const lovartHomeSidebarConfig = normalizeLovartHomeSidebarConfig( lovartHomeSidebarConfigSetting?.value ); const layerSplitEnabled = normalizeLayerSplitEnabled( layerSplitConfigSetting?.value ); const canvasAgentModeEnabled = normalizeCanvasAgentModeEnabled( canvasAgentModeEnabledSetting?.value ); const canvasAgentFeatureValueByKey = new Map( canvasAgentFeatureSettingsRows.map((row) => [row.key, row.value]) ); const canvasAgentFeatureSettings = buildCanvasAgentFeatureSettings( canvasAgentFeatureValueByKey ); const videoCanvasSidebarEnabled = normalizeVideoCanvasSidebarEnabled( videoCanvasSidebarEnabledSetting?.value ); const videoCanvasEnabled = normalizeVideoCanvasEnabled( videoCanvasEnabledSetting?.value, videoCanvasSidebarEnabledSetting?.value ); const storageFailoverConfig = normalizeStorageFailoverConfig( storageFailoverConfigSetting?.value ); const ossImageMigrationConfig = normalizeOssImageMigrationConfig( ossImageMigrationConfigSetting?.value ); const agentPresetLibraryConfig = normalizeAgentPresetLibraryConfig( agentPresetLibraryConfigSetting?.value ); const agentCategoryTreeConfig = normalizeAgentCategoryTreeConfig( agentCategoryTreeConfigSetting?.value ); const agentSecondaryTypeConfig = normalizeAgentSecondaryTypeConfig( agentSecondaryTypeConfigSetting?.value ); const agentWorkflowUiConfig = normalizeAgentWorkflowUiConfig( agentWorkflowUiConfigSetting?.value ); const agentWorkflowMaterialApiConfig = canManageAgentWorkflow ? normalizeAgentWorkflowMaterialApiConfig(agentWorkflowMaterialApiConfigSetting?.value) : normalizeAgentWorkflowMaterialApiConfigForPublic( agentWorkflowMaterialApiConfigSetting?.value ); const agentWorkflowGoodsInfoApiConfig = canManageAgentWorkflow ? normalizeAgentWorkflowGoodsInfoApiConfig(agentWorkflowGoodsInfoApiConfigSetting?.value) : normalizeAgentWorkflowGoodsInfoApiConfigForPublic( agentWorkflowGoodsInfoApiConfigSetting?.value ); const agentWorkflowBrandModelConfig = normalizeAgentWorkflowBrandModelConfig( agentWorkflowBrandModelConfigSetting?.value ); const agentWorkflowShowcaseConfig = normalizeAgentWorkflowShowcaseConfig( agentWorkflowShowcaseConfigSetting?.value ); const agentPsdTextColorConfig = normalizeAgentPsdTextColorConfig( agentPsdTextColorConfigSetting?.value ); const agentPsdTemplateSecondaryCategoryConfig = normalizeAgentPsdTemplateSecondaryCategoryConfig( agentPsdTemplateSecondaryCategoryConfigSetting?.value ); const agentPsdTemplateCategoryTagConfig = normalizeAgentPsdTemplateCategoryTagConfig( agentPsdTemplateCategoryTagConfigSetting?.value ); const agentPsdTemplateCategoryTagIndex = buildAgentPsdTemplateCategoryTagIndex({ templates: agentPsdTemplates, config: agentPsdTemplateCategoryTagConfig, }); // 如果没有设置记录,返回默认值 const baseSettings = settings || { siteName: "AI Chatbot", siteLogo: null, siteLogoDark: null, favicon: null, showAutoModel: true, defaultTheme: "system", usageTimeEnabled: false, usageTimeStart: "08:00", usageTimeEnd: "20:00", usageTimeMessage: "BuYi 累了也是需要休息的", bannerEnabled: true, bannerText: "限时特惠:升级会员最高立享 45% OFF!", bannerLink: null, bannerActionText: "立即查看", toolbox1Name: "工具箱", toolbox1Enabled: true, toolbox2Name: "工具箱2", toolbox2Enabled: false, tutorialVideoUrl: null, tutorialEnabled: false, rankingEnabled: true, productPlanningEnabled: true, canvasEnabled: true, agentEnabled: true, canvasAgentModeEnabled: true, canvasAgentFeatureSettings, videoCanvasEnabled: true, videoCanvasSidebarEnabled: true, agentPresetLibraryConfig: defaultAgentPresetLibraryConfig, agentCategoryTreeConfig: normalizeAgentCategoryTreeConfig(undefined), agentSecondaryTypeConfig: defaultAgentSecondaryTypeConfig, agentWorkflowUiConfig: defaultAgentWorkflowUiConfig, agentWorkflowMaterialApiConfig: defaultAgentWorkflowMaterialApiConfig, agentWorkflowGoodsInfoApiConfig: defaultAgentWorkflowGoodsInfoApiConfig, agentWorkflowBrandModelConfig: defaultAgentWorkflowBrandModelConfig, agentWorkflowShowcaseConfig: defaultAgentWorkflowShowcaseConfig, agentPsdTextColorConfig: defaultAgentPsdTextColorConfig, agentPsdTemplateSecondaryCategoryConfig: defaultAgentPsdTemplateSecondaryCategoryConfig, agentPsdTemplateCategoryTagConfig: defaultAgentPsdTemplateCategoryTagConfig, agentPsdTemplateCategoryTagIndex: defaultAgentPsdTemplateCategoryTagIndex, realtimeCanvasEnabled: true, realtimeCanvasModeIconConfig: defaultRealtimeCanvasModeIconConfig, canvasToolbarConfig: defaultCanvasToolbarConfig, lovartHomeSidebarConfig: defaultLovartHomeSidebarConfig, realtimeCanvasServiceEnabled: true, layerSplitEnabled, storageFailoverConfig, ossImageMigrationConfig, canvasDefaultModelId: null, canvasAutoModeDefaultModelId: null, agentWorkflowDefaultModelId: null, canvasDetailDefaultModelId: null, canvasDetailAgentModelId: null, lovartPromoText: "现在升级,立享 Kling 3.0 与 Nano Banana Pro,最高 5 折优惠", lovartPromoActionText: "立即升级", lovartHeroTitle: "𝗕𝘂𝗬𝗶 让设计触手可及", lovartHeroSubtitle: "下达指令,即刻呈现", surpriseCategories, aiDetailGenerationEnabled, lovartComposerPrefix, lovartComposerPreviewTexts, lovartPromptTagVisibility, contactConfig, membershipConfig, membershipUserOverrides, pointsSystemConfig, pointsPackages, dulupayConfig, }; const response = NextResponse.json({ ...baseSettings, lovartPromoText: baseSettings.lovartPromoText || "现在升级,立享 Kling 3.0 与 Nano Banana Pro,最高 5 折优惠", lovartPromoActionText: baseSettings.lovartPromoActionText || "立即升级", lovartHeroTitle: baseSettings.lovartHeroTitle || "𝗕𝘂𝗬𝗶 让设计触手可及", lovartHeroSubtitle: baseSettings.lovartHeroSubtitle || "下达指令,即刻呈现", notification, surpriseCategories, aiDetailGenerationEnabled, lovartComposerPrefix, lovartComposerPreviewTexts, lovartPromptTagVisibility, contactConfig, agentPresetLibraryConfig, agentCategoryTreeConfig, agentSecondaryTypeConfig, agentWorkflowUiConfig, agentWorkflowMaterialApiConfig, agentWorkflowGoodsInfoApiConfig, agentWorkflowBrandModelConfig, agentWorkflowShowcaseConfig, agentPsdTextColorConfig, agentPsdTemplateSecondaryCategoryConfig, agentPsdTemplateCategoryTagConfig, agentPsdTemplateCategoryTagIndex, membershipConfig, membershipUserOverrides, pointsSystemConfig, pointsPackages, dulupayConfig, realtimeCanvasServiceEnabled, realtimeCanvasModeIconConfig, canvasToolbarConfig, lovartHomeSidebarConfig, layerSplitEnabled, canvasAgentModeEnabled, canvasAgentFeatureSettings, videoCanvasEnabled, videoCanvasSidebarEnabled, storageFailoverConfig, ossImageMigrationConfig, }); // 禁用缓存,确保总是获取最新设置 response.headers.set("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate"); response.headers.set("Pragma", "no-cache"); response.headers.set("Expires", "0"); return response; } catch (error) { console.error("[Site Settings API] Get error:", error); return NextResponse.json( { error: "Failed to fetch site settings" }, { status: 500 } ); } } const AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS = 5000; const agentWorkflowOptionSchema: z.ZodTypeAny = z.lazy(() => z.object({ key: z.string().trim().min(1).max(64), buttonName: z.string().trim().min(1).max(64), description: z.string().trim().max(300).nullable().optional(), previewImageUrl: z.string().trim().max(1000).nullable().optional(), previewImageSourceUrl: z.string().trim().max(1000).nullable().optional(), enabled: z.boolean().optional(), sortOrder: z.number().min(0).max(999).optional(), promptFragment: z .string() .trim() .max(AGENT_WORKFLOW_PROMPT_FRAGMENT_MAX_LENGTH) .nullable() .optional(), blockedOptionKeys: z .array(z.string().trim().min(1).max(64)) .max(AGENT_WORKFLOW_BLOCKED_OPTION_KEYS_MAX) .optional(), availableOfficialCategoryKeys: z .array(z.string().trim().min(1).max(160)) .max(AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS) .optional(), specialModuleEnabled: z.boolean().optional(), specialModuleExclusionEnabled: z.boolean().optional(), specialModuleAllowedOptionKeys: z .array(z.string().trim().min(1).max(64)) .max(5000) .optional(), children: z.array(agentWorkflowOptionSchema).max(100).optional(), }) ); const agentWorkflowStepSchema: z.ZodTypeAny = z.lazy(() => z.object({ key: z.string().trim().min(1).max(64), title: z.string().trim().min(1).max(64), description: z.string().trim().max(300).nullable().optional(), enabled: z.boolean().optional(), sortOrder: z.number().min(0).max(999).optional(), specialModuleEnabled: z.boolean().optional(), options: z.array(agentWorkflowOptionSchema).max(100), children: z.array(agentWorkflowStepSchema).max(100).optional(), }) ); const updateSchema = z.object({ siteName: z.string().min(1).max(128).optional(), siteLogo: z.string().nullable().optional(), siteLogoDark: z.string().nullable().optional(), favicon: z.string().nullable().optional(), showAutoModel: z.boolean().optional(), defaultTheme: z.enum(["system", "light", "dark"]).optional(), usageTimeEnabled: z.boolean().optional(), usageTimeStart: z.string().length(5).optional(), usageTimeEnd: z.string().length(5).optional(), usageTimeMessage: z.string().optional(), bannerEnabled: z.boolean().optional(), bannerText: z.string().optional(), bannerLink: z.string().nullable().optional(), bannerActionText: z.string().max(32).optional(), toolbox1Name: z.string().max(64).optional(), toolbox1Enabled: z.boolean().optional(), toolbox2Name: z.string().max(64).optional(), toolbox2Enabled: z.boolean().optional(), tutorialVideoUrl: z.string().nullable().optional(), tutorialEnabled: z.boolean().optional(), rankingEnabled: z.boolean().optional(), productPlanningEnabled: z.boolean().optional(), canvasEnabled: z.boolean().optional(), agentEnabled: z.boolean().optional(), canvasAgentModeEnabled: z.boolean().optional(), canvasAgentFeatureSettings: z .object({ operationProtocolEnabled: z.boolean(), writebackMode: z.enum([ "legacy_canvas_media", "operation_shadow", "operation_authoritative", ]), slotEnabled: z.boolean(), viewOperationEnabled: z.boolean(), annotationEditEnabled: z.boolean(), }) .optional(), videoCanvasEnabled: z.boolean().optional(), videoCanvasSidebarEnabled: z.boolean().optional(), agentPresetLibraryConfig: z .object({ sections: z .array( z.object({ id: z.string().trim().min(1).max(64).optional(), label: z.string().trim().min(1).max(64), description: z.string().trim().max(200).nullable().optional(), enabled: z.boolean().optional(), sortOrder: z.number().min(0).max(999).optional(), categories: z .array( z.lazy(() => z.object({ id: z.string().trim().min(1).max(64).optional(), name: z.string().trim().min(1).max(64), enabled: z.boolean().optional(), sortOrder: z.number().min(0).max(999).optional(), children: z .array( z.lazy(() => z.object({ id: z.string().trim().min(1).max(64).optional(), name: z.string().trim().min(1).max(64), enabled: z.boolean().optional(), sortOrder: z.number().min(0).max(999).optional(), children: z .array( z.object({ id: z.string().trim().min(1).max(64).optional(), name: z.string().trim().min(1).max(64), enabled: z.boolean().optional(), sortOrder: z.number().min(0).max(999).optional(), children: z.array(z.any()).optional(), }) ) .max(100) .optional(), }) ) ) .max(100) .optional(), }) ) ) .max(100) .optional(), }) ) .max(30), }) .optional(), agentCategoryTreeConfig: z .object({ productCategories: z.array(z.string().trim().min(1).max(64)).max(200).optional(), audienceTags: z.array(z.string().trim().min(1).max(64)).max(200).optional(), usageScenarios: z.array(z.string().trim().min(1).max(64)).max(200).optional(), brands: z.array(z.string().trim().min(1).max(64)).max(200).optional(), }) .optional(), agentSecondaryTypeConfig: z .object({ image: z.array(z.string().trim().min(1).max(64)).max(200).optional(), video: z.array(z.string().trim().min(1).max(64)).max(200).optional(), text: z.array(z.string().trim().min(1).max(64)).max(200).optional(), }) .optional(), agentWorkflowSettingsBaseSignature: z.string().trim().min(1).max(128).optional(), agentWorkflowSettingsBaseSignatures: z .record(z.string().trim().min(1).max(128)) .optional(), agentWorkflowDestructiveChangeApproval: z .object({ token: z.string().trim().min(16).max(128), }) .optional(), agentWorkflowUiConfig: z .object({ inputOptions: z .object({ brands: z .array(z.string().trim().min(1).max(64)) .max(AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS) .optional(), stores: z .array(z.string().trim().min(1).max(64)) .max(AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS) .optional(), categoryLabels: z .object({ firstCategory: z.string().trim().min(1).max(24).optional(), secondCategory: z.string().trim().min(1).max(24).optional(), thirdCategory: z.string().trim().min(1).max(24).optional(), fourthCategory: z.string().trim().min(1).max(24).optional(), fifthCategory: z.string().trim().min(1).max(24).optional(), }) .optional(), thirdCategories: z .array(z.string().trim().min(1).max(64)) .max(AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS) .optional(), fourthCategories: z .array(z.string().trim().min(1).max(64)) .max(AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS) .optional(), fifthCategories: z .array(z.string().trim().min(1).max(64)) .max(AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS) .optional(), officialCategoryEntries: z .array( z.object({ categoryName: z.string().trim().min(1).max(64), thirdCategories: z .array(z.string().trim().min(1).max(64)) .max(AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS) .optional(), thirdCategoryEntries: z .array( z.object({ categoryName: z.string().trim().min(1).max(64), sortOrder: z.number().min(0).max(999).optional(), }) ) .max(AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS) .optional(), sortOrder: z.number().min(0).max(999).optional(), }) ) .max(AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS) .optional(), priceBands: z .array(z.string().trim().min(1).max(64)) .max(AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS) .optional(), brandStoreEntries: z .array( z.object({ brandName: z.string().trim().min(1).max(64), stores: z .array(z.string().trim().min(1).max(64)) .max(AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS) .optional(), storeEntries: z .array( z.object({ storeName: z.string().trim().min(1).max(64), thirdCategories: z .array(z.string().trim().min(1).max(64)) .max(AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS) .optional(), thirdCategoryEntries: z .array( z.object({ categoryName: z.string().trim().min(1).max(64), fourthCategories: z .array(z.string().trim().min(1).max(64)) .max(AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS) .optional(), fourthCategoryEntries: z .array( z.object({ categoryName: z.string().trim().min(1).max(64), fifthCategories: z .array(z.string().trim().min(1).max(64)) .max(AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS) .optional(), sortOrder: z.number().min(0).max(999).optional(), }) ) .max(AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS) .optional(), sortOrder: z.number().min(0).max(999).optional(), }) ) .max(AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS) .optional(), sortOrder: z.number().min(0).max(999).optional(), }) ) .max(AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS) .optional(), sortOrder: z.number().min(0).max(999).optional(), }) ) .max(AGENT_WORKFLOW_INPUT_OPTIONS_MAX_ITEMS) .optional(), }) .optional(), constraintSettings: z .object({ crossLevelMutualExclusionEnabled: z.boolean().optional(), }) .optional(), pageFeatureSettings: z .object({ presetStepEnabled: z.boolean().optional(), materialStepEnabled: z.boolean().optional(), psdTemplateEnabled: z.boolean().optional(), batchProcessingEnabled: z.boolean().optional(), }) .optional(), businessModeSettings: z .object({ modes: z .array( z.object({ key: z.enum(["main_sku", "detail", "buyer_show"]), title: z.string().trim().min(1).max(40).optional(), description: z.string().trim().max(220).nullable().optional(), enabled: z.boolean().optional(), iconKey: z.enum(["main-sku", "detail", "buyer-show"]).optional(), customSvgUrl: z.string().trim().max(2000).nullable().optional(), defaultModelId: z.string().uuid().nullable().optional(), sortOrder: z.number().min(0).max(99).optional(), referenceReplaceEnabled: z.boolean().optional(), aiDetailGenerateEnabled: z.boolean().optional(), }) ) .max(3) .optional(), }) .optional(), steps: z .array(agentWorkflowStepSchema) .max(20), }) .optional(), agentWorkflowMaterialApiConfig: z .object({ enabled: z.boolean().optional(), baseUrl: z.string().trim().max(255).optional(), endpointPath: z.string().trim().max(500).optional(), apiKey: z.string().trim().max(500).optional(), whiteBackgroundEnabled: z.boolean().optional(), taskDetailEnabled: z.boolean().optional(), resourceBaseUrl: z.string().trim().max(500).optional(), fallbackResourceBaseUrl: z.string().trim().max(500).optional(), requestTimeoutMs: z.number().int().min(3000).max(120000).optional(), productDetailEnabled: z.boolean().optional(), productDetailBaseUrl: z.string().trim().max(500).optional(), productDetailApiKey: z.string().trim().max(500).optional(), productDetailPath: z.string().trim().max(500).optional(), productDetailMethod: z.enum(["GET", "POST"]).optional(), productDetailDefaultChannel: z.enum(["taobao", "1688", "douyin", "jd"]).optional(), productDetailSyncAnalysis: z.boolean().optional(), productDetailForceReanalyze: z.boolean().optional(), }) .optional(), agentWorkflowGoodsInfoApiConfig: z .object({ enabled: z.boolean().optional(), baseUrl: z.string().trim().max(255).optional(), clientKey: z.string().trim().max(255).optional(), secret: z.string().trim().max(255).optional(), }) .optional(), agentWorkflowBrandModelConfig: z .object({ entries: z .array( z.object({ brandName: z.string().trim().min(1).max(64), images: z .array( z.object({ id: z.string().trim().min(1).max(128).optional(), assetId: z.string().trim().max(128).nullable().optional(), imageUrl: z.string().trim().min(1).max(2000), displayName: z.string().trim().max(255).nullable().optional(), mimeType: z.string().trim().max(128).nullable().optional(), sortOrder: z.number().min(0).max(999).optional(), }) ) .max(100), }) ) .max(300), }) .optional(), agentWorkflowShowcaseConfig: z .object({ enabled: z.boolean().optional(), title: z.string().max(200).optional(), subtitle: z.string().max(300).optional(), actionText: z.string().max(48).optional(), videoUrl: z.string().max(2000).optional(), posterUrl: z.string().max(2000).nullable().optional(), overlayText: z.string().max(200).optional(), footerText: z.string().max(64).optional(), }) .optional(), agentPsdTextColorConfig: z .object({ enabled: z.boolean().optional(), colors: z.array(z.string().trim().max(32)).length(2).optional(), brandConfigs: z .array( z.object({ brandName: z.string().trim().min(1).max(64), enabled: z.boolean().optional(), colors: z.array(z.string().trim().max(32)).length(2).optional(), }) ) .max(300) .optional(), }) .optional(), agentPsdTemplateSecondaryCategoryConfig: z .object({ enabled: z.boolean().optional(), }) .optional(), agentPsdTemplateCategoryTagConfig: z .object({ tags: z.array(z.string().trim().min(1).max(64)).max(200).optional(), }) .optional(), realtimeCanvasEnabled: z.boolean().optional(), realtimeCanvasModeIconConfig: z .object({ realtimeIconUrl: z.string().trim().max(2000).optional(), enhanceIconUrl: z.string().trim().max(2000).optional(), realtimeLabel: z.string().trim().max(32).optional(), enhanceLabel: z.string().trim().max(32).optional(), }) .optional(), canvasToolbarConfig: z .object({ selectedImageInlineActionCount: z.number().int().min(3).max(12).optional(), items: z .record( z.object({ label: z.string().trim().max(40).optional(), iconUrl: z.string().trim().max(2000).optional(), enabled: z.boolean().optional(), }) ) .optional(), }) .optional(), lovartHomeSidebarConfig: z .object({ items: z .record( z.object({ label: z.string().trim().max(32).optional(), iconUrl: z.string().trim().max(2000).optional(), enabled: z.boolean().optional(), }) ) .optional(), }) .optional(), aiDetailGenerationEnabled: z.boolean().optional(), canvasDefaultModelId: z.string().nullable().optional(), canvasAutoModeDefaultModelId: z.string().nullable().optional(), agentWorkflowDefaultModelId: z.string().nullable().optional(), canvasDetailDefaultModelId: z.string().nullable().optional(), canvasDetailAgentModelId: z.string().nullable().optional(), lovartPromoText: z.string().max(300).optional(), lovartPromoActionText: z.string().max(32).optional(), lovartHeroTitle: z.string().max(300).optional(), lovartHeroSubtitle: z.string().max(300).optional(), surpriseCategories: z.array(z.string().trim().min(1).max(20)).max(12).optional(), lovartComposerPrefix: z.string().trim().min(1).max(40).optional(), lovartComposerPreviewTexts: z.array(z.string().trim().min(1).max(120)).max(20).optional(), lovartPromptTagVisibility: z .object({ nanoBananaPro: z.boolean().optional(), design: z.boolean().optional(), branding: z.boolean().optional(), illustration: z.boolean().optional(), ecommerce: z.boolean().optional(), video: z.boolean().optional(), }) .optional(), contactConfig: z .object({ phone: z.string().max(64).optional(), wechatQrUrl: z.string().max(1000).optional(), }) .optional(), membershipConfig: z.unknown().optional(), membershipUserOverrides: z.unknown().optional(), pointsSystemConfig: z.unknown().optional(), pointsPackages: z.unknown().optional(), dulupayConfig: z.unknown().optional(), storageFailoverConfig: z .object({ enabled: z.boolean().optional(), backupStorageConfigId: z.string().max(128).optional(), }) .optional(), ossImageMigrationConfig: z .object({ enabled: z.boolean().optional(), preferAliyunForUploads: z.boolean().optional(), sourceStorageConfigId: z.string().max(128).optional(), targetStorageConfigId: z.string().max(128).optional(), migrateAfterHours: z.number().min(1).max(720).optional(), cleanupAfterHours: z.number().min(1).max(1440).optional(), batchSize: z.number().min(1).max(100).optional(), }) .optional(), homeAnimationConfig: z .object({ enabled: z.boolean().optional(), fileName: z.string().trim().max(255).optional(), htmlContent: z.string().max(2_000_000).optional(), updatedAt: z.string().nullable().optional(), }) .optional(), }); // PUT /api/site-settings - 更新网站设置(管理员 / 风格化管理员) export async function PUT(request: NextRequest) { try { const session = await auth(); if (!session?.user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const isAdmin = isSiteAdminType(session.user.type); const canManageAgentWorkflow = isAgentWorkflowAdminType(session.user.type); if (!canManageAgentWorkflow) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } const body = await request.json(); if (!isAdmin) { const allowedKeys = new Set([ "agentPresetLibraryConfig", "agentWorkflowSettingsBaseSignature", "agentWorkflowSettingsBaseSignatures", "agentWorkflowDestructiveChangeApproval", "agentWorkflowUiConfig", "agentWorkflowMaterialApiConfig", "agentWorkflowGoodsInfoApiConfig", "agentWorkflowBrandModelConfig", "agentWorkflowShowcaseConfig", "agentPsdTextColorConfig", "agentPsdTemplateSecondaryCategoryConfig", "agentPsdTemplateCategoryTagConfig", ]); const hasRestrictedKeys = Object.keys(body || {}).some((key) => !allowedKeys.has(key)); if (hasRestrictedKeys) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } } const data = updateSchema.parse(body); const { surpriseCategories, aiDetailGenerationEnabled, lovartComposerPrefix, lovartComposerPreviewTexts, lovartPromptTagVisibility, contactConfig, agentPresetLibraryConfig, agentCategoryTreeConfig, agentSecondaryTypeConfig, agentWorkflowSettingsBaseSignature, agentWorkflowSettingsBaseSignatures, agentWorkflowDestructiveChangeApproval, agentWorkflowUiConfig, agentWorkflowMaterialApiConfig, agentWorkflowGoodsInfoApiConfig, agentWorkflowBrandModelConfig, agentWorkflowShowcaseConfig, agentPsdTextColorConfig, agentPsdTemplateSecondaryCategoryConfig, agentPsdTemplateCategoryTagConfig, membershipConfig, membershipUserOverrides, pointsSystemConfig, pointsPackages, dulupayConfig, storageFailoverConfig, ossImageMigrationConfig, realtimeCanvasModeIconConfig, canvasToolbarConfig, lovartHomeSidebarConfig, canvasAgentModeEnabled, canvasAgentFeatureSettings, videoCanvasEnabled, videoCanvasSidebarEnabled, homeAnimationConfig, ...siteSettingsPayload } = data; const updatedAgentWorkflowFields = ( [ ["agentPresetLibraryConfig", agentPresetLibraryConfig], ["agentWorkflowUiConfig", agentWorkflowUiConfig], ["agentWorkflowMaterialApiConfig", agentWorkflowMaterialApiConfig], ["agentWorkflowGoodsInfoApiConfig", agentWorkflowGoodsInfoApiConfig], ["agentWorkflowBrandModelConfig", agentWorkflowBrandModelConfig], ["agentWorkflowShowcaseConfig", agentWorkflowShowcaseConfig], ["agentPsdTextColorConfig", agentPsdTextColorConfig], ["agentPsdTemplateSecondaryCategoryConfig", agentPsdTemplateSecondaryCategoryConfig], ] as const ) .filter(([, value]) => value !== undefined) .map(([field]) => field); const isUpdatingAgentWorkflowSettings = updatedAgentWorkflowFields.length > 0; let currentWorkflowValues = new Map(); let currentWorkflowSectionSignatures: | ReturnType | null = null; let workflowDestructiveChangeSummary: | ReturnType | null = null; let agentWorkflowSnapshotCreated = false; const createAgentWorkflowDestructiveChangeApprovalToken = (params: { currentSignature: string; nextConfig: unknown; summary: ReturnType; }) => createHash("sha256") .update( JSON.stringify({ currentSignature: params.currentSignature, nextConfig: params.nextConfig, summary: params.summary, }) ) .digest("hex"); if (isUpdatingAgentWorkflowSettings) { if (!agentWorkflowSettingsBaseSignatures && !agentWorkflowSettingsBaseSignature) { return NextResponse.json( { error: "工作流配置已启用多人协作保护,请刷新页面后再保存,避免覆盖其他管理员的数据。", }, { status: 409 } ); } const currentWorkflowRows = await db .select({ key: systemSettings.key, value: systemSettings.value, }) .from(systemSettings) .where(inArray(systemSettings.key, [...AGENT_WORKFLOW_SETTINGS_CONFLICT_KEYS])); currentWorkflowValues = new Map( currentWorkflowRows.map((row) => [row.key, row.value]) ); currentWorkflowSectionSignatures = createAgentWorkflowSettingsSectionSignatures(currentWorkflowValues); const currentWorkflowSignature = agentWorkflowSettingsBaseSignature && !agentWorkflowSettingsBaseSignatures ? createAgentWorkflowSettingsSignature(currentWorkflowValues) : null; const hasWorkflowConflict = agentWorkflowSettingsBaseSignatures ? updatedAgentWorkflowFields.some( (field) => currentWorkflowSectionSignatures![field] !== agentWorkflowSettingsBaseSignatures[field] ) : currentWorkflowSignature !== agentWorkflowSettingsBaseSignature; if (hasWorkflowConflict) { return NextResponse.json( { error: "服务器上的同一块工作流配置已被其他管理员更新,本次保存已阻止。请刷新页面确认最新配置后再保存。", }, { status: 409 } ); } if (agentWorkflowUiConfig !== undefined) { const currentWorkflowUiConfig = normalizeAgentWorkflowUiConfig( currentWorkflowValues.get(AGENT_WORKFLOW_UI_CONFIG_KEY) ); const nextWorkflowUiConfig = normalizeAgentWorkflowUiConfig(agentWorkflowUiConfig); workflowDestructiveChangeSummary = summarizeAgentWorkflowDestructiveChange( currentWorkflowUiConfig, nextWorkflowUiConfig ); if (isAgentWorkflowDestructiveChangeSignificant(workflowDestructiveChangeSummary)) { const approvalToken = createAgentWorkflowDestructiveChangeApprovalToken({ currentSignature: currentWorkflowSectionSignatures.agentWorkflowUiConfig || createAgentWorkflowSettingsSignature( new Map([[AGENT_WORKFLOW_UI_CONFIG_KEY, currentWorkflowUiConfig]]) ), nextConfig: nextWorkflowUiConfig, summary: workflowDestructiveChangeSummary, }); if (agentWorkflowDestructiveChangeApproval?.token !== approvalToken) { return NextResponse.json( { error: "本次保存会删除大量风格化工作流节点或清空关键内容,已被系统拦截。请确认后再次提交。", requiresDestructiveChangeApproval: true, destructiveChange: { ...workflowDestructiveChangeSummary, approvalToken, }, }, { status: 409 } ); } } } } const persistSystemSettingWithHistory = async ( key: string, value: unknown, reason: string ) => { const [existingSetting] = await db .select({ value: systemSettings.value }) .from(systemSettings) .where(eq(systemSettings.key, key)) .limit(1); await db.insert(systemSettingHistory).values({ key, previousValue: existingSetting?.value ?? null, nextValue: value, reason, changedAt: new Date(), changedBy: session.user.id, }); await db .insert(systemSettings) .values({ key, value, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value, updatedAt: new Date(), updatedBy: session.user.id, }, }); }; const persistSystemSetting = async (key: string, value: unknown) => { await db .insert(systemSettings) .values({ key, value, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value, updatedAt: new Date(), updatedBy: session.user.id, }, }); }; const createAgentWorkflowScopeSnapshot = async () => { if (!isUpdatingAgentWorkflowSettings || agentWorkflowSnapshotCreated) { return null; } const [currentSiteSettings] = await db .select({ agentWorkflowDefaultModelId: siteSettings.agentWorkflowDefaultModelId, }) .from(siteSettings) .limit(1); const snapshotData = createSystemSettingSnapshotData( AGENT_WORKFLOW_SNAPSHOT_SCOPE, currentWorkflowValues, { agentWorkflowDefaultModelId: String(currentSiteSettings?.agentWorkflowDefaultModelId || "").trim() || null, } ); const [snapshot] = await db .insert(systemSettingSnapshot) .values({ scope: AGENT_WORKFLOW_SNAPSHOT_SCOPE, label: workflowDestructiveChangeSummary ? "before-destructive-agent-workflow-save" : "before-agent-workflow-save", summary: { ...summarizeSystemSettingSnapshotData(snapshotData), updatedFields: updatedAgentWorkflowFields, destructiveChange: workflowDestructiveChangeSummary, }, data: snapshotData, createdAt: new Date(), createdBy: session.user.id, }) .returning({ id: systemSettingSnapshot.id }); agentWorkflowSnapshotCreated = true; return snapshot?.id ?? null; }; const persistHomeAnimationConfig = async () => { if (homeAnimationConfig === undefined) { return; } const normalized = normalizeHomeChatAnimationConfig(homeAnimationConfig); const htmlBytes = getHomeChatAnimationHtmlBytes(normalized.htmlContent); if (htmlBytes > HOME_CHAT_ANIMATION_MAX_BYTES) { throw new Error( `首页动画 HTML 超过限制,当前 ${(htmlBytes / 1024).toFixed(1)}KB,最大 ${( HOME_CHAT_ANIMATION_MAX_BYTES / 1024 ).toFixed(0)}KB`, ); } await db .insert(systemSettings) .values({ key: HOME_CHAT_ANIMATION_CONFIG_KEY, value: { ...normalized, updatedAt: new Date().toISOString(), }, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: { ...normalized, updatedAt: new Date().toISOString(), }, updatedAt: new Date(), updatedBy: session.user.id, }, }); }; const persistCanvasAgentFeatureSettings = async () => { if (canvasAgentFeatureSettings === undefined) { return; } const normalized = normalizeCanvasAgentFeatureSettings( canvasAgentFeatureSettingsToSystemSettings(canvasAgentFeatureSettings) ); const settingEntries = Object.entries( canvasAgentFeatureSettingsToSystemSettings(normalized) ); await Promise.all( settingEntries.map(([key, value]) => db .insert(systemSettings) .values({ key, value, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value, updatedAt: new Date(), updatedBy: session.user.id, }, }) ) ); }; await createAgentWorkflowScopeSnapshot(); // 查找现有设置 const [existing] = await db.select().from(siteSettings).limit(1); if (existing) { // 更新现有设置 const [updated] = await db .update(siteSettings) .set({ ...siteSettingsPayload, agentEnabled: siteSettingsPayload.agentEnabled ?? existing.agentEnabled ?? true, updatedAt: new Date(), updatedBy: session.user.id, }) .where(eq(siteSettings.id, existing.id)) .returning(); if (surpriseCategories !== undefined) { const normalized = normalizeSurpriseCategories(surpriseCategories); await db .insert(systemSettings) .values({ key: HOME_SURPRISE_CATEGORIES_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (aiDetailGenerationEnabled !== undefined) { await db .insert(systemSettings) .values({ key: AI_DETAIL_GENERATOR_ENABLED_KEY, value: Boolean(aiDetailGenerationEnabled), updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: Boolean(aiDetailGenerationEnabled), updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (lovartComposerPreviewTexts !== undefined) { const normalized = normalizeLovartComposerPreviewTexts(lovartComposerPreviewTexts); await db .insert(systemSettings) .values({ key: LOVART_COMPOSER_PREVIEW_TEXTS_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (lovartComposerPrefix !== undefined) { const normalized = normalizeLovartComposerPrefix(lovartComposerPrefix); await db .insert(systemSettings) .values({ key: LOVART_COMPOSER_PREFIX_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (lovartPromptTagVisibility !== undefined) { const normalized = normalizeLovartPromptTagVisibility(lovartPromptTagVisibility); await db .insert(systemSettings) .values({ key: LOVART_PROMPT_TAG_VISIBILITY_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (contactConfig !== undefined) { const normalized = normalizeContactConfig(contactConfig); await db .insert(systemSettings) .values({ key: CONTACT_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (canvasAgentModeEnabled !== undefined) { const normalized = normalizeCanvasAgentModeEnabled(canvasAgentModeEnabled); await db .insert(systemSettings) .values({ key: CANVAS_AGENT_MODE_ENABLED_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } await persistCanvasAgentFeatureSettings(); if (videoCanvasEnabled !== undefined) { const normalized = normalizeVideoCanvasEnabled(videoCanvasEnabled); await db .insert(systemSettings) .values({ key: VIDEO_CANVAS_ENABLED_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (videoCanvasSidebarEnabled !== undefined) { const normalized = normalizeVideoCanvasSidebarEnabled(videoCanvasSidebarEnabled); await db .insert(systemSettings) .values({ key: VIDEO_CANVAS_SIDEBAR_ENABLED_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (agentPresetLibraryConfig !== undefined) { const normalized = normalizeAgentPresetLibraryConfig(agentPresetLibraryConfig); await persistSystemSettingWithHistory( AGENT_PRESET_LIBRARY_CONFIG_KEY, normalized, "agent-workflow" ); } if (agentCategoryTreeConfig !== undefined) { const normalized = normalizeAgentCategoryTreeConfig(agentCategoryTreeConfig); await db .insert(systemSettings) .values({ key: AGENT_CATEGORY_TREE_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (agentSecondaryTypeConfig !== undefined) { const normalized = normalizeAgentSecondaryTypeConfig(agentSecondaryTypeConfig); await db .insert(systemSettings) .values({ key: AGENT_SECONDARY_TYPE_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (agentWorkflowUiConfig !== undefined) { const normalized = normalizeAgentWorkflowUiConfig(agentWorkflowUiConfig); await persistSystemSettingWithHistory( AGENT_WORKFLOW_UI_CONFIG_KEY, normalized, "agent-workflow" ); } if (agentWorkflowMaterialApiConfig !== undefined) { const normalized = normalizeAgentWorkflowMaterialApiConfig( agentWorkflowMaterialApiConfig ); await persistSystemSettingWithHistory( AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY, normalized, "agent-workflow" ); } if (agentWorkflowGoodsInfoApiConfig !== undefined) { const normalized = normalizeAgentWorkflowGoodsInfoApiConfig( agentWorkflowGoodsInfoApiConfig ); await persistSystemSettingWithHistory( AGENT_WORKFLOW_GOODS_INFO_API_CONFIG_KEY, normalized, "agent-workflow" ); } if (agentWorkflowBrandModelConfig !== undefined) { const normalized = normalizeAgentWorkflowBrandModelConfig( agentWorkflowBrandModelConfig ); await persistSystemSettingWithHistory( AGENT_WORKFLOW_BRAND_MODEL_CONFIG_KEY, normalized, "agent-workflow" ); } if (agentWorkflowShowcaseConfig !== undefined) { const normalized = normalizeAgentWorkflowShowcaseConfig(agentWorkflowShowcaseConfig); await persistSystemSettingWithHistory( AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY, normalized, "agent-workflow" ); } if (agentPsdTextColorConfig !== undefined) { const normalized = normalizeAgentPsdTextColorConfig(agentPsdTextColorConfig); await persistSystemSettingWithHistory( AGENT_PSD_TEXT_COLOR_CONFIG_KEY, normalized, "agent-workflow" ); } if (agentPsdTemplateSecondaryCategoryConfig !== undefined) { const normalized = normalizeAgentPsdTemplateSecondaryCategoryConfig( agentPsdTemplateSecondaryCategoryConfig ); await persistSystemSettingWithHistory( AGENT_PSD_TEMPLATE_SECONDARY_CATEGORY_CONFIG_KEY, normalized, "agent-workflow" ); } if (agentPsdTemplateCategoryTagConfig !== undefined) { const normalized = normalizeAgentPsdTemplateCategoryTagConfig( agentPsdTemplateCategoryTagConfig ); await db .insert(systemSettings) .values({ key: AGENT_PSD_TEMPLATE_CATEGORY_TAG_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (membershipConfig !== undefined) { const normalized = normalizeMembershipConfig(membershipConfig); await db .insert(systemSettings) .values({ key: MEMBERSHIP_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (membershipUserOverrides !== undefined) { const normalized = normalizeMembershipUserOverrides(membershipUserOverrides); await db .insert(systemSettings) .values({ key: MEMBERSHIP_USER_OVERRIDES_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (pointsSystemConfig !== undefined) { const normalized = normalizePointsSystemConfig(pointsSystemConfig); await db .insert(systemSettings) .values({ key: POINTS_SYSTEM_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (pointsPackages !== undefined) { const normalized = normalizePointsPackages(pointsPackages); await db .insert(systemSettings) .values({ key: POINTS_PACKAGES_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (dulupayConfig !== undefined) { const normalized = normalizeDulupayConfig(dulupayConfig); await db .insert(systemSettings) .values({ key: DULUPAY_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (storageFailoverConfig !== undefined) { const normalized = normalizeStorageFailoverConfig(storageFailoverConfig); await db .insert(systemSettings) .values({ key: STORAGE_FAILOVER_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (ossImageMigrationConfig !== undefined) { const normalized = normalizeOssImageMigrationConfig(ossImageMigrationConfig); await db .insert(systemSettings) .values({ key: OSS_MIGRATION_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (realtimeCanvasModeIconConfig !== undefined) { await persistSystemSetting( REALTIME_CANVAS_MODE_ICON_CONFIG_KEY, normalizeRealtimeCanvasModeIconConfig(realtimeCanvasModeIconConfig) ); } if (canvasToolbarConfig !== undefined) { await persistSystemSetting( CANVAS_TOOLBAR_CONFIG_SETTING_KEY, normalizeCanvasToolbarConfig(canvasToolbarConfig) ); } if (lovartHomeSidebarConfig !== undefined) { await persistSystemSetting( LOVART_HOME_SIDEBAR_CONFIG_KEY, normalizeLovartHomeSidebarConfig(lovartHomeSidebarConfig) ); } await persistHomeAnimationConfig(); clearScopedSiteSettingsCache(); return NextResponse.json({ ...updated, surpriseCategories: surpriseCategories !== undefined ? normalizeSurpriseCategories(surpriseCategories) : normalizeSurpriseCategories(undefined), aiDetailGenerationEnabled: aiDetailGenerationEnabled ?? false, lovartComposerPrefix: lovartComposerPrefix !== undefined ? normalizeLovartComposerPrefix(lovartComposerPrefix) : normalizeLovartComposerPrefix(undefined), lovartComposerPreviewTexts: lovartComposerPreviewTexts !== undefined ? normalizeLovartComposerPreviewTexts(lovartComposerPreviewTexts) : normalizeLovartComposerPreviewTexts(undefined), lovartPromptTagVisibility: lovartPromptTagVisibility !== undefined ? normalizeLovartPromptTagVisibility(lovartPromptTagVisibility) : normalizeLovartPromptTagVisibility(undefined), contactConfig: contactConfig !== undefined ? normalizeContactConfig(contactConfig) : normalizeContactConfig(undefined), agentPresetLibraryConfig: agentPresetLibraryConfig !== undefined ? normalizeAgentPresetLibraryConfig(agentPresetLibraryConfig) : defaultAgentPresetLibraryConfig, agentCategoryTreeConfig: agentCategoryTreeConfig !== undefined ? normalizeAgentCategoryTreeConfig(agentCategoryTreeConfig) : normalizeAgentCategoryTreeConfig(undefined), agentSecondaryTypeConfig: agentSecondaryTypeConfig !== undefined ? normalizeAgentSecondaryTypeConfig(agentSecondaryTypeConfig) : defaultAgentSecondaryTypeConfig, agentWorkflowUiConfig: agentWorkflowUiConfig !== undefined ? normalizeAgentWorkflowUiConfig(agentWorkflowUiConfig) : defaultAgentWorkflowUiConfig, agentWorkflowMaterialApiConfig: agentWorkflowMaterialApiConfig !== undefined ? normalizeAgentWorkflowMaterialApiConfig(agentWorkflowMaterialApiConfig) : defaultAgentWorkflowMaterialApiConfig, agentWorkflowGoodsInfoApiConfig: agentWorkflowGoodsInfoApiConfig !== undefined ? normalizeAgentWorkflowGoodsInfoApiConfig(agentWorkflowGoodsInfoApiConfig) : defaultAgentWorkflowGoodsInfoApiConfig, agentWorkflowBrandModelConfig: agentWorkflowBrandModelConfig !== undefined ? normalizeAgentWorkflowBrandModelConfig(agentWorkflowBrandModelConfig) : defaultAgentWorkflowBrandModelConfig, agentWorkflowShowcaseConfig: agentWorkflowShowcaseConfig !== undefined ? normalizeAgentWorkflowShowcaseConfig(agentWorkflowShowcaseConfig) : defaultAgentWorkflowShowcaseConfig, agentPsdTextColorConfig: agentPsdTextColorConfig !== undefined ? normalizeAgentPsdTextColorConfig(agentPsdTextColorConfig) : defaultAgentPsdTextColorConfig, agentPsdTemplateSecondaryCategoryConfig: agentPsdTemplateSecondaryCategoryConfig !== undefined ? normalizeAgentPsdTemplateSecondaryCategoryConfig( agentPsdTemplateSecondaryCategoryConfig ) : defaultAgentPsdTemplateSecondaryCategoryConfig, agentPsdTemplateCategoryTagConfig: agentPsdTemplateCategoryTagConfig !== undefined ? normalizeAgentPsdTemplateCategoryTagConfig(agentPsdTemplateCategoryTagConfig) : defaultAgentPsdTemplateCategoryTagConfig, membershipConfig: membershipConfig !== undefined ? normalizeMembershipConfig(membershipConfig) : defaultMembershipConfig, membershipUserOverrides: membershipUserOverrides !== undefined ? normalizeMembershipUserOverrides(membershipUserOverrides) : defaultMembershipUserOverrides, pointsSystemConfig: pointsSystemConfig !== undefined ? normalizePointsSystemConfig(pointsSystemConfig) : defaultPointsSystemConfig, pointsPackages: pointsPackages !== undefined ? normalizePointsPackages(pointsPackages) : defaultPointsPackages, dulupayConfig: dulupayConfig !== undefined ? normalizeDulupayConfig(dulupayConfig) : defaultDulupayConfig, storageFailoverConfig: storageFailoverConfig !== undefined ? normalizeStorageFailoverConfig(storageFailoverConfig) : normalizeStorageFailoverConfig(undefined), ossImageMigrationConfig: ossImageMigrationConfig !== undefined ? normalizeOssImageMigrationConfig(ossImageMigrationConfig) : defaultOssImageMigrationConfig, realtimeCanvasModeIconConfig: realtimeCanvasModeIconConfig !== undefined ? normalizeRealtimeCanvasModeIconConfig(realtimeCanvasModeIconConfig) : defaultRealtimeCanvasModeIconConfig, canvasToolbarConfig: canvasToolbarConfig !== undefined ? normalizeCanvasToolbarConfig(canvasToolbarConfig) : defaultCanvasToolbarConfig, lovartHomeSidebarConfig: lovartHomeSidebarConfig !== undefined ? normalizeLovartHomeSidebarConfig(lovartHomeSidebarConfig) : defaultLovartHomeSidebarConfig, canvasAgentModeEnabled: canvasAgentModeEnabled !== undefined ? normalizeCanvasAgentModeEnabled(canvasAgentModeEnabled) : normalizeCanvasAgentModeEnabled(undefined), videoCanvasEnabled: videoCanvasEnabled !== undefined ? normalizeVideoCanvasEnabled(videoCanvasEnabled) : normalizeVideoCanvasEnabled(undefined, videoCanvasSidebarEnabled), videoCanvasSidebarEnabled: videoCanvasSidebarEnabled !== undefined ? normalizeVideoCanvasSidebarEnabled(videoCanvasSidebarEnabled) : normalizeVideoCanvasSidebarEnabled(undefined), }); } else { // 创建新设置 const [created] = await db .insert(siteSettings) .values({ siteName: siteSettingsPayload.siteName || "AI Chatbot", siteLogo: siteSettingsPayload.siteLogo, siteLogoDark: siteSettingsPayload.siteLogoDark, favicon: siteSettingsPayload.favicon, showAutoModel: siteSettingsPayload.showAutoModel ?? true, defaultTheme: siteSettingsPayload.defaultTheme ?? "system", usageTimeEnabled: siteSettingsPayload.usageTimeEnabled ?? false, usageTimeStart: siteSettingsPayload.usageTimeStart ?? "08:00", usageTimeEnd: siteSettingsPayload.usageTimeEnd ?? "20:00", usageTimeMessage: siteSettingsPayload.usageTimeMessage ?? "BuYi 累了也是需要休息的", bannerEnabled: siteSettingsPayload.bannerEnabled ?? true, bannerText: siteSettingsPayload.bannerText ?? "限时特惠:升级会员最高立享 45% OFF!", bannerLink: siteSettingsPayload.bannerLink, bannerActionText: siteSettingsPayload.bannerActionText ?? "立即查看", toolbox1Name: siteSettingsPayload.toolbox1Name ?? "工具箱", toolbox1Enabled: siteSettingsPayload.toolbox1Enabled ?? true, toolbox2Name: siteSettingsPayload.toolbox2Name ?? "工具箱2", toolbox2Enabled: siteSettingsPayload.toolbox2Enabled ?? false, tutorialVideoUrl: siteSettingsPayload.tutorialVideoUrl, tutorialEnabled: siteSettingsPayload.tutorialEnabled ?? false, rankingEnabled: siteSettingsPayload.rankingEnabled ?? true, productPlanningEnabled: siteSettingsPayload.productPlanningEnabled ?? true, canvasEnabled: siteSettingsPayload.canvasEnabled ?? true, agentEnabled: siteSettingsPayload.agentEnabled ?? true, realtimeCanvasEnabled: siteSettingsPayload.realtimeCanvasEnabled ?? true, canvasDefaultModelId: siteSettingsPayload.canvasDefaultModelId, canvasAutoModeDefaultModelId: siteSettingsPayload.canvasAutoModeDefaultModelId, agentWorkflowDefaultModelId: siteSettingsPayload.agentWorkflowDefaultModelId, canvasDetailDefaultModelId: siteSettingsPayload.canvasDetailDefaultModelId, canvasDetailAgentModelId: siteSettingsPayload.canvasDetailAgentModelId, lovartPromoText: siteSettingsPayload.lovartPromoText ?? "现在升级,立享 Kling 3.0 与 Nano Banana Pro,最高 5 折优惠", lovartPromoActionText: siteSettingsPayload.lovartPromoActionText ?? "立即升级", lovartHeroTitle: siteSettingsPayload.lovartHeroTitle ?? "𝗕𝘂𝗬𝗶 让设计触手可及", lovartHeroSubtitle: siteSettingsPayload.lovartHeroSubtitle ?? "下达指令,即刻呈现", updatedBy: session.user.id, }) .returning(); if (surpriseCategories !== undefined) { const normalized = normalizeSurpriseCategories(surpriseCategories); await db .insert(systemSettings) .values({ key: HOME_SURPRISE_CATEGORIES_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } await persistHomeAnimationConfig(); if (aiDetailGenerationEnabled !== undefined) { await db .insert(systemSettings) .values({ key: AI_DETAIL_GENERATOR_ENABLED_KEY, value: Boolean(aiDetailGenerationEnabled), updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: Boolean(aiDetailGenerationEnabled), updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (lovartComposerPreviewTexts !== undefined) { const normalized = normalizeLovartComposerPreviewTexts(lovartComposerPreviewTexts); await db .insert(systemSettings) .values({ key: LOVART_COMPOSER_PREVIEW_TEXTS_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (lovartComposerPrefix !== undefined) { const normalized = normalizeLovartComposerPrefix(lovartComposerPrefix); await db .insert(systemSettings) .values({ key: LOVART_COMPOSER_PREFIX_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (lovartPromptTagVisibility !== undefined) { const normalized = normalizeLovartPromptTagVisibility(lovartPromptTagVisibility); await db .insert(systemSettings) .values({ key: LOVART_PROMPT_TAG_VISIBILITY_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (contactConfig !== undefined) { const normalized = normalizeContactConfig(contactConfig); await db .insert(systemSettings) .values({ key: CONTACT_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (canvasAgentModeEnabled !== undefined) { const normalized = normalizeCanvasAgentModeEnabled(canvasAgentModeEnabled); await db .insert(systemSettings) .values({ key: CANVAS_AGENT_MODE_ENABLED_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } await persistCanvasAgentFeatureSettings(); if (videoCanvasSidebarEnabled !== undefined) { const normalized = normalizeVideoCanvasSidebarEnabled(videoCanvasSidebarEnabled); await db .insert(systemSettings) .values({ key: VIDEO_CANVAS_SIDEBAR_ENABLED_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (agentPresetLibraryConfig !== undefined) { const normalized = normalizeAgentPresetLibraryConfig(agentPresetLibraryConfig); await persistSystemSettingWithHistory( AGENT_PRESET_LIBRARY_CONFIG_KEY, normalized, "agent-workflow" ); } if (agentCategoryTreeConfig !== undefined) { const normalized = normalizeAgentCategoryTreeConfig(agentCategoryTreeConfig); await db .insert(systemSettings) .values({ key: AGENT_CATEGORY_TREE_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (agentSecondaryTypeConfig !== undefined) { const normalized = normalizeAgentSecondaryTypeConfig(agentSecondaryTypeConfig); await db .insert(systemSettings) .values({ key: AGENT_SECONDARY_TYPE_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (agentWorkflowUiConfig !== undefined) { const normalized = normalizeAgentWorkflowUiConfig(agentWorkflowUiConfig); await persistSystemSettingWithHistory( AGENT_WORKFLOW_UI_CONFIG_KEY, normalized, "agent-workflow" ); } if (agentWorkflowMaterialApiConfig !== undefined) { const normalized = normalizeAgentWorkflowMaterialApiConfig( agentWorkflowMaterialApiConfig ); await persistSystemSettingWithHistory( AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY, normalized, "agent-workflow" ); } if (agentWorkflowBrandModelConfig !== undefined) { const normalized = normalizeAgentWorkflowBrandModelConfig( agentWorkflowBrandModelConfig ); await persistSystemSettingWithHistory( AGENT_WORKFLOW_BRAND_MODEL_CONFIG_KEY, normalized, "agent-workflow" ); } if (agentWorkflowShowcaseConfig !== undefined) { const normalized = normalizeAgentWorkflowShowcaseConfig(agentWorkflowShowcaseConfig); await persistSystemSettingWithHistory( AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY, normalized, "agent-workflow" ); } if (agentPsdTextColorConfig !== undefined) { const normalized = normalizeAgentPsdTextColorConfig(agentPsdTextColorConfig); await persistSystemSettingWithHistory( AGENT_PSD_TEXT_COLOR_CONFIG_KEY, normalized, "agent-workflow" ); } if (agentPsdTemplateCategoryTagConfig !== undefined) { const normalized = normalizeAgentPsdTemplateCategoryTagConfig( agentPsdTemplateCategoryTagConfig ); await db .insert(systemSettings) .values({ key: AGENT_PSD_TEMPLATE_CATEGORY_TAG_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (membershipConfig !== undefined) { const normalized = normalizeMembershipConfig(membershipConfig); await db .insert(systemSettings) .values({ key: MEMBERSHIP_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (membershipUserOverrides !== undefined) { const normalized = normalizeMembershipUserOverrides(membershipUserOverrides); await db .insert(systemSettings) .values({ key: MEMBERSHIP_USER_OVERRIDES_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (pointsSystemConfig !== undefined) { const normalized = normalizePointsSystemConfig(pointsSystemConfig); await db .insert(systemSettings) .values({ key: POINTS_SYSTEM_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (pointsPackages !== undefined) { const normalized = normalizePointsPackages(pointsPackages); await db .insert(systemSettings) .values({ key: POINTS_PACKAGES_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (dulupayConfig !== undefined) { const normalized = normalizeDulupayConfig(dulupayConfig); await db .insert(systemSettings) .values({ key: DULUPAY_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (storageFailoverConfig !== undefined) { const normalized = normalizeStorageFailoverConfig(storageFailoverConfig); await db .insert(systemSettings) .values({ key: STORAGE_FAILOVER_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (ossImageMigrationConfig !== undefined) { const normalized = normalizeOssImageMigrationConfig(ossImageMigrationConfig); await db .insert(systemSettings) .values({ key: OSS_MIGRATION_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (realtimeCanvasModeIconConfig !== undefined) { await persistSystemSetting( REALTIME_CANVAS_MODE_ICON_CONFIG_KEY, normalizeRealtimeCanvasModeIconConfig(realtimeCanvasModeIconConfig) ); } if (canvasToolbarConfig !== undefined) { await persistSystemSetting( CANVAS_TOOLBAR_CONFIG_SETTING_KEY, normalizeCanvasToolbarConfig(canvasToolbarConfig) ); } if (lovartHomeSidebarConfig !== undefined) { await persistSystemSetting( LOVART_HOME_SIDEBAR_CONFIG_KEY, normalizeLovartHomeSidebarConfig(lovartHomeSidebarConfig) ); } if (videoCanvasEnabled !== undefined) { const normalized = normalizeVideoCanvasEnabled(videoCanvasEnabled); await db .insert(systemSettings) .values({ key: VIDEO_CANVAS_ENABLED_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } clearScopedSiteSettingsCache(); return NextResponse.json({ ...created, surpriseCategories: surpriseCategories !== undefined ? normalizeSurpriseCategories(surpriseCategories) : normalizeSurpriseCategories(undefined), aiDetailGenerationEnabled: aiDetailGenerationEnabled ?? false, lovartComposerPrefix: lovartComposerPrefix !== undefined ? normalizeLovartComposerPrefix(lovartComposerPrefix) : normalizeLovartComposerPrefix(undefined), lovartComposerPreviewTexts: lovartComposerPreviewTexts !== undefined ? normalizeLovartComposerPreviewTexts(lovartComposerPreviewTexts) : normalizeLovartComposerPreviewTexts(undefined), lovartPromptTagVisibility: lovartPromptTagVisibility !== undefined ? normalizeLovartPromptTagVisibility(lovartPromptTagVisibility) : normalizeLovartPromptTagVisibility(undefined), contactConfig: contactConfig !== undefined ? normalizeContactConfig(contactConfig) : normalizeContactConfig(undefined), agentPresetLibraryConfig: agentPresetLibraryConfig !== undefined ? normalizeAgentPresetLibraryConfig(agentPresetLibraryConfig) : defaultAgentPresetLibraryConfig, agentCategoryTreeConfig: agentCategoryTreeConfig !== undefined ? normalizeAgentCategoryTreeConfig(agentCategoryTreeConfig) : normalizeAgentCategoryTreeConfig(undefined), agentSecondaryTypeConfig: agentSecondaryTypeConfig !== undefined ? normalizeAgentSecondaryTypeConfig(agentSecondaryTypeConfig) : defaultAgentSecondaryTypeConfig, agentWorkflowUiConfig: agentWorkflowUiConfig !== undefined ? normalizeAgentWorkflowUiConfig(agentWorkflowUiConfig) : defaultAgentWorkflowUiConfig, agentWorkflowMaterialApiConfig: agentWorkflowMaterialApiConfig !== undefined ? normalizeAgentWorkflowMaterialApiConfig(agentWorkflowMaterialApiConfig) : defaultAgentWorkflowMaterialApiConfig, agentWorkflowGoodsInfoApiConfig: agentWorkflowGoodsInfoApiConfig !== undefined ? normalizeAgentWorkflowGoodsInfoApiConfig(agentWorkflowGoodsInfoApiConfig) : defaultAgentWorkflowGoodsInfoApiConfig, agentWorkflowBrandModelConfig: agentWorkflowBrandModelConfig !== undefined ? normalizeAgentWorkflowBrandModelConfig(agentWorkflowBrandModelConfig) : defaultAgentWorkflowBrandModelConfig, agentWorkflowShowcaseConfig: agentWorkflowShowcaseConfig !== undefined ? normalizeAgentWorkflowShowcaseConfig(agentWorkflowShowcaseConfig) : defaultAgentWorkflowShowcaseConfig, agentPsdTextColorConfig: agentPsdTextColorConfig !== undefined ? normalizeAgentPsdTextColorConfig(agentPsdTextColorConfig) : defaultAgentPsdTextColorConfig, agentPsdTemplateSecondaryCategoryConfig: agentPsdTemplateSecondaryCategoryConfig !== undefined ? normalizeAgentPsdTemplateSecondaryCategoryConfig( agentPsdTemplateSecondaryCategoryConfig ) : defaultAgentPsdTemplateSecondaryCategoryConfig, agentPsdTemplateCategoryTagConfig: agentPsdTemplateCategoryTagConfig !== undefined ? normalizeAgentPsdTemplateCategoryTagConfig(agentPsdTemplateCategoryTagConfig) : defaultAgentPsdTemplateCategoryTagConfig, membershipConfig: membershipConfig !== undefined ? normalizeMembershipConfig(membershipConfig) : defaultMembershipConfig, membershipUserOverrides: membershipUserOverrides !== undefined ? normalizeMembershipUserOverrides(membershipUserOverrides) : defaultMembershipUserOverrides, pointsSystemConfig: pointsSystemConfig !== undefined ? normalizePointsSystemConfig(pointsSystemConfig) : defaultPointsSystemConfig, pointsPackages: pointsPackages !== undefined ? normalizePointsPackages(pointsPackages) : defaultPointsPackages, dulupayConfig: dulupayConfig !== undefined ? normalizeDulupayConfig(dulupayConfig) : defaultDulupayConfig, storageFailoverConfig: storageFailoverConfig !== undefined ? normalizeStorageFailoverConfig(storageFailoverConfig) : normalizeStorageFailoverConfig(undefined), ossImageMigrationConfig: ossImageMigrationConfig !== undefined ? normalizeOssImageMigrationConfig(ossImageMigrationConfig) : defaultOssImageMigrationConfig, realtimeCanvasModeIconConfig: realtimeCanvasModeIconConfig !== undefined ? normalizeRealtimeCanvasModeIconConfig(realtimeCanvasModeIconConfig) : defaultRealtimeCanvasModeIconConfig, canvasToolbarConfig: canvasToolbarConfig !== undefined ? normalizeCanvasToolbarConfig(canvasToolbarConfig) : defaultCanvasToolbarConfig, lovartHomeSidebarConfig: lovartHomeSidebarConfig !== undefined ? normalizeLovartHomeSidebarConfig(lovartHomeSidebarConfig) : defaultLovartHomeSidebarConfig, canvasAgentModeEnabled: canvasAgentModeEnabled !== undefined ? normalizeCanvasAgentModeEnabled(canvasAgentModeEnabled) : normalizeCanvasAgentModeEnabled(undefined), videoCanvasEnabled: videoCanvasEnabled !== undefined ? normalizeVideoCanvasEnabled(videoCanvasEnabled) : normalizeVideoCanvasEnabled(undefined, videoCanvasSidebarEnabled), videoCanvasSidebarEnabled: videoCanvasSidebarEnabled !== undefined ? normalizeVideoCanvasSidebarEnabled(videoCanvasSidebarEnabled) : normalizeVideoCanvasSidebarEnabled(undefined), }); } } catch (error) { console.error("[Site Settings API] Update error:", error); if (error instanceof z.ZodError) { return NextResponse.json( { error: "Invalid request data", details: error.errors }, { status: 400 } ); } if (error instanceof Error) { return NextResponse.json( { error: error.message || "Failed to update site settings" }, { status: 500 } ); } return NextResponse.json( { error: "Failed to update site settings" }, { status: 500 } ); } }