2026-05-26 20:10:36 +08:00

3455 lines
120 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { db } from "@/lib/db";
import { aiProvider, siteSettings, systemSettingHistory, 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,
normalizeAgentWorkflowStepOrdering,
normalizeAgentWorkflowUiConfig,
} 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 {
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 {
getHomeChatAnimationHtmlBytes,
HOME_CHAT_ANIMATION_CONFIG_KEY,
HOME_CHAT_ANIMATION_MAX_BYTES,
normalizeHomeChatAnimationConfig,
} from "@/lib/home/home-animation-config";
import {
ENHANCE_PRO_KREA_SETTING_KEY,
normalizeEnhanceProKreaConfig,
} from "@/lib/enhance-pro/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<string, unknown>) {
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 {
angleEditEnabled: normalizeAngleEditEnabled(
valueByKey.get(ANGLE_EDIT_CONFIG_KEY),
),
enhanceProEnabled: normalizeEnhanceProKreaConfig(
valueByKey.get(ENHANCE_PRO_KREA_SETTING_KEY),
).enabled,
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<string, unknown>)
: {};
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<string, unknown>)
.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<string, unknown>) {
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<AgentWorkflowSettingsField, unknown>;
}
function createAgentWorkflowSettingsSectionSignatures(valueByKey: Map<string, unknown>) {
const state = createAgentWorkflowSettingsStateFromValues(valueByKey);
return Object.fromEntries(
Object.entries(state).map(([field, value]) => [field, createSettingsValueSignature(value)])
) as Record<AgentWorkflowSettingsField, string>;
}
function createAgentWorkflowSettingsSignature(valueByKey: Map<string, unknown>): 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 "让Buyi-ai";
}
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<string, unknown>)
: {};
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<string, unknown>)
: {};
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<string, unknown>)
: {};
const backupStorageConfigId = String(raw.backupStorageConfigId || "").trim();
return {
enabled: raw.enabled === true,
backupStorageConfigId,
};
}
function normalizeCanvasAgentModeEnabled(value: unknown): boolean {
return value !== false;
}
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<string, unknown>)
: {};
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<string>([
"agent-brand-model",
"agent-canvas",
"agent-presets",
"auth",
"branding",
"canvas",
"canvas-chat",
"chat",
"home",
]);
const HOME_SITE_SETTINGS_FALLBACK: Record<string, unknown> = {
siteName: "AI Chatbot",
siteLogo: null,
siteLogoDark: null,
favicon: null,
lovartPromoText: "现在升级,立享 Kling 3.0 与 Nano Banana Pro,最高 5 折优惠",
lovartPromoActionText: "立即升级",
lovartHeroTitle: "由壹点AI 让设计更简单",
lovartHeroSubtitle: "懂你的设计代理,帮你搞定一切",
surpriseCategories: ["全部", "精选"],
canvasDefaultModelId: null,
lovartComposerPrefix: "让Buyi-ai",
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<string, unknown>,
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<string, unknown> | undefined) {
return {
siteName: String(settings?.siteName || "AI Chatbot"),
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<string, unknown> | 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,
VIDEO_CANVAS_ENABLED_KEY,
VIDEO_CANVAS_SIDEBAR_ENABLED_KEY,
ENHANCE_PRO_KREA_SETTING_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<string, unknown> | 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)
),
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,
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<string, unknown> | undefined)
);
}
if (scope === "chat") {
const { settings } = await readScopedSiteSettings([]);
const typedSettings = settings as Record<string, unknown> | 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 || "由壹点累了也是需要休息的",
});
}
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,
VIDEO_CANVAS_ENABLED_KEY,
VIDEO_CANVAS_SIDEBAR_ENABLED_KEY,
ENHANCE_PRO_KREA_SETTING_KEY,
STYLE_PRO_CONFIG_KEY,
]);
const canvasFeatureAvailability = await readCanvasFeatureAvailability(valueByKey);
const typedSettings = settings as Record<string, unknown> | 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)
),
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,
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,
]);
const typedSettings = settings as Record<string, unknown> | undefined;
return createScopedSiteSettingsResponse(scope, {
...createBrandingSettings(typedSettings),
bannerEnabled: typedSettings?.bannerEnabled ?? true,
bannerText:
typedSettings?.bannerText || "升级会员, Nano Banana Pro 免费365天!",
bannerLink: typedSettings?.bannerLink || null,
lovartComposerPrefix: normalizeLovartComposerPrefix(
valueByKey.get(LOVART_COMPOSER_PREFIX_KEY)
),
lovartComposerPreviewTexts: normalizeLovartComposerPreviewTexts(
valueByKey.get(LOVART_COMPOSER_PREVIEW_TEXTS_KEY)
),
canvasEnabled: typedSettings?.canvasEnabled ?? true,
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)
),
});
}
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<string, unknown> | undefined;
return createScopedSiteSettingsResponse(scope, {
...createBrandingSettings(typedSettings),
lovartPromoText:
typedSettings?.lovartPromoText ||
"现在升级,立享 Kling 3.0 与 Nano Banana Pro,最高 5 折优惠",
lovartPromoActionText: typedSettings?.lovartPromoActionText || "立即升级",
lovartHeroTitle: typedSettings?.lovartHeroTitle || "由壹点AI 让设计更简单",
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") || "";
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,
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()
.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 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: "由壹点累了也是需要休息的",
bannerEnabled: true,
bannerText: "升级会员, Nano Banana Pro 免费365天!",
bannerLink: null,
toolbox1Name: "工具箱",
toolbox1Enabled: true,
toolbox2Name: "工具箱2",
toolbox2Enabled: false,
tutorialVideoUrl: null,
tutorialEnabled: false,
rankingEnabled: true,
productPlanningEnabled: true,
canvasEnabled: true,
agentEnabled: true,
canvasAgentModeEnabled: true,
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: "由壹点AI 让设计更简单",
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 || "由壹点AI 让设计更简单",
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,
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(),
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(),
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(),
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(),
apiKey: z.string().trim().max(500).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({
items: z
.record(
z.object({
label: z.string().trim().max(40).optional(),
iconUrl: z.string().trim().max(2000).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",
"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,
agentWorkflowUiConfig,
agentWorkflowMaterialApiConfig,
agentWorkflowGoodsInfoApiConfig,
agentWorkflowBrandModelConfig,
agentWorkflowShowcaseConfig,
agentPsdTextColorConfig,
agentPsdTemplateSecondaryCategoryConfig,
agentPsdTemplateCategoryTagConfig,
membershipConfig,
membershipUserOverrides,
pointsSystemConfig,
pointsPackages,
dulupayConfig,
storageFailoverConfig,
ossImageMigrationConfig,
realtimeCanvasModeIconConfig,
canvasToolbarConfig,
lovartHomeSidebarConfig,
canvasAgentModeEnabled,
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;
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]));
const currentWorkflowValues = new Map<string, unknown>(
currentWorkflowRows.map((row) => [row.key, row.value])
);
const currentSectionSignatures =
createAgentWorkflowSettingsSectionSignatures(currentWorkflowValues);
const currentWorkflowSignature =
agentWorkflowSettingsBaseSignature && !agentWorkflowSettingsBaseSignatures
? createAgentWorkflowSettingsSignature(currentWorkflowValues)
: null;
const hasWorkflowConflict = agentWorkflowSettingsBaseSignatures
? updatedAgentWorkflowFields.some(
(field) =>
currentSectionSignatures[field] !== agentWorkflowSettingsBaseSignatures[field]
)
: currentWorkflowSignature !== agentWorkflowSettingsBaseSignature;
if (hasWorkflowConflict) {
return NextResponse.json(
{
error:
"服务器上的同一块工作流配置已被其他管理员更新,本次保存已阻止。请刷新页面确认最新配置后再保存。",
},
{ 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 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 [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,
},
});
}
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 ?? "由壹点累了也是需要休息的",
bannerEnabled: siteSettingsPayload.bannerEnabled ?? true,
bannerText: siteSettingsPayload.bannerText ?? "升级会员, Nano Banana Pro 免费365天!",
bannerLink: siteSettingsPayload.bannerLink,
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 ?? "由壹点AI 让设计更简单",
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,
},
});
}
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 }
);
}
}