239 lines
9.7 KiB
TypeScript
239 lines
9.7 KiB
TypeScript
import type {
|
||
AgentWorkflowOptionUiConfig,
|
||
AgentWorkflowStepKey,
|
||
AgentWorkflowStepUiConfig,
|
||
AgentWorkflowUiConfig,
|
||
} from "@/types/agent";
|
||
|
||
export const AGENT_WORKFLOW_UI_CONFIG_KEY = "agent_workflow_ui_config";
|
||
|
||
function cloneOption(
|
||
option: AgentWorkflowOptionUiConfig
|
||
): AgentWorkflowOptionUiConfig {
|
||
return {
|
||
...option,
|
||
description: option.description || "",
|
||
previewImageUrl: option.previewImageUrl || "",
|
||
promptFragment: option.promptFragment || "",
|
||
};
|
||
}
|
||
|
||
function cloneStep(step: AgentWorkflowStepUiConfig): AgentWorkflowStepUiConfig {
|
||
return {
|
||
...step,
|
||
description: step.description || "",
|
||
options: step.options.map(cloneOption),
|
||
};
|
||
}
|
||
|
||
function createStep(
|
||
key: AgentWorkflowStepKey,
|
||
title: string,
|
||
description: string,
|
||
options: AgentWorkflowOptionUiConfig[]
|
||
): AgentWorkflowStepUiConfig {
|
||
return {
|
||
key,
|
||
title,
|
||
description,
|
||
enabled: true,
|
||
sortOrder: 0,
|
||
options,
|
||
};
|
||
}
|
||
|
||
function createOption(
|
||
key: string,
|
||
buttonName: string,
|
||
promptFragment: string,
|
||
sortOrder: number
|
||
): AgentWorkflowOptionUiConfig {
|
||
return {
|
||
key,
|
||
buttonName,
|
||
description: "",
|
||
previewImageUrl: "",
|
||
enabled: true,
|
||
sortOrder,
|
||
promptFragment,
|
||
};
|
||
}
|
||
|
||
const defaultSteps: AgentWorkflowStepUiConfig[] = [
|
||
createStep("audience", "人群选择", "先确认目标家庭结构与生活状态。", [
|
||
createOption("single_youth", "独居青年", "面向独居青年,强调轻盈、效率感和个人表达。", 0),
|
||
createOption("young_couple", "年轻情侣", "面向年轻情侣,强调陪伴感、松弛感和高颜值日常。", 1),
|
||
createOption("midlife_no_kids", "中年无孩夫妻", "面向中年无孩夫妻,强调稳定、质感与舒适收纳。", 2),
|
||
createOption("family_with_kids", "有孩家庭", "面向有孩家庭,强调互动、耐用、安全与成长场景。", 3),
|
||
createOption("senior_couple", "老年夫妻", "面向老年夫妻,强调便利、温暖、低刺激与安心动线。", 4),
|
||
]),
|
||
createStep("style", "主题风格", "确定空间视觉主风格。", [
|
||
createOption("japanese", "日系风", "整体风格为日系,强调自然木色、留白与温和秩序。", 0),
|
||
createOption("new_chinese", "新中式", "整体风格为新中式,强调东方韵味与现代克制。", 1),
|
||
createOption("american", "美式", "整体风格为美式,强调舒展体量、生活感与复古质感。", 2),
|
||
createOption("healing", "治愈风", "整体风格为治愈风,强调柔和、放松与亲和力。", 3),
|
||
createOption("modern", "现代风", "整体风格为现代风,强调简洁、利落与当代感。", 4),
|
||
createOption("eclectic", "混搭风", "整体风格为混搭风,强调层次变化与个性搭配。", 5),
|
||
createOption("vintage", "中古风", "整体风格为中古风,强调复古家具与时间感。", 6),
|
||
createOption("cream", "奶油风", "整体风格为奶油风,强调柔光、低饱和与绵密材质。", 7),
|
||
]),
|
||
createStep("space", "空间选择", "确定主要空间。", [
|
||
createOption("living_room", "客厅", "主空间为客厅。", 0),
|
||
createOption("kitchen", "厨房", "主空间为厨房。", 1),
|
||
createOption("bedroom", "卧室", "主空间为卧室。", 2),
|
||
createOption("dining_room", "餐厅", "主空间为餐厅。", 3),
|
||
createOption("study", "书房", "主空间为书房。", 4),
|
||
createOption("foyer", "门厅", "主空间为门厅。", 5),
|
||
createOption("balcony", "阳台", "主空间为阳台。", 6),
|
||
createOption("bathroom", "卫生间", "主空间为卫生间。", 7),
|
||
]),
|
||
createStep("subSpace", "子空间选择", "确定重点表现区域。", [
|
||
createOption("tv_console", "电视柜空间", "重点表现电视柜空间与背景墙关系。", 0),
|
||
createOption("sofa_area", "沙发空间", "重点表现沙发空间与会客氛围。", 1),
|
||
createOption("dining_table", "餐桌空间", "重点表现餐桌空间与就餐动线。", 2),
|
||
createOption("bedside_area", "床头空间", "重点表现床头空间与睡眠氛围。", 3),
|
||
createOption("island_area", "岛台空间", "重点表现岛台空间与操作效率。", 4),
|
||
createOption("desk_area", "书桌空间", "重点表现书桌空间与专注氛围。", 5),
|
||
]),
|
||
createStep("hardDecoration", "硬装方案", "确定空间硬装基调。", [
|
||
createOption("hard_plan_1", "硬装方案1", "硬装采用方案1,整体偏克制基础款。", 0),
|
||
createOption("hard_plan_2", "硬装方案2", "硬装采用方案2,整体偏精致层次款。", 1),
|
||
createOption("hard_plan_3", "硬装方案3", "硬装采用方案3,整体偏视觉强化款。", 2),
|
||
]),
|
||
createStep("softDecoration", "软装方案", "确定软装搭配方向。", [
|
||
createOption("soft_plan_1", "软装方案1", "软装采用方案1,整体偏稳妥耐看。", 0),
|
||
createOption("soft_plan_2", "软装方案2", "软装采用方案2,整体偏生活感与柔和搭配。", 1),
|
||
createOption("soft_plan_3", "软装方案3", "软装采用方案3,整体偏高辨识度和氛围表达。", 2),
|
||
]),
|
||
createStep("lighting", "光氛选择", "确定光照时段与氛围。", [
|
||
createOption("natural_daylight", "自然阳光", "光照氛围为自然阳光,通透明亮。", 0),
|
||
createOption("warm_afternoon", "暖调午后", "光照氛围为暖调午后,柔和松弛。", 1),
|
||
createOption("romantic_night", "浪漫夜晚", "光照氛围为浪漫夜晚,暖光层次分明。", 2),
|
||
]),
|
||
createStep("accessories", "配饰陈列", "确定配饰收口与陈列方式。", [
|
||
createOption("accessories_a", "配饰方案A", "配饰采用方案A,强调克制点缀。", 0),
|
||
createOption("accessories_b", "配饰方案B", "配饰采用方案B,强调生活化陈列。", 1),
|
||
createOption("accessories_c", "配饰方案C", "配饰采用方案C,强调高完成度展示。", 2),
|
||
]),
|
||
];
|
||
|
||
export const defaultAgentWorkflowUiConfig: AgentWorkflowUiConfig = {
|
||
inputOptions: {
|
||
brands: [],
|
||
stores: [],
|
||
},
|
||
steps: defaultSteps.map((step, index) =>
|
||
cloneStep({
|
||
...step,
|
||
sortOrder: index,
|
||
})
|
||
),
|
||
};
|
||
|
||
function normalizeText(value: unknown, maxLength: number) {
|
||
return String(value || "").trim().slice(0, maxLength);
|
||
}
|
||
|
||
function normalizeBoolean(value: unknown, fallback: boolean) {
|
||
return typeof value === "boolean" ? value : fallback;
|
||
}
|
||
|
||
function normalizeNumber(value: unknown, fallback: number, max = 999) {
|
||
const parsed = Number(value);
|
||
if (!Number.isFinite(parsed)) return fallback;
|
||
return Math.max(0, Math.min(max, Math.round(parsed)));
|
||
}
|
||
|
||
export function normalizeAgentWorkflowUiConfig(value: unknown): AgentWorkflowUiConfig {
|
||
const raw =
|
||
value && typeof value === "object" && !Array.isArray(value)
|
||
? (value as Record<string, unknown>)
|
||
: {};
|
||
const rawSteps = Array.isArray(raw.steps) ? raw.steps : [];
|
||
const normalizeStringList = (input: unknown) => {
|
||
if (!Array.isArray(input)) return [] as string[];
|
||
return Array.from(
|
||
new Set(
|
||
input
|
||
.map((item) => normalizeText(item, 64))
|
||
.filter(Boolean)
|
||
.slice(0, 300)
|
||
)
|
||
);
|
||
};
|
||
const rawInputOptions =
|
||
raw.inputOptions && typeof raw.inputOptions === "object" && !Array.isArray(raw.inputOptions)
|
||
? (raw.inputOptions as Record<string, unknown>)
|
||
: {};
|
||
|
||
const normalizedSteps = defaultAgentWorkflowUiConfig.steps.map((defaultStep, stepIndex) => {
|
||
const matchedStep =
|
||
rawSteps.find(
|
||
(item) =>
|
||
item &&
|
||
typeof item === "object" &&
|
||
normalizeText((item as Record<string, unknown>).key, 64) === defaultStep.key
|
||
) || null;
|
||
const matchedStepRecord =
|
||
matchedStep && typeof matchedStep === "object"
|
||
? (matchedStep as Record<string, unknown>)
|
||
: null;
|
||
const rawOptions = Array.isArray(matchedStepRecord?.options)
|
||
? (matchedStepRecord?.options as unknown[])
|
||
: [];
|
||
|
||
const options = defaultStep.options.map((defaultOption, optionIndex) => {
|
||
const matchedOption =
|
||
rawOptions.find(
|
||
(item) =>
|
||
item &&
|
||
typeof item === "object" &&
|
||
normalizeText((item as Record<string, unknown>).key, 64) === defaultOption.key
|
||
) || null;
|
||
const matchedOptionRecord =
|
||
matchedOption && typeof matchedOption === "object"
|
||
? (matchedOption as Record<string, unknown>)
|
||
: null;
|
||
|
||
return {
|
||
key: defaultOption.key,
|
||
buttonName:
|
||
normalizeText(matchedOptionRecord?.buttonName, 64) || defaultOption.buttonName,
|
||
description:
|
||
normalizeText(matchedOptionRecord?.description, 300) || defaultOption.description || "",
|
||
previewImageUrl: normalizeText(matchedOptionRecord?.previewImageUrl, 1000),
|
||
enabled: normalizeBoolean(matchedOptionRecord?.enabled, defaultOption.enabled),
|
||
sortOrder: normalizeNumber(
|
||
matchedOptionRecord?.sortOrder,
|
||
defaultOption.sortOrder ?? optionIndex
|
||
),
|
||
promptFragment:
|
||
normalizeText(matchedOptionRecord?.promptFragment, 1000) ||
|
||
defaultOption.promptFragment ||
|
||
"",
|
||
};
|
||
});
|
||
|
||
return {
|
||
key: defaultStep.key,
|
||
title: normalizeText(matchedStepRecord?.title, 64) || defaultStep.title,
|
||
description:
|
||
normalizeText(matchedStepRecord?.description, 300) || defaultStep.description || "",
|
||
enabled: normalizeBoolean(matchedStepRecord?.enabled, defaultStep.enabled),
|
||
sortOrder: normalizeNumber(
|
||
matchedStepRecord?.sortOrder,
|
||
defaultStep.sortOrder ?? stepIndex
|
||
),
|
||
options,
|
||
};
|
||
});
|
||
|
||
return {
|
||
inputOptions: {
|
||
brands: normalizeStringList(rawInputOptions.brands),
|
||
stores: normalizeStringList(rawInputOptions.stores),
|
||
},
|
||
steps: normalizedSteps.sort((left, right) => left.sortOrder - right.sortOrder),
|
||
};
|
||
}
|