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) : {}; 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) : {}; const normalizedSteps = defaultAgentWorkflowUiConfig.steps.map((defaultStep, stepIndex) => { const matchedStep = rawSteps.find( (item) => item && typeof item === "object" && normalizeText((item as Record).key, 64) === defaultStep.key ) || null; const matchedStepRecord = matchedStep && typeof matchedStep === "object" ? (matchedStep as Record) : 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).key, 64) === defaultOption.key ) || null; const matchedOptionRecord = matchedOption && typeof matchedOption === "object" ? (matchedOption as Record) : 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), }; }