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