diff --git a/app/(admin)/settings/agent-workflow/agent-workflow-settings-client.tsx b/app/(admin)/settings/agent-workflow/agent-workflow-settings-client.tsx index bdb086c42..5bd94265a 100644 --- a/app/(admin)/settings/agent-workflow/agent-workflow-settings-client.tsx +++ b/app/(admin)/settings/agent-workflow/agent-workflow-settings-client.tsx @@ -367,7 +367,8 @@ function createPresetLibrarySectionDraft( } function buildWorkflowInputOptionsFromBrandEntries( - entries: AgentWorkflowBrandStoreEntry[] + entries: AgentWorkflowBrandStoreEntry[], + priceBands: string[] = [] ) { const normalizedEntries = entries.map((entry, index) => ({ brandName: entry.brandName.trim(), @@ -380,11 +381,19 @@ function buildWorkflowInputOptionsFromBrandEntries( ), sortOrder: index, })); + const normalizedPriceBands = Array.from( + new Set( + priceBands + .map((item) => item.trim()) + .filter(Boolean) + ) + ); return { brandStoreEntries: normalizedEntries, brands: normalizedEntries.map((entry) => entry.brandName), stores: Array.from(new Set(normalizedEntries.flatMap((entry) => entry.stores))), + priceBands: normalizedPriceBands, }; } @@ -426,6 +435,7 @@ export function AgentWorkflowSettingsClient({ const [expandedSteps, setExpandedSteps] = useState>(new Set()); const [activeTab, setActiveTab] = useState("showcase"); const [brandDraft, setBrandDraft] = useState(""); + const [priceBandDraft, setPriceBandDraft] = useState(""); const [storeDrafts, setStoreDrafts] = useState>({}); const fileInputRefs = useRef>({}); @@ -468,6 +478,13 @@ export function AgentWorkflowSettingsClient({ [], [config.inputOptions?.brandStoreEntries] ); + const priceBandOptions = useMemo( + () => + config.inputOptions?.priceBands || + defaultAgentWorkflowUiConfig.inputOptions?.priceBands || + [], + [config.inputOptions?.priceBands] + ); const configuredBrands = useMemo( () => brandStoreEntries.map((entry) => entry.brandName), [brandStoreEntries] @@ -493,7 +510,10 @@ export function AgentWorkflowSettingsClient({ const updateInputOptions = (nextEntries: AgentWorkflowBrandStoreEntry[]) => { setConfig((prev) => ({ ...prev, - inputOptions: buildWorkflowInputOptionsFromBrandEntries(nextEntries), + inputOptions: buildWorkflowInputOptionsFromBrandEntries( + nextEntries, + prev.inputOptions?.priceBands || [] + ), })); }; @@ -576,6 +596,38 @@ export function AgentWorkflowSettingsClient({ ); }; + const handleAddPriceBand = () => { + const nextValue = normalizeInputOptionValue(priceBandDraft); + if (!nextValue) { + toast.error("请输入价格带区间"); + return; + } + + if (priceBandOptions.some((item) => item.toLowerCase() === nextValue.toLowerCase())) { + toast.error("该价格带已存在"); + return; + } + + setConfig((prev) => ({ + ...prev, + inputOptions: buildWorkflowInputOptionsFromBrandEntries( + prev.inputOptions?.brandStoreEntries || [], + [...(prev.inputOptions?.priceBands || []), nextValue] + ), + })); + setPriceBandDraft(""); + }; + + const handleRemovePriceBand = (priceBand: string) => { + setConfig((prev) => ({ + ...prev, + inputOptions: buildWorkflowInputOptionsFromBrandEntries( + prev.inputOptions?.brandStoreEntries || [], + (prev.inputOptions?.priceBands || []).filter((item) => item !== priceBand) + ), + })); + }; + const enabledStepsCount = useMemo( () => flattenAgentWorkflowSteps(sortedSteps).filter(({ step }) => step.enabled).length, [sortedSteps] @@ -2149,7 +2201,7 @@ export function AgentWorkflowSettingsClient({
基础下拉选项 - 配置品牌与品牌下对应的店铺母子关系 + 配置品牌、店铺母子关系,以及第一页可选的价格带区间
@@ -2271,6 +2323,64 @@ export function AgentWorkflowSettingsClient({ )} + +
+
+ + + {priceBandOptions.length} 个区间 + +
+
+
+ setPriceBandDraft(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + handleAddPriceBand(); + } + }} + placeholder="例如:0-999元 / 1000-1999元" + /> + +
+ + {priceBandOptions.length > 0 ? ( +
+ {priceBandOptions.map((priceBand) => ( +
+ {priceBand} + +
+ ))} +
+ ) : ( +
+ 还没有添加价格带。添加后,第一页品牌和店铺右侧会显示价格带下拉框。 +
+ )} +
+
diff --git a/app/api/site-settings/route.ts b/app/api/site-settings/route.ts index 99eca1294..bef743d35 100644 --- a/app/api/site-settings/route.ts +++ b/app/api/site-settings/route.ts @@ -668,6 +668,7 @@ const updateSchema = z.object({ .object({ brands: z.array(z.string().trim().min(1).max(64)).max(300).optional(), stores: z.array(z.string().trim().min(1).max(64)).max(300).optional(), + priceBands: z.array(z.string().trim().min(1).max(64)).max(300).optional(), brandStoreEntries: z .array( z.object({ diff --git a/app/canvas/[id]/page.tsx b/app/canvas/[id]/page.tsx index 4edc2271b..426562878 100644 --- a/app/canvas/[id]/page.tsx +++ b/app/canvas/[id]/page.tsx @@ -232,6 +232,7 @@ export default function CanvasEditorPage({ params }: PageProps) { secondarySku: "", brand: "", store: "", + priceBand: "", }); const [workflowMaterials, setWorkflowMaterials] = useState( () => createEmptyAgentWorkflowMaterialSelections() @@ -373,6 +374,7 @@ export default function CanvasEditorPage({ params }: PageProps) { secondarySku: "", brand: "", store: "", + priceBand: "", }); setWorkflowMaterials(createEmptyAgentWorkflowMaterialSelections()); setWorkflowSelections({}); @@ -400,6 +402,7 @@ export default function CanvasEditorPage({ params }: PageProps) { secondarySku: String(persistedWorkflow.inputs?.secondarySku || "").trim(), brand: String(persistedWorkflow.inputs?.brand || "").trim(), store: String(persistedWorkflow.inputs?.store || "").trim(), + priceBand: String(persistedWorkflow.inputs?.priceBand || "").trim(), }); setWorkflowMaterials( normalizeAgentWorkflowMaterialSelections(persistedWorkflow.materials) @@ -504,6 +507,7 @@ export default function CanvasEditorPage({ params }: PageProps) { secondarySku: workflowInputs.secondarySku.trim(), brand: workflowInputs.brand.trim(), store: workflowInputs.store.trim(), + priceBand: workflowInputs.priceBand.trim(), }, materials: workflowMaterials, selections: workflowSelections, @@ -548,6 +552,7 @@ export default function CanvasEditorPage({ params }: PageProps) { secondarySku: String(parsed.inputs?.secondarySku || "").trim(), brand: String(parsed.inputs?.brand || "").trim(), store: String(parsed.inputs?.store || "").trim(), + priceBand: String(parsed.inputs?.priceBand || "").trim(), }); setWorkflowMaterials(normalizeAgentWorkflowMaterialSelections(parsed.materials)); setWorkflowSelections(parsed.selections || {}); @@ -706,6 +711,7 @@ export default function CanvasEditorPage({ params }: PageProps) { secondarySku: String(persistedWorkflow.inputs?.secondarySku || "").trim(), brand: String(persistedWorkflow.inputs?.brand || "").trim(), store: String(persistedWorkflow.inputs?.store || "").trim(), + priceBand: String(persistedWorkflow.inputs?.priceBand || "").trim(), }); setWorkflowMaterials(normalizeAgentWorkflowMaterialSelections(persistedWorkflow.materials)); setWorkflowSelections(persistedWorkflow.selections || {}); diff --git a/components/agents/AgentGenerationWorkspaceDarkV2.tsx b/components/agents/AgentGenerationWorkspaceDarkV2.tsx index 7250580c7..37acf044d 100644 --- a/components/agents/AgentGenerationWorkspaceDarkV2.tsx +++ b/components/agents/AgentGenerationWorkspaceDarkV2.tsx @@ -170,6 +170,25 @@ function arePickedAttachmentsEqual( return true; } +function getAttachmentIdentityKey( + attachment: + | PickedAttachment + | { + url?: string | null; + assetId?: string | null; + } + | null + | undefined +) { + const assetId = String(attachment?.assetId || "").trim(); + if (assetId) { + return `asset:${assetId}`; + } + + const url = String(attachment?.url || "").trim(); + return url ? `url:${url}` : null; +} + type ToolStepKey = | "preset" | "brand-model" @@ -489,6 +508,7 @@ export function AgentGenerationWorkspaceDarkV2({ const hiddenPromptRef = useRef(null); const generatedImageKeysRef = useRef>(new Set()); const lastSyncedBrandModelUrlRef = useRef(null); + const lastSyncedProductSlotKeysRef = useRef>([]); const workspaceStateSignatureRef = useRef(""); const [localAgentModeState, setLocalAgentModeState] = useState( () => createEmptyAgentModeWorkspaceState() @@ -639,8 +659,49 @@ export function AgentGenerationWorkspaceDarkV2({ const skuReferenceSourceLabel = getTaskDetailReferenceSourceLabel(skuReferenceTarget); const workflowStoreName = String(workflowContext?.store || "").trim(); const shouldAllowModelOverride = !isSceneGenerationPreset || presetModeConfig.scene.allowModelOverride; - const selectedProductAttachments = attachments.filter((item, index) => - modelSlotIndex >= 0 ? index !== modelSlotIndex && Boolean(item) : Boolean(item) + const brandModelAttachmentKeys = useMemo(() => { + const keys = new Set(); + referenceImages + .filter((image) => image.source === "brandModel") + .forEach((image) => { + const key = getAttachmentIdentityKey(image); + if (key) { + keys.add(key); + } + }); + + const selectedBrandModelKey = getAttachmentIdentityKey({ + url: selectedBrandModel?.imageUrl || null, + assetId: selectedBrandModel?.assetId || null, + }); + if (selectedBrandModelKey) { + keys.add(selectedBrandModelKey); + } + + const currentModelSlotKey = + modelSlotIndex >= 0 ? getAttachmentIdentityKey(attachments[modelSlotIndex]) : null; + if (currentModelSlotKey) { + keys.add(currentModelSlotKey); + } + + return keys; + }, [ + attachments, + modelSlotIndex, + referenceImages, + selectedBrandModel?.assetId, + selectedBrandModel?.imageUrl, + ]); + const selectedProductAttachments = useMemo( + () => + attachments + .slice(0, productSlotCount) + .filter((item): item is PickedAttachment => { + if (!item) return false; + const attachmentKey = getAttachmentIdentityKey(item); + return !attachmentKey || !brandModelAttachmentKeys.has(attachmentKey); + }), + [attachments, brandModelAttachmentKeys, productSlotCount] ); const selectedModelAttachment = modelSlotIndex >= 0 && attachments[modelSlotIndex] ? attachments[modelSlotIndex] : null; @@ -790,6 +851,7 @@ export function AgentGenerationWorkspaceDarkV2({ useEffect(() => { if (!hasAgentUploadSlots) { setAttachments((current) => (current.length === 0 ? current : [])); + lastSyncedProductSlotKeysRef.current = []; return; } const nextSlots: Array = Array.from( @@ -817,15 +879,34 @@ export function AgentGenerationWorkspaceDarkV2({ { length: agentUploadSlots.length }, (_, index) => current[index] || null ); - for (let index = 0; index < agentUploadSlots.length; index += 1) { - if (!result[index] && nextSlots[index]) { - result[index] = nextSlots[index] as PickedAttachment; + for (let index = 0; index < productSlotCount; index += 1) { + const currentSlot = result[index]; + const nextAttachment = nextSlots[index]; + const currentSlotKey = getAttachmentIdentityKey(currentSlot); + const previousSyncedKey = lastSyncedProductSlotKeysRef.current[index] || null; + const currentIsPreviousAutoSelection = + Boolean(previousSyncedKey) && currentSlotKey === previousSyncedKey; + const currentIsBrandModel = + currentSlotKey ? brandModelAttachmentKeys.has(currentSlotKey) : false; + + if (nextAttachment) { + if (!currentSlot || currentIsPreviousAutoSelection || currentIsBrandModel) { + result[index] = nextAttachment as PickedAttachment; + } + } else if (currentIsPreviousAutoSelection || currentIsBrandModel) { + result[index] = null; } } return arePickedAttachmentsEqual(current, result) ? current : result; }); + + lastSyncedProductSlotKeysRef.current = Array.from( + { length: productSlotCount }, + (_, index) => getAttachmentIdentityKey(nextSlots[index]) + ); }, [ agentUploadSlots.length, + brandModelAttachmentKeys, hasAgentUploadSlots, modelSlotEnabled, modelSlotIndex, @@ -2082,13 +2163,13 @@ export function AgentGenerationWorkspaceDarkV2({ 风格步骤卡片

- 在同一页完成风格卡片、品牌模特和 Prompt 组合,不再跳转第二个页面。 + 先完成风格步骤卡片,再继续选择品牌模特,方便后续做人群联动,不再跳转第二个页面。

- {hasBrandModelStep ? brandModelPanel : null} {workflowStylePanel} + {hasBrandModelStep ? brandModelPanel : null}
) : null} diff --git a/components/agents/AgentPageDark.tsx b/components/agents/AgentPageDark.tsx index 90849c03f..6b78d022f 100644 --- a/components/agents/AgentPageDark.tsx +++ b/components/agents/AgentPageDark.tsx @@ -37,6 +37,7 @@ export function AgentPageDark({ secondarySku: "", brand: "", store: "", + priceBand: "", }); const [workflowMaterials, setWorkflowMaterials] = useState( createEmptyAgentWorkflowMaterialSelections() diff --git a/components/agents/AgentWorkflowPanel.tsx b/components/agents/AgentWorkflowPanel.tsx index bd83c1d2a..00eb9dfe9 100644 --- a/components/agents/AgentWorkflowPanel.tsx +++ b/components/agents/AgentWorkflowPanel.tsx @@ -11,6 +11,7 @@ import { cn } from "@/lib/utils"; import { defaultAgentWorkflowUiConfig, getAgentWorkflowBrandStoreEntries, + getAgentWorkflowPriceBandOptions, getAgentWorkflowStoreOptionsByBrand, } from "@/lib/agents/workflow-ui-config"; import { createEmptyAgentWorkflowMaterialSelection } from "@/lib/agents/workflow-material-config"; @@ -30,6 +31,7 @@ export interface AgentWorkflowInputs { secondarySku: string; brand: string; store: string; + priceBand: string; } interface AgentWorkflowPanelProps { @@ -96,6 +98,10 @@ export function AgentWorkflowPanel({ () => getAgentWorkflowStoreOptionsByBrand({ config, brand: inputs.brand }), [config, inputs.brand] ); + const priceBandOptions = useMemo( + () => getAgentWorkflowPriceBandOptions(config), + [config] + ); const resolvedConfig = useMemo(() => { const source = config || defaultAgentWorkflowUiConfig; return { @@ -180,6 +186,7 @@ export function AgentWorkflowPanel({ secondarySku: "", brand: "", store: "", + priceBand: "", }); onMaterialsChange({ primary: createEmptyAgentWorkflowMaterialSelection(), @@ -198,7 +205,7 @@ export function AgentWorkflowPanel({
基础配置
-
+
0 && storeOptions.length === 0)} onValueChange={(value) => onInputsChange({ ...inputs, store: value })} /> + onInputsChange({ ...inputs, priceBand: value })} + />
{(brandOptions.length > 0 || storeOptions.length > 0) && (
@@ -306,6 +322,7 @@ export function AgentWorkflowPanel({ inputs.secondarySku || inputs.brand || inputs.store || + inputs.priceBand || materials.primary.selectedImageIds.length > 0 || materials.secondary.selectedImageIds.length > 0 ? (
@@ -321,6 +338,9 @@ export function AgentWorkflowPanel({ {inputs.store ? ( 店铺:{inputs.store} ) : null} + {inputs.priceBand ? ( + 价格带:{inputs.priceBand} + ) : null} {materials.primary.selectedImageIds.length > 0 ? ( 主SPU图:已选 {materials.primary.selectedImageIds.length} 张 diff --git a/components/agents/AgentWorkflowPanelDark.tsx b/components/agents/AgentWorkflowPanelDark.tsx index f41b7fc74..be0d4a4dc 100644 --- a/components/agents/AgentWorkflowPanelDark.tsx +++ b/components/agents/AgentWorkflowPanelDark.tsx @@ -12,6 +12,7 @@ import { cn } from "@/lib/utils"; import { defaultAgentWorkflowUiConfig, getAgentWorkflowBrandStoreEntries, + getAgentWorkflowPriceBandOptions, getAgentWorkflowStoreOptionsByBrand, } from "@/lib/agents/workflow-ui-config"; import { createEmptyAgentWorkflowMaterialSelection } from "@/lib/agents/workflow-material-config"; @@ -31,6 +32,7 @@ export interface AgentWorkflowInputs { secondarySku: string; brand: string; store: string; + priceBand: string; } interface AgentWorkflowPanelDarkProps { @@ -178,6 +180,10 @@ export function AgentWorkflowPanelDark({ () => getAgentWorkflowStoreOptionsByBrand({ config, brand: inputs.brand }), [config, inputs.brand] ); + const priceBandOptions = useMemo( + () => getAgentWorkflowPriceBandOptions(config), + [config] + ); const resolvedConfig = useMemo(() => { const source = config || defaultAgentWorkflowUiConfig; @@ -267,7 +273,7 @@ export function AgentWorkflowPanelDark({ onReset(); return; } - onInputsChange({ primarySku: "", secondarySku: "", brand: "", store: "" }); + onInputsChange({ primarySku: "", secondarySku: "", brand: "", store: "", priceBand: "" }); onMaterialsChange({ primary: createEmptyAgentWorkflowMaterialSelection(), secondary: createEmptyAgentWorkflowMaterialSelection(), @@ -301,7 +307,7 @@ export function AgentWorkflowPanelDark({ 基础配置 0 && storeOptions.length === 0)} onValueChange={(value) => onInputsChange({ ...inputs, store: value })} /> + onInputsChange({ ...inputs, priceBand: value })} + />
diff --git a/components/agents/AgentWorkflowPanelDarkV2.tsx b/components/agents/AgentWorkflowPanelDarkV2.tsx index 05063a214..a0cf6eb7c 100644 --- a/components/agents/AgentWorkflowPanelDarkV2.tsx +++ b/components/agents/AgentWorkflowPanelDarkV2.tsx @@ -13,6 +13,7 @@ import { flattenAgentWorkflowSteps, flattenVisibleAgentWorkflowSteps, getAgentWorkflowBrandStoreEntries, + getAgentWorkflowPriceBandOptions, getAgentWorkflowSelectedOptionPath, getAgentWorkflowStoreOptionsByBrand, getEnabledAgentWorkflowOptionTree, @@ -39,6 +40,7 @@ export interface AgentWorkflowInputs { secondarySku: string; brand: string; store: string; + priceBand: string; } interface AgentWorkflowPanelDarkV2Props { @@ -202,7 +204,7 @@ export function AgentWorkflowBrandStorePanel({ onInputsChange, theme = "dark", title = "品牌和店铺选择", - description = "先选择品牌和店铺,后续品牌模特与风格步骤会按这里的选择联动。", + description = "先选择品牌、店铺和价格带,后续品牌模特与风格步骤会按这里的选择联动。", }: AgentWorkflowBrandStorePanelProps) { const brandOptions = useMemo( () => config?.inputOptions?.brands || defaultAgentWorkflowUiConfig.inputOptions?.brands || [], @@ -216,6 +218,10 @@ export function AgentWorkflowBrandStorePanel({ () => getAgentWorkflowStoreOptionsByBrand({ config, brand: inputs.brand }), [config, inputs.brand] ); + const priceBandOptions = useMemo( + () => getAgentWorkflowPriceBandOptions(config), + [config] + ); const isDark = theme === "dark"; return ( @@ -233,7 +239,7 @@ export function AgentWorkflowBrandStorePanel({ {description}
-
+
0 && storeOptions.length === 0)} onValueChange={(value) => onInputsChange({ ...inputs, store: value })} /> + onInputsChange({ ...inputs, priceBand: value })} + />
); diff --git a/components/agents/AgentWorkflowPanelNew.tsx b/components/agents/AgentWorkflowPanelNew.tsx index ac95620db..ce507f9f8 100644 --- a/components/agents/AgentWorkflowPanelNew.tsx +++ b/components/agents/AgentWorkflowPanelNew.tsx @@ -12,6 +12,7 @@ import { cn } from "@/lib/utils"; import { defaultAgentWorkflowUiConfig, getAgentWorkflowBrandStoreEntries, + getAgentWorkflowPriceBandOptions, getAgentWorkflowStoreOptionsByBrand, } from "@/lib/agents/workflow-ui-config"; import { createEmptyAgentWorkflowMaterialSelection } from "@/lib/agents/workflow-material-config"; @@ -31,6 +32,7 @@ export interface AgentWorkflowInputs { secondarySku: string; brand: string; store: string; + priceBand: string; } interface AgentWorkflowPanelNewProps { @@ -190,6 +192,10 @@ export function AgentWorkflowPanelNew({ () => getAgentWorkflowStoreOptionsByBrand({ config, brand: inputs.brand }), [config, inputs.brand] ); + const priceBandOptions = useMemo( + () => getAgentWorkflowPriceBandOptions(config), + [config] + ); const resolvedConfig = useMemo(() => { const source = config || defaultAgentWorkflowUiConfig; @@ -288,7 +294,7 @@ export function AgentWorkflowPanelNew({ onReset(); return; } - onInputsChange({ primarySku: "", secondarySku: "", brand: "", store: "" }); + onInputsChange({ primarySku: "", secondarySku: "", brand: "", store: "", priceBand: "" }); onMaterialsChange({ primary: createEmptyAgentWorkflowMaterialSelection(), secondary: createEmptyAgentWorkflowMaterialSelection(), @@ -340,7 +346,7 @@ export function AgentWorkflowPanelNew({ 基础配置 -
+
0 && storeOptions.length === 0)} onValueChange={(value) => onInputsChange({ ...inputs, store: value })} /> + onInputsChange({ ...inputs, priceBand: value })} + />
diff --git a/lib/agents/generation-project-state.ts b/lib/agents/generation-project-state.ts index bf13ba29b..f15f40345 100644 --- a/lib/agents/generation-project-state.ts +++ b/lib/agents/generation-project-state.ts @@ -32,6 +32,7 @@ export function createEmptyAgentWorkflowPersistedState(): AgentWorkflowPersisted secondarySku: "", brand: "", store: "", + priceBand: "", }, materials: createEmptyAgentWorkflowMaterialSelections(), selections: {}, @@ -106,6 +107,7 @@ function normalizeWorkflowState(value: unknown): AgentWorkflowPersistedState { secondarySku: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record).secondarySku || "" : "").trim(), brand: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record).brand || "" : "").trim(), store: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record).store || "" : "").trim(), + priceBand: String(raw.inputs && typeof raw.inputs === "object" ? (raw.inputs as Record).priceBand || "" : "").trim(), }, materials: normalizeAgentWorkflowMaterialSelections(raw.materials), selections: diff --git a/lib/agents/workflow-composer.ts b/lib/agents/workflow-composer.ts index 337533e95..2d42ebd2e 100644 --- a/lib/agents/workflow-composer.ts +++ b/lib/agents/workflow-composer.ts @@ -19,6 +19,7 @@ export interface AgentWorkflowInputsValue { secondarySku: string; brand: string; store: string; + priceBand: string; } export function buildAgentWorkflowComposerContext(params: { @@ -70,6 +71,9 @@ export function buildAgentWorkflowComposerContext(params: { if (params.inputs.store.trim()) { summaryParts.push(`店铺:${params.inputs.store.trim()}`); } + if (params.inputs.priceBand.trim()) { + summaryParts.push(`价格带:${params.inputs.priceBand.trim()}`); + } if (referenceImages.some((item) => item.source === "primary")) { summaryParts.push( `主SPU图:${referenceImages.filter((item) => item.source === "primary").length}张` @@ -106,6 +110,9 @@ export function buildAgentWorkflowComposerContext(params: { if (params.inputs.store.trim()) { promptLines.push(`店铺:${params.inputs.store.trim()}`); } + if (params.inputs.priceBand.trim()) { + promptLines.push(`价格带:${params.inputs.priceBand.trim()}`); + } if (referenceImages.some((item) => item.source === "primary")) { const labels = referenceImages .filter((item) => item.source === "primary") @@ -151,6 +158,7 @@ export function buildAgentWorkflowComposerContext(params: { secondarySku: params.inputs.secondarySku.trim() || undefined, brand: params.inputs.brand.trim() || undefined, store: params.inputs.store.trim() || undefined, + priceBand: params.inputs.priceBand.trim() || undefined, selections, referenceImages, summaryText: summaryParts.join(" / "), diff --git a/lib/agents/workflow-ui-config.ts b/lib/agents/workflow-ui-config.ts index 9ff8a3ae3..5300da8a5 100644 --- a/lib/agents/workflow-ui-config.ts +++ b/lib/agents/workflow-ui-config.ts @@ -133,6 +133,7 @@ export const defaultAgentWorkflowUiConfig: AgentWorkflowUiConfig = { inputOptions: { brands: [], stores: [], + priceBands: [], brandStoreEntries: [], }, steps: defaultSteps.map((step, index) => @@ -402,6 +403,7 @@ export function normalizeAgentWorkflowUiConfig(value: unknown): AgentWorkflowUiC ); const normalizedBrands = normalizeStringList(rawInputOptions.brands); const normalizedStores = normalizeStringList(rawInputOptions.stores); + const normalizedPriceBands = normalizeStringList(rawInputOptions.priceBands); const fallbackBrandStoreEntries: AgentWorkflowBrandStoreEntry[] = normalizedBrandStoreEntries.length > 0 ? normalizedBrandStoreEntries @@ -571,6 +573,7 @@ export function normalizeAgentWorkflowUiConfig(value: unknown): AgentWorkflowUiC new Set(fallbackBrandStoreEntries.flatMap((entry) => entry.stores)) ) : normalizedStores, + priceBands: normalizedPriceBands, brandStoreEntries: fallbackBrandStoreEntries, }, steps: normalizedSteps, @@ -601,6 +604,10 @@ export function getAgentWorkflowStoreOptionsByBrand(params: { return params.config?.inputOptions?.stores || defaultAgentWorkflowUiConfig.inputOptions?.stores || []; } +export function getAgentWorkflowPriceBandOptions(config?: AgentWorkflowUiConfig | null) { + return config?.inputOptions?.priceBands || defaultAgentWorkflowUiConfig.inputOptions?.priceBands || []; +} + export function parseAgentWorkflowSelectionPath(value?: string | null) { return String(value || "") .split(">") diff --git a/types/agent.ts b/types/agent.ts index 2d04f1553..2fcefa5c1 100644 --- a/types/agent.ts +++ b/types/agent.ts @@ -101,6 +101,7 @@ export interface AgentWorkflowUiConfig { inputOptions?: { brands: string[]; stores: string[]; + priceBands?: string[]; brandStoreEntries?: AgentWorkflowBrandStoreEntry[]; }; steps: AgentWorkflowStepUiConfig[]; @@ -243,6 +244,7 @@ export interface AgentWorkflowComposerContext { secondarySku?: string; brand?: string; store?: string; + priceBand?: string; selections: AgentWorkflowComposerSelection[]; referenceImages: AgentWorkflowReferenceImage[]; summaryText: string; @@ -321,6 +323,7 @@ export interface AgentWorkflowPersistedState { secondarySku: string; brand: string; store: string; + priceBand: string; }; materials?: AgentWorkflowMaterialSelections; selections: Partial>;