fix: protect workflow settings from overwrites
This commit is contained in:
parent
c5a4b4add2
commit
8c8d2c07f5
@ -2737,8 +2737,13 @@ export function AdminSettingsClient() {
|
|||||||
siteSettings.pointsSystemConfig
|
siteSettings.pointsSystemConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
const payload = {
|
const siteSettingsPayload: Partial<SiteSettings> = {
|
||||||
...siteSettings,
|
...siteSettings,
|
||||||
|
};
|
||||||
|
delete siteSettingsPayload.agentWorkflowUiConfig;
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
...siteSettingsPayload,
|
||||||
homeAnimationConfig: {
|
homeAnimationConfig: {
|
||||||
enabled: homeAnimationConfig.enabled,
|
enabled: homeAnimationConfig.enabled,
|
||||||
fileName: homeAnimationConfig.fileName.trim(),
|
fileName: homeAnimationConfig.fileName.trim(),
|
||||||
@ -2762,7 +2767,10 @@ export function AdminSettingsClient() {
|
|||||||
homeAnimationConfig: _ignoredHomeAnimationConfig,
|
homeAnimationConfig: _ignoredHomeAnimationConfig,
|
||||||
...nextSiteSettings
|
...nextSiteSettings
|
||||||
} = payload;
|
} = payload;
|
||||||
setSiteSettings(nextSiteSettings);
|
setSiteSettings((prev) => ({
|
||||||
|
...prev,
|
||||||
|
...nextSiteSettings,
|
||||||
|
}));
|
||||||
setHomeAnimationConfig((prev) => ({
|
setHomeAnimationConfig((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
htmlContent: normalizedHomeAnimationHtml,
|
htmlContent: normalizedHomeAnimationHtml,
|
||||||
|
|||||||
@ -144,6 +144,7 @@ const WORKFLOW_OPTION_LEVEL_LABELS = CHINESE_LEVEL_NUMERALS.map((item) => `${ite
|
|||||||
const PRESET_LIBRARY_LEVEL_LABELS = ["一级分类", "二级分类", "三级分类"];
|
const PRESET_LIBRARY_LEVEL_LABELS = ["一级分类", "二级分类", "三级分类"];
|
||||||
const WORKFLOW_STEP_DRAG_DATA_TYPE = "application/x-agent-workflow-step";
|
const WORKFLOW_STEP_DRAG_DATA_TYPE = "application/x-agent-workflow-step";
|
||||||
const WORKFLOW_SETTINGS_DRAFT_STORAGE_KEY = "agent-workflow-settings-draft:v1";
|
const WORKFLOW_SETTINGS_DRAFT_STORAGE_KEY = "agent-workflow-settings-draft:v1";
|
||||||
|
const WORKFLOW_SETTINGS_STALE_DRAFT_STORAGE_KEY = "agent-workflow-settings-stale-draft:v1";
|
||||||
const WORKFLOW_SETTINGS_DRAFT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
const WORKFLOW_SETTINGS_DRAFT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
type WorkflowStepDropPosition = "before" | "after" | "inside";
|
type WorkflowStepDropPosition = "before" | "after" | "inside";
|
||||||
@ -165,11 +166,74 @@ interface AgentWorkflowSettingsDraftState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface AgentWorkflowSettingsDraft {
|
interface AgentWorkflowSettingsDraft {
|
||||||
version: 1;
|
version: 1 | 2;
|
||||||
updatedAt: number;
|
updatedAt: number;
|
||||||
|
baseSignature?: string;
|
||||||
|
baseSignatures?: Partial<Record<keyof AgentWorkflowSettingsPayload, string>>;
|
||||||
state: AgentWorkflowSettingsDraftState;
|
state: AgentWorkflowSettingsDraftState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stableStringifyWorkflowDraftValue(value: unknown): string {
|
||||||
|
if (value === null || typeof value !== "object") {
|
||||||
|
return JSON.stringify(value) ?? "null";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return `[${value.map((item) => stableStringifyWorkflowDraftValue(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)}:${stableStringifyWorkflowDraftValue(item)}`)
|
||||||
|
.join(",")}}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAgentWorkflowValueSignature(value: unknown) {
|
||||||
|
const serialized = stableStringifyWorkflowDraftValue(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 createAgentWorkflowSettingsStateSignature(state: AgentWorkflowSettingsDraftState) {
|
||||||
|
return createAgentWorkflowValueSignature(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAgentWorkflowSettingsSectionSignatures(state: AgentWorkflowSettingsDraftState) {
|
||||||
|
return {
|
||||||
|
agentPresetLibraryConfig: createAgentWorkflowValueSignature(state.presetLibraryConfig),
|
||||||
|
agentWorkflowUiConfig: createAgentWorkflowValueSignature({
|
||||||
|
...state.config,
|
||||||
|
steps: normalizeAgentWorkflowStepOrdering(state.config.steps),
|
||||||
|
}),
|
||||||
|
agentWorkflowMaterialApiConfig: createAgentWorkflowValueSignature(state.materialApiConfig),
|
||||||
|
agentWorkflowGoodsInfoApiConfig: createAgentWorkflowValueSignature(state.goodsInfoApiConfig),
|
||||||
|
agentWorkflowBrandModelConfig: createAgentWorkflowValueSignature(state.brandModelConfig),
|
||||||
|
agentWorkflowShowcaseConfig: createAgentWorkflowValueSignature(state.showcaseConfig),
|
||||||
|
agentPsdTextColorConfig: createAgentWorkflowValueSignature(state.psdTextColorConfig),
|
||||||
|
agentPsdTemplateSecondaryCategoryConfig: createAgentWorkflowValueSignature(
|
||||||
|
state.psdTemplateSecondaryCategoryConfig
|
||||||
|
),
|
||||||
|
} satisfies Record<keyof AgentWorkflowSettingsPayload, string>;
|
||||||
|
}
|
||||||
|
|
||||||
function getWorkflowLevelLabel(depth: number, kind: "模块" | "卡片") {
|
function getWorkflowLevelLabel(depth: number, kind: "模块" | "卡片") {
|
||||||
const labels =
|
const labels =
|
||||||
kind === "模块" ? WORKFLOW_LEVEL_LABELS : WORKFLOW_OPTION_LEVEL_LABELS;
|
kind === "模块" ? WORKFLOW_LEVEL_LABELS : WORKFLOW_OPTION_LEVEL_LABELS;
|
||||||
@ -634,12 +698,19 @@ function readAgentWorkflowSettingsDraft(): AgentWorkflowSettingsDraft | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const parsed = JSON.parse(rawDraft) as Partial<AgentWorkflowSettingsDraft>;
|
const parsed = JSON.parse(rawDraft) as Partial<AgentWorkflowSettingsDraft>;
|
||||||
|
const isLegacyDraft = parsed?.version === 1;
|
||||||
|
const isVersionedDraft =
|
||||||
|
parsed?.version === 2 &&
|
||||||
|
typeof parsed.baseSignature === "string" &&
|
||||||
|
parsed.baseSignature.length > 0;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
parsed?.version !== 1 ||
|
(!isLegacyDraft && !isVersionedDraft) ||
|
||||||
typeof parsed.updatedAt !== "number" ||
|
typeof parsed.updatedAt !== "number" ||
|
||||||
!parsed.state ||
|
!parsed.state ||
|
||||||
typeof parsed.state !== "object"
|
typeof parsed.state !== "object"
|
||||||
) {
|
) {
|
||||||
|
removeAgentWorkflowSettingsDraft();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -661,6 +732,46 @@ function writeAgentWorkflowSettingsDraft(draft: AgentWorkflowSettingsDraft) {
|
|||||||
window.localStorage.setItem(WORKFLOW_SETTINGS_DRAFT_STORAGE_KEY, JSON.stringify(draft));
|
window.localStorage.setItem(WORKFLOW_SETTINGS_DRAFT_STORAGE_KEY, JSON.stringify(draft));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function preserveAgentWorkflowSettingsDraftForRecovery(draft: AgentWorkflowSettingsDraft) {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.localStorage.setItem(
|
||||||
|
WORKFLOW_SETTINGS_STALE_DRAFT_STORAGE_KEY,
|
||||||
|
JSON.stringify({
|
||||||
|
...draft,
|
||||||
|
preservedAt: Date.now(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPreservedAgentWorkflowSettingsDraftForRecovery(): AgentWorkflowSettingsDraft | null {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const rawDraft = window.localStorage.getItem(WORKFLOW_SETTINGS_STALE_DRAFT_STORAGE_KEY);
|
||||||
|
if (!rawDraft) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = JSON.parse(rawDraft) as Partial<AgentWorkflowSettingsDraft>;
|
||||||
|
if (
|
||||||
|
(parsed?.version !== 1 && parsed?.version !== 2) ||
|
||||||
|
typeof parsed.updatedAt !== "number" ||
|
||||||
|
!parsed.state ||
|
||||||
|
typeof parsed.state !== "object"
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed as AgentWorkflowSettingsDraft;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function removeAgentWorkflowSettingsDraft() {
|
function removeAgentWorkflowSettingsDraft() {
|
||||||
if (typeof window === "undefined") {
|
if (typeof window === "undefined") {
|
||||||
return;
|
return;
|
||||||
@ -1115,20 +1226,99 @@ interface AgentWorkflowSettingsClientProps {
|
|||||||
backLabel?: string;
|
backLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AgentWorkflowSettingsPayload {
|
||||||
|
agentPresetLibraryConfig?: AgentPresetLibraryConfig;
|
||||||
|
agentWorkflowUiConfig?: AgentWorkflowUiConfig;
|
||||||
|
agentWorkflowMaterialApiConfig?: AgentWorkflowMaterialApiConfig;
|
||||||
|
agentWorkflowGoodsInfoApiConfig?: AgentWorkflowGoodsInfoApiConfig;
|
||||||
|
agentWorkflowBrandModelConfig?: AgentWorkflowBrandModelConfig;
|
||||||
|
agentWorkflowShowcaseConfig?: AgentWorkflowShowcaseConfig;
|
||||||
|
agentPsdTextColorConfig?: AgentPsdTextColorConfig;
|
||||||
|
agentPsdTemplateSecondaryCategoryConfig?: AgentPsdTemplateSecondaryCategoryConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAgentWorkflowSettingsStateFromPayload(
|
||||||
|
payload?: AgentWorkflowSettingsPayload | null
|
||||||
|
): AgentWorkflowSettingsDraftState {
|
||||||
|
return {
|
||||||
|
presetLibraryConfig: normalizeAgentPresetLibraryConfig(payload?.agentPresetLibraryConfig),
|
||||||
|
config: normalizeConfigOrdering(normalizeAgentWorkflowUiConfig(payload?.agentWorkflowUiConfig)),
|
||||||
|
materialApiConfig: normalizeAgentWorkflowMaterialApiConfig(
|
||||||
|
payload?.agentWorkflowMaterialApiConfig
|
||||||
|
),
|
||||||
|
goodsInfoApiConfig: normalizeAgentWorkflowGoodsInfoApiConfig(
|
||||||
|
payload?.agentWorkflowGoodsInfoApiConfig
|
||||||
|
),
|
||||||
|
showcaseConfig: normalizeAgentWorkflowShowcaseConfig(payload?.agentWorkflowShowcaseConfig),
|
||||||
|
psdTextColorConfig: normalizeAgentPsdTextColorConfig(payload?.agentPsdTextColorConfig),
|
||||||
|
psdTemplateSecondaryCategoryConfig: normalizeAgentPsdTemplateSecondaryCategoryConfig(
|
||||||
|
payload?.agentPsdTemplateSecondaryCategoryConfig
|
||||||
|
),
|
||||||
|
brandModelConfig: normalizeAgentWorkflowBrandModelConfig(
|
||||||
|
payload?.agentWorkflowBrandModelConfig
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type AgentWorkflowSettingsField = keyof AgentWorkflowSettingsPayload;
|
||||||
|
|
||||||
|
const AGENT_WORKFLOW_SETTINGS_FIELD_LABELS: Record<AgentWorkflowSettingsField, string> = {
|
||||||
|
agentPresetLibraryConfig: "预设库",
|
||||||
|
agentWorkflowUiConfig: "工作流步骤",
|
||||||
|
agentWorkflowMaterialApiConfig: "素材搜索接口",
|
||||||
|
agentWorkflowGoodsInfoApiConfig: "商品信息接口",
|
||||||
|
agentWorkflowBrandModelConfig: "品牌模特",
|
||||||
|
agentWorkflowShowcaseConfig: "页面展示",
|
||||||
|
agentPsdTextColorConfig: "PSD 文字颜色",
|
||||||
|
agentPsdTemplateSecondaryCategoryConfig: "PSD 二级分类",
|
||||||
|
};
|
||||||
|
|
||||||
|
function getAgentWorkflowSettingsPayloadEntries(state: AgentWorkflowSettingsDraftState) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
field: "agentPresetLibraryConfig",
|
||||||
|
value: state.presetLibraryConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "agentWorkflowUiConfig",
|
||||||
|
value: state.config,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "agentWorkflowMaterialApiConfig",
|
||||||
|
value: state.materialApiConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "agentWorkflowGoodsInfoApiConfig",
|
||||||
|
value: state.goodsInfoApiConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "agentWorkflowBrandModelConfig",
|
||||||
|
value: state.brandModelConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "agentWorkflowShowcaseConfig",
|
||||||
|
value: state.showcaseConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "agentPsdTextColorConfig",
|
||||||
|
value: state.psdTextColorConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "agentPsdTemplateSecondaryCategoryConfig",
|
||||||
|
value: state.psdTemplateSecondaryCategoryConfig,
|
||||||
|
},
|
||||||
|
] satisfies Array<{ field: AgentWorkflowSettingsField; value: unknown }>;
|
||||||
|
}
|
||||||
|
|
||||||
export function AgentWorkflowSettingsClient({
|
export function AgentWorkflowSettingsClient({
|
||||||
backHref = "/settings",
|
backHref = "/settings",
|
||||||
backLabel = "返回设置",
|
backLabel = "返回设置",
|
||||||
}: AgentWorkflowSettingsClientProps = {}) {
|
}: AgentWorkflowSettingsClientProps = {}) {
|
||||||
const { data, isLoading, mutate } = useSWR<{
|
const { data, isLoading, mutate } = useSWR<AgentWorkflowSettingsPayload>(
|
||||||
agentPresetLibraryConfig?: AgentPresetLibraryConfig;
|
"/api/site-settings",
|
||||||
agentWorkflowUiConfig?: AgentWorkflowUiConfig;
|
fetcher,
|
||||||
agentWorkflowMaterialApiConfig?: AgentWorkflowMaterialApiConfig;
|
{ refreshInterval: 60_000 }
|
||||||
agentWorkflowGoodsInfoApiConfig?: AgentWorkflowGoodsInfoApiConfig;
|
);
|
||||||
agentWorkflowBrandModelConfig?: AgentWorkflowBrandModelConfig;
|
|
||||||
agentWorkflowShowcaseConfig?: AgentWorkflowShowcaseConfig;
|
|
||||||
agentPsdTextColorConfig?: AgentPsdTextColorConfig;
|
|
||||||
agentPsdTemplateSecondaryCategoryConfig?: AgentPsdTemplateSecondaryCategoryConfig;
|
|
||||||
}>("/api/site-settings", fetcher);
|
|
||||||
const [presetLibraryConfig, setPresetLibraryConfig] = useState<AgentPresetLibraryConfig>(
|
const [presetLibraryConfig, setPresetLibraryConfig] = useState<AgentPresetLibraryConfig>(
|
||||||
defaultAgentPresetLibraryConfig
|
defaultAgentPresetLibraryConfig
|
||||||
);
|
);
|
||||||
@ -1178,11 +1368,17 @@ export function AgentWorkflowSettingsClient({
|
|||||||
);
|
);
|
||||||
const [autoDraftUpdatedAt, setAutoDraftUpdatedAt] = useState<number | null>(null);
|
const [autoDraftUpdatedAt, setAutoDraftUpdatedAt] = useState<number | null>(null);
|
||||||
const [autoDraftStatus, setAutoDraftStatus] = useState<"idle" | "restored" | "saved">("idle");
|
const [autoDraftStatus, setAutoDraftStatus] = useState<"idle" | "restored" | "saved">("idle");
|
||||||
|
const [recoverableDraftUpdatedAt, setRecoverableDraftUpdatedAt] = useState<number | null>(null);
|
||||||
const fileInputRefs = useRef<Record<string, HTMLInputElement | null>>({});
|
const fileInputRefs = useRef<Record<string, HTMLInputElement | null>>({});
|
||||||
const brandImportInputRef = useRef<HTMLInputElement | null>(null);
|
const brandImportInputRef = useRef<HTMLInputElement | null>(null);
|
||||||
const hasAppliedInitialSettingsRef = useRef(false);
|
const hasAppliedInitialSettingsRef = useRef(false);
|
||||||
const hasHydratedSettingsRef = useRef(false);
|
const hasHydratedSettingsRef = useRef(false);
|
||||||
const isApplyingSettingsRef = useRef(false);
|
const isApplyingSettingsRef = useRef(false);
|
||||||
|
const serverDraftBaseSignatureRef = useRef<string | null>(null);
|
||||||
|
const serverDraftBaseSignaturesRef = useRef<
|
||||||
|
Record<keyof AgentWorkflowSettingsPayload, string> | null
|
||||||
|
>(null);
|
||||||
|
const hasWarnedRemoteWorkflowUpdateRef = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
@ -1192,24 +1388,22 @@ export function AgentWorkflowSettingsClient({
|
|||||||
hasHydratedSettingsRef.current = false;
|
hasHydratedSettingsRef.current = false;
|
||||||
isApplyingSettingsRef.current = true;
|
isApplyingSettingsRef.current = true;
|
||||||
|
|
||||||
const serverState: AgentWorkflowSettingsDraftState = {
|
const serverState = createAgentWorkflowSettingsStateFromPayload(data);
|
||||||
presetLibraryConfig: normalizeAgentPresetLibraryConfig(data.agentPresetLibraryConfig),
|
|
||||||
config: normalizeConfigOrdering(normalizeAgentWorkflowUiConfig(data.agentWorkflowUiConfig)),
|
const serverSignature = createAgentWorkflowSettingsStateSignature(serverState);
|
||||||
materialApiConfig: normalizeAgentWorkflowMaterialApiConfig(data.agentWorkflowMaterialApiConfig),
|
const serverSignatures = createAgentWorkflowSettingsSectionSignatures(serverState);
|
||||||
goodsInfoApiConfig: normalizeAgentWorkflowGoodsInfoApiConfig(
|
serverDraftBaseSignatureRef.current = serverSignature;
|
||||||
data.agentWorkflowGoodsInfoApiConfig
|
serverDraftBaseSignaturesRef.current = serverSignatures;
|
||||||
),
|
hasWarnedRemoteWorkflowUpdateRef.current = false;
|
||||||
showcaseConfig: normalizeAgentWorkflowShowcaseConfig(data.agentWorkflowShowcaseConfig),
|
|
||||||
psdTextColorConfig: normalizeAgentPsdTextColorConfig(data.agentPsdTextColorConfig),
|
|
||||||
psdTemplateSecondaryCategoryConfig: normalizeAgentPsdTemplateSecondaryCategoryConfig(
|
|
||||||
data.agentPsdTemplateSecondaryCategoryConfig
|
|
||||||
),
|
|
||||||
brandModelConfig: normalizeAgentWorkflowBrandModelConfig(data.agentWorkflowBrandModelConfig),
|
|
||||||
};
|
|
||||||
|
|
||||||
const draft = readAgentWorkflowSettingsDraft();
|
const draft = readAgentWorkflowSettingsDraft();
|
||||||
const nextState: AgentWorkflowSettingsDraftState = draft
|
let restoredDraft: AgentWorkflowSettingsDraft | null = null;
|
||||||
? {
|
let nextState: AgentWorkflowSettingsDraftState = serverState;
|
||||||
|
|
||||||
|
if (draft) {
|
||||||
|
if (draft.version === 2 && draft.baseSignature === serverSignature) {
|
||||||
|
restoredDraft = draft;
|
||||||
|
nextState = {
|
||||||
presetLibraryConfig: normalizeAgentPresetLibraryConfig(draft.state.presetLibraryConfig),
|
presetLibraryConfig: normalizeAgentPresetLibraryConfig(draft.state.presetLibraryConfig),
|
||||||
config: normalizeConfigOrdering(normalizeAgentWorkflowUiConfig(draft.state.config)),
|
config: normalizeConfigOrdering(normalizeAgentWorkflowUiConfig(draft.state.config)),
|
||||||
materialApiConfig: normalizeAgentWorkflowMaterialApiConfig(
|
materialApiConfig: normalizeAgentWorkflowMaterialApiConfig(
|
||||||
@ -1224,8 +1418,18 @@ export function AgentWorkflowSettingsClient({
|
|||||||
draft.state.psdTemplateSecondaryCategoryConfig
|
draft.state.psdTemplateSecondaryCategoryConfig
|
||||||
),
|
),
|
||||||
brandModelConfig: normalizeAgentWorkflowBrandModelConfig(draft.state.brandModelConfig),
|
brandModelConfig: normalizeAgentWorkflowBrandModelConfig(draft.state.brandModelConfig),
|
||||||
}
|
};
|
||||||
: serverState;
|
} else {
|
||||||
|
preserveAgentWorkflowSettingsDraftForRecovery(draft);
|
||||||
|
setRecoverableDraftUpdatedAt(draft.updatedAt);
|
||||||
|
removeAgentWorkflowSettingsDraft();
|
||||||
|
toast.warning(
|
||||||
|
draft.version === 1
|
||||||
|
? "检测到旧版本地草稿,已转存为恢复副本,不会自动覆盖服务器配置"
|
||||||
|
: "服务器配置已有更新,已转存旧草稿为恢复副本,避免覆盖其他管理员保存的数据"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setPresetLibraryConfig(nextState.presetLibraryConfig);
|
setPresetLibraryConfig(nextState.presetLibraryConfig);
|
||||||
setConfig(nextState.config);
|
setConfig(nextState.config);
|
||||||
@ -1241,10 +1445,18 @@ export function AgentWorkflowSettingsClient({
|
|||||||
setExpandedSteps(new Set([nextState.config.steps[0].key]));
|
setExpandedSteps(new Set([nextState.config.steps[0].key]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (draft) {
|
if (restoredDraft) {
|
||||||
setAutoDraftUpdatedAt(draft.updatedAt);
|
setAutoDraftUpdatedAt(restoredDraft.updatedAt);
|
||||||
setAutoDraftStatus("restored");
|
setAutoDraftStatus("restored");
|
||||||
toast.success("已恢复本地自动保存草稿,确认无误后点击保存配置写入服务器");
|
toast.success("已恢复本地自动保存草稿,确认无误后点击保存配置写入服务器");
|
||||||
|
} else {
|
||||||
|
setAutoDraftUpdatedAt(null);
|
||||||
|
setAutoDraftStatus("idle");
|
||||||
|
}
|
||||||
|
|
||||||
|
const preservedDraft = readPreservedAgentWorkflowSettingsDraftForRecovery();
|
||||||
|
if (preservedDraft) {
|
||||||
|
setRecoverableDraftUpdatedAt(preservedDraft.updatedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hydrationTimer = window.setTimeout(() => {
|
const hydrationTimer = window.setTimeout(() => {
|
||||||
@ -1255,6 +1467,28 @@ export function AgentWorkflowSettingsClient({
|
|||||||
return () => window.clearTimeout(hydrationTimer);
|
return () => window.clearTimeout(hydrationTimer);
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data || !hasHydratedSettingsRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestServerState = createAgentWorkflowSettingsStateFromPayload(data);
|
||||||
|
const latestServerSignature =
|
||||||
|
createAgentWorkflowSettingsStateSignature(latestServerState);
|
||||||
|
const baseSignature = serverDraftBaseSignatureRef.current;
|
||||||
|
|
||||||
|
if (
|
||||||
|
baseSignature &&
|
||||||
|
latestServerSignature !== baseSignature &&
|
||||||
|
!hasWarnedRemoteWorkflowUpdateRef.current
|
||||||
|
) {
|
||||||
|
hasWarnedRemoteWorkflowUpdateRef.current = true;
|
||||||
|
toast.warning(
|
||||||
|
"服务器工作流配置已有新版本。当前页面不会自动覆盖你的编辑,保存时只会提交你改过的部分。"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
const brandStoreEntries = useMemo(
|
const brandStoreEntries = useMemo(
|
||||||
() =>
|
() =>
|
||||||
config.inputOptions?.brandStoreEntries ||
|
config.inputOptions?.brandStoreEntries ||
|
||||||
@ -1357,8 +1591,14 @@ export function AgentWorkflowSettingsClient({
|
|||||||
const updatedAt = Date.now();
|
const updatedAt = Date.now();
|
||||||
try {
|
try {
|
||||||
writeAgentWorkflowSettingsDraft({
|
writeAgentWorkflowSettingsDraft({
|
||||||
version: 1,
|
version: 2,
|
||||||
updatedAt,
|
updatedAt,
|
||||||
|
baseSignature:
|
||||||
|
serverDraftBaseSignatureRef.current ||
|
||||||
|
createAgentWorkflowSettingsStateSignature(autoDraftState),
|
||||||
|
baseSignatures:
|
||||||
|
serverDraftBaseSignaturesRef.current ||
|
||||||
|
createAgentWorkflowSettingsSectionSignatures(autoDraftState),
|
||||||
state: autoDraftState,
|
state: autoDraftState,
|
||||||
});
|
});
|
||||||
setAutoDraftUpdatedAt(updatedAt);
|
setAutoDraftUpdatedAt(updatedAt);
|
||||||
@ -2671,22 +2911,108 @@ export function AgentWorkflowSettingsClient({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRestorePreservedDraft = () => {
|
||||||
|
const draft = readPreservedAgentWorkflowSettingsDraftForRecovery();
|
||||||
|
if (!draft) {
|
||||||
|
setRecoverableDraftUpdatedAt(null);
|
||||||
|
toast.error("没有找到可恢复的本地旧草稿");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextState: AgentWorkflowSettingsDraftState = {
|
||||||
|
presetLibraryConfig: normalizeAgentPresetLibraryConfig(draft.state.presetLibraryConfig),
|
||||||
|
config: normalizeConfigOrdering(normalizeAgentWorkflowUiConfig(draft.state.config)),
|
||||||
|
materialApiConfig: normalizeAgentWorkflowMaterialApiConfig(
|
||||||
|
draft.state.materialApiConfig
|
||||||
|
),
|
||||||
|
goodsInfoApiConfig: normalizeAgentWorkflowGoodsInfoApiConfig(
|
||||||
|
draft.state.goodsInfoApiConfig
|
||||||
|
),
|
||||||
|
showcaseConfig: normalizeAgentWorkflowShowcaseConfig(draft.state.showcaseConfig),
|
||||||
|
psdTextColorConfig: normalizeAgentPsdTextColorConfig(draft.state.psdTextColorConfig),
|
||||||
|
psdTemplateSecondaryCategoryConfig: normalizeAgentPsdTemplateSecondaryCategoryConfig(
|
||||||
|
draft.state.psdTemplateSecondaryCategoryConfig
|
||||||
|
),
|
||||||
|
brandModelConfig: normalizeAgentWorkflowBrandModelConfig(draft.state.brandModelConfig),
|
||||||
|
};
|
||||||
|
|
||||||
|
setPresetLibraryConfig(nextState.presetLibraryConfig);
|
||||||
|
setConfig(nextState.config);
|
||||||
|
setMaterialApiConfig(nextState.materialApiConfig);
|
||||||
|
setGoodsInfoApiConfig(nextState.goodsInfoApiConfig);
|
||||||
|
setShowcaseConfig(nextState.showcaseConfig);
|
||||||
|
setPsdTextColorConfig(nextState.psdTextColorConfig);
|
||||||
|
setPsdTemplateSecondaryCategoryConfig(nextState.psdTemplateSecondaryCategoryConfig);
|
||||||
|
setBrandModelConfig(nextState.brandModelConfig);
|
||||||
|
if (nextState.config.steps[0]) {
|
||||||
|
setExpandedSteps(new Set([nextState.config.steps[0].key]));
|
||||||
|
}
|
||||||
|
setAutoDraftUpdatedAt(Date.now());
|
||||||
|
setAutoDraftStatus("restored");
|
||||||
|
toast.success("已把旧草稿恢复到当前页面,确认无误后点击保存配置写入服务器");
|
||||||
|
};
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
try {
|
try {
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
|
const latestSettingsResponse = await fetch("/api/site-settings", {
|
||||||
|
cache: "no-store",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!latestSettingsResponse.ok) {
|
||||||
|
throw new Error("保存前校验服务器配置失败,请刷新页面后重试");
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestSettings =
|
||||||
|
(await latestSettingsResponse.json()) as AgentWorkflowSettingsPayload;
|
||||||
|
const latestServerState = createAgentWorkflowSettingsStateFromPayload(latestSettings);
|
||||||
|
const latestServerSignatures =
|
||||||
|
createAgentWorkflowSettingsSectionSignatures(latestServerState);
|
||||||
|
const baseSignatures =
|
||||||
|
serverDraftBaseSignaturesRef.current ||
|
||||||
|
createAgentWorkflowSettingsSectionSignatures(latestServerState);
|
||||||
|
const currentSignatures =
|
||||||
|
createAgentWorkflowSettingsSectionSignatures(autoDraftState);
|
||||||
|
const changedEntries = getAgentWorkflowSettingsPayloadEntries(autoDraftState).filter(
|
||||||
|
({ field }) => currentSignatures[field] !== baseSignatures[field]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (changedEntries.length === 0) {
|
||||||
|
removeAgentWorkflowSettingsDraft();
|
||||||
|
setAutoDraftUpdatedAt(null);
|
||||||
|
setAutoDraftStatus("idle");
|
||||||
|
toast.success("没有检测到需要保存的修改");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conflictedFields = changedEntries
|
||||||
|
.filter(({ field }) => latestServerSignatures[field] !== baseSignatures[field])
|
||||||
|
.map(({ field }) => AGENT_WORKFLOW_SETTINGS_FIELD_LABELS[field]);
|
||||||
|
|
||||||
|
if (conflictedFields.length > 0) {
|
||||||
|
toast.error(
|
||||||
|
`以下配置已被其他管理员更新,本次保存已阻止:${conflictedFields.join("、")}。请刷新页面确认后再保存。`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const changedPayload: Partial<AgentWorkflowSettingsPayload> & {
|
||||||
|
agentWorkflowSettingsBaseSignatures: Partial<
|
||||||
|
Record<AgentWorkflowSettingsField, string>
|
||||||
|
>;
|
||||||
|
} = {
|
||||||
|
agentWorkflowSettingsBaseSignatures: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const { field, value } of changedEntries) {
|
||||||
|
changedPayload[field] = value as never;
|
||||||
|
changedPayload.agentWorkflowSettingsBaseSignatures[field] = baseSignatures[field];
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch("/api/site-settings", {
|
const response = await fetch("/api/site-settings", {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(changedPayload),
|
||||||
agentPresetLibraryConfig: presetLibraryConfig,
|
|
||||||
agentWorkflowUiConfig: config,
|
|
||||||
agentWorkflowMaterialApiConfig: materialApiConfig,
|
|
||||||
agentWorkflowGoodsInfoApiConfig: goodsInfoApiConfig,
|
|
||||||
agentWorkflowBrandModelConfig: brandModelConfig,
|
|
||||||
agentWorkflowShowcaseConfig: showcaseConfig,
|
|
||||||
agentPsdTextColorConfig: psdTextColorConfig,
|
|
||||||
agentPsdTemplateSecondaryCategoryConfig: psdTemplateSecondaryCategoryConfig,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@ -2724,17 +3050,32 @@ export function AgentWorkflowSettingsClient({
|
|||||||
);
|
);
|
||||||
const expectedStepCount = countAgentWorkflowSteps(config.steps || []);
|
const expectedStepCount = countAgentWorkflowSteps(config.steps || []);
|
||||||
const savedStepCount = countAgentWorkflowSteps(savedWorkflowConfig.steps || []);
|
const savedStepCount = countAgentWorkflowSteps(savedWorkflowConfig.steps || []);
|
||||||
if (savedStepCount !== expectedStepCount) {
|
const savedWorkflowConfigWasChanged = changedEntries.some(
|
||||||
|
({ field }) => field === "agentWorkflowUiConfig"
|
||||||
|
);
|
||||||
|
if (savedWorkflowConfigWasChanged && savedStepCount !== expectedStepCount) {
|
||||||
toast.error(
|
toast.error(
|
||||||
`服务端回读到的模块数量不一致(本地 ${expectedStepCount} / 服务端 ${savedStepCount}),已保留本地自动草稿,请先不要刷新页面。`
|
`服务端回读到的模块数量不一致(本地 ${expectedStepCount} / 服务端 ${savedStepCount}),已保留本地自动草稿,请先不要刷新页面。`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updatedServerState = createAgentWorkflowSettingsStateFromPayload(
|
||||||
|
updatedSettings || latestSettings
|
||||||
|
);
|
||||||
|
serverDraftBaseSignatureRef.current =
|
||||||
|
createAgentWorkflowSettingsStateSignature(updatedServerState);
|
||||||
|
serverDraftBaseSignaturesRef.current =
|
||||||
|
createAgentWorkflowSettingsSectionSignatures(updatedServerState);
|
||||||
|
hasWarnedRemoteWorkflowUpdateRef.current = false;
|
||||||
removeAgentWorkflowSettingsDraft();
|
removeAgentWorkflowSettingsDraft();
|
||||||
setAutoDraftUpdatedAt(null);
|
setAutoDraftUpdatedAt(null);
|
||||||
setAutoDraftStatus("idle");
|
setAutoDraftStatus("idle");
|
||||||
toast.success("风格化工作流配置已保存");
|
toast.success(
|
||||||
|
`已保存 ${changedEntries
|
||||||
|
.map(({ field }) => AGENT_WORKFLOW_SETTINGS_FIELD_LABELS[field])
|
||||||
|
.join("、")}`
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(error instanceof Error ? error.message : "保存失败");
|
toast.error(error instanceof Error ? error.message : "保存失败");
|
||||||
} finally {
|
} finally {
|
||||||
@ -3916,6 +4257,16 @@ export function AgentWorkflowSettingsClient({
|
|||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
{recoverableDraftUpdatedAt ? (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleRestorePreservedDraft}
|
||||||
|
>
|
||||||
|
恢复旧草稿
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
<Button asChild variant="outline" size="sm">
|
<Button asChild variant="outline" size="sm">
|
||||||
<Link href="/settings/agent-workflow/psd-templates">
|
<Link href="/settings/agent-workflow/psd-templates">
|
||||||
<Layers className="mr-2 h-4 w-4" />
|
<Layers className="mr-2 h-4 w-4" />
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
import { siteSettings, systemSettings } from "@/lib/db/schema";
|
import { siteSettings, systemSettingHistory, systemSettings } from "@/lib/db/schema";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq, inArray } from "drizzle-orm";
|
||||||
import { auth } from "@/app/(auth)/auth";
|
import { auth } from "@/app/(auth)/auth";
|
||||||
import { isAgentWorkflowAdminType, isSiteAdminType } from "@/lib/middleware/admin";
|
import { isAgentWorkflowAdminType, isSiteAdminType } from "@/lib/middleware/admin";
|
||||||
import {
|
import {
|
||||||
@ -34,6 +34,7 @@ import {
|
|||||||
AGENT_WORKFLOW_UI_CONFIG_KEY,
|
AGENT_WORKFLOW_UI_CONFIG_KEY,
|
||||||
AGENT_WORKFLOW_PROMPT_FRAGMENT_MAX_LENGTH,
|
AGENT_WORKFLOW_PROMPT_FRAGMENT_MAX_LENGTH,
|
||||||
defaultAgentWorkflowUiConfig,
|
defaultAgentWorkflowUiConfig,
|
||||||
|
normalizeAgentWorkflowStepOrdering,
|
||||||
normalizeAgentWorkflowUiConfig,
|
normalizeAgentWorkflowUiConfig,
|
||||||
} from "@/lib/agents/workflow-ui-config";
|
} from "@/lib/agents/workflow-ui-config";
|
||||||
import {
|
import {
|
||||||
@ -111,6 +112,112 @@ const AGENT_CATEGORY_TREE_CONFIG_KEY = "agent_category_tree_config";
|
|||||||
const CANVAS_AGENT_MODE_ENABLED_KEY = "canvas_agent_mode_enabled";
|
const CANVAS_AGENT_MODE_ENABLED_KEY = "canvas_agent_mode_enabled";
|
||||||
const VIDEO_CANVAS_ENABLED_KEY = "video_canvas_enabled";
|
const VIDEO_CANVAS_ENABLED_KEY = "video_canvas_enabled";
|
||||||
const VIDEO_CANVAS_SIDEBAR_ENABLED_KEY = "video_canvas_sidebar_enabled";
|
const VIDEO_CANVAS_SIDEBAR_ENABLED_KEY = "video_canvas_sidebar_enabled";
|
||||||
|
const AGENT_WORKFLOW_SETTINGS_CONFLICT_KEYS = [
|
||||||
|
AGENT_PRESET_LIBRARY_CONFIG_KEY,
|
||||||
|
AGENT_WORKFLOW_UI_CONFIG_KEY,
|
||||||
|
AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY,
|
||||||
|
AGENT_WORKFLOW_GOODS_INFO_API_CONFIG_KEY,
|
||||||
|
AGENT_WORKFLOW_BRAND_MODEL_CONFIG_KEY,
|
||||||
|
AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY,
|
||||||
|
AGENT_PSD_TEXT_COLOR_CONFIG_KEY,
|
||||||
|
AGENT_PSD_TEMPLATE_SECONDARY_CATEGORY_CONFIG_KEY,
|
||||||
|
] as const;
|
||||||
|
const AGENT_WORKFLOW_SETTINGS_FIELD_KEY_MAP = {
|
||||||
|
agentPresetLibraryConfig: AGENT_PRESET_LIBRARY_CONFIG_KEY,
|
||||||
|
agentWorkflowUiConfig: AGENT_WORKFLOW_UI_CONFIG_KEY,
|
||||||
|
agentWorkflowMaterialApiConfig: AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY,
|
||||||
|
agentWorkflowGoodsInfoApiConfig: AGENT_WORKFLOW_GOODS_INFO_API_CONFIG_KEY,
|
||||||
|
agentWorkflowBrandModelConfig: AGENT_WORKFLOW_BRAND_MODEL_CONFIG_KEY,
|
||||||
|
agentWorkflowShowcaseConfig: AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY,
|
||||||
|
agentPsdTextColorConfig: AGENT_PSD_TEXT_COLOR_CONFIG_KEY,
|
||||||
|
agentPsdTemplateSecondaryCategoryConfig:
|
||||||
|
AGENT_PSD_TEMPLATE_SECONDARY_CATEGORY_CONFIG_KEY,
|
||||||
|
} as const;
|
||||||
|
type AgentWorkflowSettingsField = keyof typeof AGENT_WORKFLOW_SETTINGS_FIELD_KEY_MAP;
|
||||||
|
|
||||||
|
function stableStringifySettingsValue(value: unknown): string {
|
||||||
|
if (value === null || typeof value !== "object") {
|
||||||
|
return JSON.stringify(value) ?? "null";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return `[${value.map((item) => stableStringifySettingsValue(item)).join(",")}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries = Object.entries(value as Record<string, unknown>)
|
||||||
|
.filter(([, item]) => item !== undefined)
|
||||||
|
.sort(([left], [right]) => left.localeCompare(right));
|
||||||
|
|
||||||
|
return `{${entries
|
||||||
|
.map(([key, item]) => `${JSON.stringify(key)}:${stableStringifySettingsValue(item)}`)
|
||||||
|
.join(",")}}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSettingsValueSignature(value: unknown): string {
|
||||||
|
const serialized = stableStringifySettingsValue(value);
|
||||||
|
let hashA = 0xdeadbeef ^ serialized.length;
|
||||||
|
let hashB = 0x41c6ce57 ^ serialized.length;
|
||||||
|
|
||||||
|
for (let index = 0; index < serialized.length; index += 1) {
|
||||||
|
const code = serialized.charCodeAt(index);
|
||||||
|
hashA = Math.imul(hashA ^ code, 2654435761);
|
||||||
|
hashB = Math.imul(hashB ^ code, 1597334677);
|
||||||
|
}
|
||||||
|
|
||||||
|
hashA =
|
||||||
|
Math.imul(hashA ^ (hashA >>> 16), 2246822507) ^
|
||||||
|
Math.imul(hashB ^ (hashB >>> 13), 3266489909);
|
||||||
|
hashB =
|
||||||
|
Math.imul(hashB ^ (hashB >>> 16), 2246822507) ^
|
||||||
|
Math.imul(hashA ^ (hashA >>> 13), 3266489909);
|
||||||
|
|
||||||
|
return `${serialized.length}:${(hashB >>> 0).toString(16)}${(hashA >>> 0).toString(16)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAgentWorkflowSettingsStateFromValues(valueByKey: Map<string, unknown>) {
|
||||||
|
const workflowUiConfig = normalizeAgentWorkflowUiConfig(
|
||||||
|
valueByKey.get(AGENT_WORKFLOW_UI_CONFIG_KEY)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
agentPresetLibraryConfig: normalizeAgentPresetLibraryConfig(
|
||||||
|
valueByKey.get(AGENT_PRESET_LIBRARY_CONFIG_KEY)
|
||||||
|
),
|
||||||
|
agentWorkflowUiConfig: {
|
||||||
|
...workflowUiConfig,
|
||||||
|
steps: normalizeAgentWorkflowStepOrdering(workflowUiConfig.steps),
|
||||||
|
},
|
||||||
|
agentWorkflowMaterialApiConfig: normalizeAgentWorkflowMaterialApiConfig(
|
||||||
|
valueByKey.get(AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY)
|
||||||
|
),
|
||||||
|
agentWorkflowGoodsInfoApiConfig: normalizeAgentWorkflowGoodsInfoApiConfig(
|
||||||
|
valueByKey.get(AGENT_WORKFLOW_GOODS_INFO_API_CONFIG_KEY)
|
||||||
|
),
|
||||||
|
agentWorkflowBrandModelConfig: normalizeAgentWorkflowBrandModelConfig(
|
||||||
|
valueByKey.get(AGENT_WORKFLOW_BRAND_MODEL_CONFIG_KEY)
|
||||||
|
),
|
||||||
|
agentWorkflowShowcaseConfig: normalizeAgentWorkflowShowcaseConfig(
|
||||||
|
valueByKey.get(AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY)
|
||||||
|
),
|
||||||
|
agentPsdTextColorConfig: normalizeAgentPsdTextColorConfig(
|
||||||
|
valueByKey.get(AGENT_PSD_TEXT_COLOR_CONFIG_KEY)
|
||||||
|
),
|
||||||
|
agentPsdTemplateSecondaryCategoryConfig: normalizeAgentPsdTemplateSecondaryCategoryConfig(
|
||||||
|
valueByKey.get(AGENT_PSD_TEMPLATE_SECONDARY_CATEGORY_CONFIG_KEY)
|
||||||
|
),
|
||||||
|
} satisfies Record<AgentWorkflowSettingsField, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAgentWorkflowSettingsSectionSignatures(valueByKey: Map<string, unknown>) {
|
||||||
|
const state = createAgentWorkflowSettingsStateFromValues(valueByKey);
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(state).map(([field, value]) => [field, createSettingsValueSignature(value)])
|
||||||
|
) as Record<AgentWorkflowSettingsField, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAgentWorkflowSettingsSignature(valueByKey: Map<string, unknown>): string {
|
||||||
|
return createSettingsValueSignature(createAgentWorkflowSettingsStateFromValues(valueByKey));
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeHomeVariant(value: unknown): "classic" | "lovart" {
|
function normalizeHomeVariant(value: unknown): "classic" | "lovart" {
|
||||||
return value === "lovart" ? "lovart" : "classic";
|
return value === "lovart" ? "lovart" : "classic";
|
||||||
@ -853,6 +960,10 @@ const updateSchema = z.object({
|
|||||||
text: 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(),
|
.optional(),
|
||||||
|
agentWorkflowSettingsBaseSignature: z.string().trim().min(1).max(128).optional(),
|
||||||
|
agentWorkflowSettingsBaseSignatures: z
|
||||||
|
.record(z.string().trim().min(1).max(128))
|
||||||
|
.optional(),
|
||||||
agentWorkflowUiConfig: z
|
agentWorkflowUiConfig: z
|
||||||
.object({
|
.object({
|
||||||
inputOptions: z
|
inputOptions: z
|
||||||
@ -1112,6 +1223,8 @@ export async function PUT(request: NextRequest) {
|
|||||||
if (!isAdmin) {
|
if (!isAdmin) {
|
||||||
const allowedKeys = new Set([
|
const allowedKeys = new Set([
|
||||||
"agentPresetLibraryConfig",
|
"agentPresetLibraryConfig",
|
||||||
|
"agentWorkflowSettingsBaseSignature",
|
||||||
|
"agentWorkflowSettingsBaseSignatures",
|
||||||
"agentWorkflowUiConfig",
|
"agentWorkflowUiConfig",
|
||||||
"agentWorkflowMaterialApiConfig",
|
"agentWorkflowMaterialApiConfig",
|
||||||
"agentWorkflowGoodsInfoApiConfig",
|
"agentWorkflowGoodsInfoApiConfig",
|
||||||
@ -1138,6 +1251,8 @@ export async function PUT(request: NextRequest) {
|
|||||||
agentPresetLibraryConfig,
|
agentPresetLibraryConfig,
|
||||||
agentCategoryTreeConfig,
|
agentCategoryTreeConfig,
|
||||||
agentSecondaryTypeConfig,
|
agentSecondaryTypeConfig,
|
||||||
|
agentWorkflowSettingsBaseSignature,
|
||||||
|
agentWorkflowSettingsBaseSignatures,
|
||||||
agentWorkflowUiConfig,
|
agentWorkflowUiConfig,
|
||||||
agentWorkflowMaterialApiConfig,
|
agentWorkflowMaterialApiConfig,
|
||||||
agentWorkflowGoodsInfoApiConfig,
|
agentWorkflowGoodsInfoApiConfig,
|
||||||
@ -1160,6 +1275,106 @@ export async function PUT(request: NextRequest) {
|
|||||||
...siteSettingsPayload
|
...siteSettingsPayload
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
|
const updatedAgentWorkflowFields = (
|
||||||
|
[
|
||||||
|
["agentPresetLibraryConfig", agentPresetLibraryConfig],
|
||||||
|
["agentWorkflowUiConfig", agentWorkflowUiConfig],
|
||||||
|
["agentWorkflowMaterialApiConfig", agentWorkflowMaterialApiConfig],
|
||||||
|
["agentWorkflowGoodsInfoApiConfig", agentWorkflowGoodsInfoApiConfig],
|
||||||
|
["agentWorkflowBrandModelConfig", agentWorkflowBrandModelConfig],
|
||||||
|
["agentWorkflowShowcaseConfig", agentWorkflowShowcaseConfig],
|
||||||
|
["agentPsdTextColorConfig", agentPsdTextColorConfig],
|
||||||
|
["agentPsdTemplateSecondaryCategoryConfig", agentPsdTemplateSecondaryCategoryConfig],
|
||||||
|
] as const
|
||||||
|
)
|
||||||
|
.filter(([, value]) => value !== undefined)
|
||||||
|
.map(([field]) => field);
|
||||||
|
|
||||||
|
const isUpdatingAgentWorkflowSettings = updatedAgentWorkflowFields.length > 0;
|
||||||
|
|
||||||
|
if (isUpdatingAgentWorkflowSettings) {
|
||||||
|
if (!agentWorkflowSettingsBaseSignatures && !agentWorkflowSettingsBaseSignature) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error:
|
||||||
|
"工作流配置已启用多人协作保护,请刷新页面后再保存,避免覆盖其他管理员的数据。",
|
||||||
|
},
|
||||||
|
{ status: 409 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentWorkflowRows = await db
|
||||||
|
.select({
|
||||||
|
key: systemSettings.key,
|
||||||
|
value: systemSettings.value,
|
||||||
|
})
|
||||||
|
.from(systemSettings)
|
||||||
|
.where(inArray(systemSettings.key, [...AGENT_WORKFLOW_SETTINGS_CONFLICT_KEYS]));
|
||||||
|
const currentWorkflowValues = new Map<string, unknown>(
|
||||||
|
currentWorkflowRows.map((row) => [row.key, row.value])
|
||||||
|
);
|
||||||
|
const currentSectionSignatures =
|
||||||
|
createAgentWorkflowSettingsSectionSignatures(currentWorkflowValues);
|
||||||
|
const currentWorkflowSignature =
|
||||||
|
agentWorkflowSettingsBaseSignature && !agentWorkflowSettingsBaseSignatures
|
||||||
|
? createAgentWorkflowSettingsSignature(currentWorkflowValues)
|
||||||
|
: null;
|
||||||
|
const hasWorkflowConflict = agentWorkflowSettingsBaseSignatures
|
||||||
|
? updatedAgentWorkflowFields.some(
|
||||||
|
(field) =>
|
||||||
|
currentSectionSignatures[field] !== agentWorkflowSettingsBaseSignatures[field]
|
||||||
|
)
|
||||||
|
: currentWorkflowSignature !== agentWorkflowSettingsBaseSignature;
|
||||||
|
|
||||||
|
if (hasWorkflowConflict) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error:
|
||||||
|
"服务器上的同一块工作流配置已被其他管理员更新,本次保存已阻止。请刷新页面确认最新配置后再保存。",
|
||||||
|
},
|
||||||
|
{ status: 409 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const persistSystemSettingWithHistory = async (
|
||||||
|
key: string,
|
||||||
|
value: unknown,
|
||||||
|
reason: string
|
||||||
|
) => {
|
||||||
|
const [existingSetting] = await db
|
||||||
|
.select({ value: systemSettings.value })
|
||||||
|
.from(systemSettings)
|
||||||
|
.where(eq(systemSettings.key, key))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
await db.insert(systemSettingHistory).values({
|
||||||
|
key,
|
||||||
|
previousValue: existingSetting?.value ?? null,
|
||||||
|
nextValue: value,
|
||||||
|
reason,
|
||||||
|
changedAt: new Date(),
|
||||||
|
changedBy: session.user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await db
|
||||||
|
.insert(systemSettings)
|
||||||
|
.values({
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
updatedBy: session.user.id,
|
||||||
|
})
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: systemSettings.key,
|
||||||
|
set: {
|
||||||
|
value,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
updatedBy: session.user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const persistHomeAnimationConfig = async () => {
|
const persistHomeAnimationConfig = async () => {
|
||||||
if (homeAnimationConfig === undefined) {
|
if (homeAnimationConfig === undefined) {
|
||||||
return;
|
return;
|
||||||
@ -1416,22 +1631,11 @@ export async function PUT(request: NextRequest) {
|
|||||||
|
|
||||||
if (agentPresetLibraryConfig !== undefined) {
|
if (agentPresetLibraryConfig !== undefined) {
|
||||||
const normalized = normalizeAgentPresetLibraryConfig(agentPresetLibraryConfig);
|
const normalized = normalizeAgentPresetLibraryConfig(agentPresetLibraryConfig);
|
||||||
await db
|
await persistSystemSettingWithHistory(
|
||||||
.insert(systemSettings)
|
AGENT_PRESET_LIBRARY_CONFIG_KEY,
|
||||||
.values({
|
normalized,
|
||||||
key: AGENT_PRESET_LIBRARY_CONFIG_KEY,
|
"agent-workflow"
|
||||||
value: normalized,
|
);
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: systemSettings.key,
|
|
||||||
set: {
|
|
||||||
value: normalized,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agentCategoryTreeConfig !== undefined) {
|
if (agentCategoryTreeConfig !== undefined) {
|
||||||
@ -1476,172 +1680,73 @@ export async function PUT(request: NextRequest) {
|
|||||||
|
|
||||||
if (agentWorkflowUiConfig !== undefined) {
|
if (agentWorkflowUiConfig !== undefined) {
|
||||||
const normalized = normalizeAgentWorkflowUiConfig(agentWorkflowUiConfig);
|
const normalized = normalizeAgentWorkflowUiConfig(agentWorkflowUiConfig);
|
||||||
await db
|
await persistSystemSettingWithHistory(
|
||||||
.insert(systemSettings)
|
AGENT_WORKFLOW_UI_CONFIG_KEY,
|
||||||
.values({
|
normalized,
|
||||||
key: AGENT_WORKFLOW_UI_CONFIG_KEY,
|
"agent-workflow"
|
||||||
value: normalized,
|
);
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: systemSettings.key,
|
|
||||||
set: {
|
|
||||||
value: normalized,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agentWorkflowMaterialApiConfig !== undefined) {
|
if (agentWorkflowMaterialApiConfig !== undefined) {
|
||||||
const normalized = normalizeAgentWorkflowMaterialApiConfig(
|
const normalized = normalizeAgentWorkflowMaterialApiConfig(
|
||||||
agentWorkflowMaterialApiConfig
|
agentWorkflowMaterialApiConfig
|
||||||
);
|
);
|
||||||
await db
|
await persistSystemSettingWithHistory(
|
||||||
.insert(systemSettings)
|
AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY,
|
||||||
.values({
|
normalized,
|
||||||
key: AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY,
|
"agent-workflow"
|
||||||
value: normalized,
|
);
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: systemSettings.key,
|
|
||||||
set: {
|
|
||||||
value: normalized,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agentWorkflowGoodsInfoApiConfig !== undefined) {
|
if (agentWorkflowGoodsInfoApiConfig !== undefined) {
|
||||||
const normalized = normalizeAgentWorkflowGoodsInfoApiConfig(
|
const normalized = normalizeAgentWorkflowGoodsInfoApiConfig(
|
||||||
agentWorkflowGoodsInfoApiConfig
|
agentWorkflowGoodsInfoApiConfig
|
||||||
);
|
);
|
||||||
await db
|
await persistSystemSettingWithHistory(
|
||||||
.insert(systemSettings)
|
AGENT_WORKFLOW_GOODS_INFO_API_CONFIG_KEY,
|
||||||
.values({
|
normalized,
|
||||||
key: AGENT_WORKFLOW_GOODS_INFO_API_CONFIG_KEY,
|
"agent-workflow"
|
||||||
value: normalized,
|
);
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: systemSettings.key,
|
|
||||||
set: {
|
|
||||||
value: normalized,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agentWorkflowBrandModelConfig !== undefined) {
|
if (agentWorkflowBrandModelConfig !== undefined) {
|
||||||
const normalized = normalizeAgentWorkflowBrandModelConfig(
|
const normalized = normalizeAgentWorkflowBrandModelConfig(
|
||||||
agentWorkflowBrandModelConfig
|
agentWorkflowBrandModelConfig
|
||||||
);
|
);
|
||||||
await db
|
await persistSystemSettingWithHistory(
|
||||||
.insert(systemSettings)
|
AGENT_WORKFLOW_BRAND_MODEL_CONFIG_KEY,
|
||||||
.values({
|
normalized,
|
||||||
key: AGENT_WORKFLOW_BRAND_MODEL_CONFIG_KEY,
|
"agent-workflow"
|
||||||
value: normalized,
|
);
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: systemSettings.key,
|
|
||||||
set: {
|
|
||||||
value: normalized,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agentWorkflowShowcaseConfig !== undefined) {
|
if (agentWorkflowShowcaseConfig !== undefined) {
|
||||||
const normalized = normalizeAgentWorkflowShowcaseConfig(agentWorkflowShowcaseConfig);
|
const normalized = normalizeAgentWorkflowShowcaseConfig(agentWorkflowShowcaseConfig);
|
||||||
await db
|
await persistSystemSettingWithHistory(
|
||||||
.insert(systemSettings)
|
AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY,
|
||||||
.values({
|
normalized,
|
||||||
key: AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY,
|
"agent-workflow"
|
||||||
value: normalized,
|
);
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: systemSettings.key,
|
|
||||||
set: {
|
|
||||||
value: normalized,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agentPsdTextColorConfig !== undefined) {
|
if (agentPsdTextColorConfig !== undefined) {
|
||||||
const normalized = normalizeAgentPsdTextColorConfig(agentPsdTextColorConfig);
|
const normalized = normalizeAgentPsdTextColorConfig(agentPsdTextColorConfig);
|
||||||
await db
|
await persistSystemSettingWithHistory(
|
||||||
.insert(systemSettings)
|
AGENT_PSD_TEXT_COLOR_CONFIG_KEY,
|
||||||
.values({
|
normalized,
|
||||||
key: AGENT_PSD_TEXT_COLOR_CONFIG_KEY,
|
"agent-workflow"
|
||||||
value: normalized,
|
);
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: systemSettings.key,
|
|
||||||
set: {
|
|
||||||
value: normalized,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agentPsdTemplateSecondaryCategoryConfig !== undefined) {
|
if (agentPsdTemplateSecondaryCategoryConfig !== undefined) {
|
||||||
const normalized = normalizeAgentPsdTemplateSecondaryCategoryConfig(
|
const normalized = normalizeAgentPsdTemplateSecondaryCategoryConfig(
|
||||||
agentPsdTemplateSecondaryCategoryConfig
|
agentPsdTemplateSecondaryCategoryConfig
|
||||||
);
|
);
|
||||||
await db
|
await persistSystemSettingWithHistory(
|
||||||
.insert(systemSettings)
|
AGENT_PSD_TEMPLATE_SECONDARY_CATEGORY_CONFIG_KEY,
|
||||||
.values({
|
normalized,
|
||||||
key: AGENT_PSD_TEMPLATE_SECONDARY_CATEGORY_CONFIG_KEY,
|
"agent-workflow"
|
||||||
value: normalized,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: systemSettings.key,
|
|
||||||
set: {
|
|
||||||
value: normalized,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (agentPsdTemplateSecondaryCategoryConfig !== undefined) {
|
|
||||||
const normalized = normalizeAgentPsdTemplateSecondaryCategoryConfig(
|
|
||||||
agentPsdTemplateSecondaryCategoryConfig
|
|
||||||
);
|
);
|
||||||
await db
|
|
||||||
.insert(systemSettings)
|
|
||||||
.values({
|
|
||||||
key: AGENT_PSD_TEMPLATE_SECONDARY_CATEGORY_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 (agentPsdTemplateCategoryTagConfig !== undefined) {
|
if (agentPsdTemplateCategoryTagConfig !== undefined) {
|
||||||
@ -2144,22 +2249,11 @@ export async function PUT(request: NextRequest) {
|
|||||||
|
|
||||||
if (agentPresetLibraryConfig !== undefined) {
|
if (agentPresetLibraryConfig !== undefined) {
|
||||||
const normalized = normalizeAgentPresetLibraryConfig(agentPresetLibraryConfig);
|
const normalized = normalizeAgentPresetLibraryConfig(agentPresetLibraryConfig);
|
||||||
await db
|
await persistSystemSettingWithHistory(
|
||||||
.insert(systemSettings)
|
AGENT_PRESET_LIBRARY_CONFIG_KEY,
|
||||||
.values({
|
normalized,
|
||||||
key: AGENT_PRESET_LIBRARY_CONFIG_KEY,
|
"agent-workflow"
|
||||||
value: normalized,
|
);
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: systemSettings.key,
|
|
||||||
set: {
|
|
||||||
value: normalized,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agentCategoryTreeConfig !== undefined) {
|
if (agentCategoryTreeConfig !== undefined) {
|
||||||
@ -2204,106 +2298,51 @@ export async function PUT(request: NextRequest) {
|
|||||||
|
|
||||||
if (agentWorkflowUiConfig !== undefined) {
|
if (agentWorkflowUiConfig !== undefined) {
|
||||||
const normalized = normalizeAgentWorkflowUiConfig(agentWorkflowUiConfig);
|
const normalized = normalizeAgentWorkflowUiConfig(agentWorkflowUiConfig);
|
||||||
await db
|
await persistSystemSettingWithHistory(
|
||||||
.insert(systemSettings)
|
AGENT_WORKFLOW_UI_CONFIG_KEY,
|
||||||
.values({
|
normalized,
|
||||||
key: AGENT_WORKFLOW_UI_CONFIG_KEY,
|
"agent-workflow"
|
||||||
value: normalized,
|
);
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: systemSettings.key,
|
|
||||||
set: {
|
|
||||||
value: normalized,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agentWorkflowMaterialApiConfig !== undefined) {
|
if (agentWorkflowMaterialApiConfig !== undefined) {
|
||||||
const normalized = normalizeAgentWorkflowMaterialApiConfig(
|
const normalized = normalizeAgentWorkflowMaterialApiConfig(
|
||||||
agentWorkflowMaterialApiConfig
|
agentWorkflowMaterialApiConfig
|
||||||
);
|
);
|
||||||
await db
|
await persistSystemSettingWithHistory(
|
||||||
.insert(systemSettings)
|
AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY,
|
||||||
.values({
|
normalized,
|
||||||
key: AGENT_WORKFLOW_MATERIAL_API_CONFIG_KEY,
|
"agent-workflow"
|
||||||
value: normalized,
|
);
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: systemSettings.key,
|
|
||||||
set: {
|
|
||||||
value: normalized,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agentWorkflowBrandModelConfig !== undefined) {
|
if (agentWorkflowBrandModelConfig !== undefined) {
|
||||||
const normalized = normalizeAgentWorkflowBrandModelConfig(
|
const normalized = normalizeAgentWorkflowBrandModelConfig(
|
||||||
agentWorkflowBrandModelConfig
|
agentWorkflowBrandModelConfig
|
||||||
);
|
);
|
||||||
await db
|
await persistSystemSettingWithHistory(
|
||||||
.insert(systemSettings)
|
AGENT_WORKFLOW_BRAND_MODEL_CONFIG_KEY,
|
||||||
.values({
|
normalized,
|
||||||
key: AGENT_WORKFLOW_BRAND_MODEL_CONFIG_KEY,
|
"agent-workflow"
|
||||||
value: normalized,
|
);
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: systemSettings.key,
|
|
||||||
set: {
|
|
||||||
value: normalized,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agentWorkflowShowcaseConfig !== undefined) {
|
if (agentWorkflowShowcaseConfig !== undefined) {
|
||||||
const normalized = normalizeAgentWorkflowShowcaseConfig(agentWorkflowShowcaseConfig);
|
const normalized = normalizeAgentWorkflowShowcaseConfig(agentWorkflowShowcaseConfig);
|
||||||
await db
|
await persistSystemSettingWithHistory(
|
||||||
.insert(systemSettings)
|
AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY,
|
||||||
.values({
|
normalized,
|
||||||
key: AGENT_WORKFLOW_SHOWCASE_CONFIG_KEY,
|
"agent-workflow"
|
||||||
value: normalized,
|
);
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: systemSettings.key,
|
|
||||||
set: {
|
|
||||||
value: normalized,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agentPsdTextColorConfig !== undefined) {
|
if (agentPsdTextColorConfig !== undefined) {
|
||||||
const normalized = normalizeAgentPsdTextColorConfig(agentPsdTextColorConfig);
|
const normalized = normalizeAgentPsdTextColorConfig(agentPsdTextColorConfig);
|
||||||
await db
|
await persistSystemSettingWithHistory(
|
||||||
.insert(systemSettings)
|
AGENT_PSD_TEXT_COLOR_CONFIG_KEY,
|
||||||
.values({
|
normalized,
|
||||||
key: AGENT_PSD_TEXT_COLOR_CONFIG_KEY,
|
"agent-workflow"
|
||||||
value: normalized,
|
);
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
})
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: systemSettings.key,
|
|
||||||
set: {
|
|
||||||
value: normalized,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
updatedBy: session.user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (agentPsdTemplateCategoryTagConfig !== undefined) {
|
if (agentPsdTemplateCategoryTagConfig !== undefined) {
|
||||||
|
|||||||
21
lib/db/migrations/0073_add_system_setting_history.sql
Normal file
21
lib/db/migrations/0073_add_system_setting_history.sql
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS "SystemSettingHistory" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"key" varchar(128) NOT NULL,
|
||||||
|
"previousValue" jsonb,
|
||||||
|
"nextValue" jsonb,
|
||||||
|
"reason" varchar(128),
|
||||||
|
"changedAt" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"changedBy" uuid
|
||||||
|
);
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "SystemSettingHistory"
|
||||||
|
ADD CONSTRAINT "SystemSettingHistory_changedBy_User_id_fk"
|
||||||
|
FOREIGN KEY ("changedBy") REFERENCES "public"."User"("id")
|
||||||
|
ON DELETE no action ON UPDATE no action;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS "SystemSettingHistory_key_changedAt_idx"
|
||||||
|
ON "SystemSettingHistory" ("key", "changedAt" DESC);
|
||||||
@ -414,6 +414,13 @@
|
|||||||
"when": 1776042000000,
|
"when": 1776042000000,
|
||||||
"tag": "0072_add_krea_provider_type",
|
"tag": "0072_add_krea_provider_type",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 59,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1776336730695,
|
||||||
|
"tag": "0073_add_system_setting_history",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -248,6 +248,18 @@ export const systemSettings = pgTable("SystemSettings", {
|
|||||||
|
|
||||||
export type SystemSettings = InferSelectModel<typeof systemSettings>;
|
export type SystemSettings = InferSelectModel<typeof systemSettings>;
|
||||||
|
|
||||||
|
export const systemSettingHistory = pgTable("SystemSettingHistory", {
|
||||||
|
id: uuid("id").primaryKey().notNull().defaultRandom(),
|
||||||
|
key: varchar("key", { length: 128 }).notNull(),
|
||||||
|
previousValue: jsonb("previousValue"),
|
||||||
|
nextValue: jsonb("nextValue"),
|
||||||
|
reason: varchar("reason", { length: 128 }),
|
||||||
|
changedAt: timestamp("changedAt").notNull().defaultNow(),
|
||||||
|
changedBy: uuid("changedBy").references(() => user.id),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SystemSettingHistory = InferSelectModel<typeof systemSettingHistory>;
|
||||||
|
|
||||||
export const toolConfig = pgTable("ToolConfig", {
|
export const toolConfig = pgTable("ToolConfig", {
|
||||||
id: uuid("id").primaryKey().notNull().defaultRandom(),
|
id: uuid("id").primaryKey().notNull().defaultRandom(),
|
||||||
toolName: varchar("toolName", { length: 128 }).notNull().unique(),
|
toolName: varchar("toolName", { length: 128 }).notNull().unique(),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user