Your Name 076e4f55e6
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: add workflow page feature toggles
2026-04-19 15:36:29 +08:00

2672 lines
93 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 { siteSettings, systemSettingHistory, systemSettings } from "@/lib/db/schema";
import { 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_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 {
defaultOssImageMigrationConfig,
normalizeOssImageMigrationConfig,
OSS_MIGRATION_CONFIG_KEY,
} from "@/lib/storage/migration-config";
import {
getHomeChatAnimationHtmlBytes,
HOME_CHAT_ANIMATION_CONFIG_KEY,
HOME_CHAT_ANIMATION_MAX_BYTES,
normalizeHomeChatAnimationConfig,
} from "@/lib/home/home-animation-config";
import type { AgentCategoryTreeConfig } from "@/types/agent";
const HOME_VARIANT_KEY = "home_variant";
// 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 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;
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 normalizeHomeVariant(value: unknown): "classic" | "lovart" {
return value === "lovart" ? "lovart" : "classic";
}
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 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),
};
}
// GET /api/site-settings - 获取网站设置 (公开)
export async function GET() {
try {
const session = await auth().catch(() => null);
const isAdmin = isSiteAdminType(session?.user?.type);
const canManageAgentWorkflow = isAgentWorkflowAdminType(session?.user?.type);
const [
settings,
notificationSetting,
homeVariantSetting,
surpriseCategoriesSetting,
aiDetailGeneratorEnabledSetting,
lovartComposerPreviewTextsSetting,
lovartComposerPrefixSetting,
lovartPromptTagVisibilitySetting,
contactConfigSetting,
membershipConfigSetting,
membershipOverridesSetting,
pointsSystemConfigSetting,
pointsPackagesSetting,
dulupayConfigSetting,
realtimeCanvasConfigSetting,
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_VARIANT_KEY))
.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, 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 homeVariant = normalizeHomeVariant(homeVariantSetting?.value);
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 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,
realtimeCanvasServiceEnabled: true,
layerSplitEnabled,
storageFailoverConfig,
ossImageMigrationConfig,
canvasDefaultModelId: null,
canvasAutoModeDefaultModelId: null,
canvasDetailDefaultModelId: null,
canvasDetailAgentModelId: null,
lovartPromoText: "现在升级,立享 Kling 3.0 与 Nano Banana Pro,最高 5 折优惠",
lovartPromoActionText: "立即升级",
lovartHeroTitle: "由壹点AI 让设计更简单",
lovartHeroSubtitle: "懂你的设计代理,帮你搞定一切",
homeVariant,
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,
homeVariant,
surpriseCategories,
aiDetailGenerationEnabled,
lovartComposerPrefix,
lovartComposerPreviewTexts,
lovartPromptTagVisibility,
contactConfig,
agentPresetLibraryConfig,
agentCategoryTreeConfig,
agentSecondaryTypeConfig,
agentWorkflowUiConfig,
agentWorkflowMaterialApiConfig,
agentWorkflowGoodsInfoApiConfig,
agentWorkflowBrandModelConfig,
agentWorkflowShowcaseConfig,
agentPsdTextColorConfig,
agentPsdTemplateSecondaryCategoryConfig,
agentPsdTemplateCategoryTagConfig,
agentPsdTemplateCategoryTagIndex,
membershipConfig,
membershipUserOverrides,
pointsSystemConfig,
pointsPackages,
dulupayConfig,
realtimeCanvasServiceEnabled,
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(),
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(100).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(),
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(),
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(),
})
.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(),
})
.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(),
aiDetailGenerationEnabled: z.boolean().optional(),
canvasDefaultModelId: z.string().nullable().optional(),
canvasAutoModeDefaultModelId: 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(),
homeVariant: z.enum(["classic", "lovart"]).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 {
homeVariant,
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,
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 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 (homeVariant !== undefined) {
await db
.insert(systemSettings)
.values({
key: HOME_VARIANT_KEY,
value: homeVariant,
updatedAt: new Date(),
updatedBy: session.user.id,
})
.onConflictDoUpdate({
target: systemSettings.key,
set: {
value: homeVariant,
updatedAt: new Date(),
updatedBy: session.user.id,
},
});
}
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,
},
});
}
await persistHomeAnimationConfig();
return NextResponse.json({
...updated,
homeVariant: homeVariant ?? normalizeHomeVariant(undefined),
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,
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,
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 (homeVariant !== undefined) {
await db
.insert(systemSettings)
.values({
key: HOME_VARIANT_KEY,
value: homeVariant,
updatedAt: new Date(),
updatedBy: session.user.id,
})
.onConflictDoUpdate({
target: systemSettings.key,
set: {
value: homeVariant,
updatedAt: new Date(),
updatedBy: session.user.id,
},
});
}
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 (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,
},
});
}
return NextResponse.json({
...created,
homeVariant: homeVariant ?? normalizeHomeVariant(undefined),
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,
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 }
);
}
}