ai-chatbot/lib/agents/workflow-composer.ts
Your Name 8df3173cb2
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
fix agent workflow freshness and goods info prompts
2026-03-26 00:08:26 +08:00

417 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
defaultAgentWorkflowUiConfig,
flattenVisibleAgentWorkflowSteps,
getAgentWorkflowSelectedOptionPath,
getEnabledAgentWorkflowStepTree,
isAgentWorkflowStepSelectionComplete,
} from "@/lib/agents/workflow-ui-config";
import type {
AgentWorkflowComposerContext,
AgentWorkflowComposerSelection,
AgentWorkflowMaterialSelections,
AgentWorkflowReferenceImage,
AgentWorkflowSelectedBrandModel,
AgentWorkflowStepKey,
AgentWorkflowUiConfig,
} from "@/types/agent";
export interface AgentWorkflowInputsValue {
primarySku: string;
secondarySku: string;
brand: string;
store: string;
priceBand: string;
}
function buildGoodsInfoPromptLine(params: {
label: string;
result?: AgentWorkflowMaterialSelections["primary"]["result"] | null;
}) {
const goodsInfo = params.result?.goodsInfo;
if (!goodsInfo) {
return "";
}
const parts = [
goodsInfo.productName ? `商品名称:${goodsInfo.productName}` : "",
goodsInfo.sizeInfo ? `尺寸信息:${goodsInfo.sizeInfo}` : "",
goodsInfo.materialInfo ? `材质信息:${goodsInfo.materialInfo}` : "",
goodsInfo.designer ? `设计师:${goodsInfo.designer}` : "",
goodsInfo.buyer ? `采购负责人:${goodsInfo.buyer}` : "",
goodsInfo.boardType ? `板材类型:${goodsInfo.boardType}` : "",
].filter(Boolean);
if (parts.length === 0) {
return "";
}
return `${params.label}:${parts.join(";")}`;
}
function buildGoodsInfoSizeConstraintLine(params: {
label: string;
result?: AgentWorkflowMaterialSelections["primary"]["result"] | null;
}) {
const goodsInfo = params.result?.goodsInfo;
const sizeInfo = String(goodsInfo?.sizeInfo || "").trim();
if (!sizeInfo) {
return "";
}
return `${params.label}尺寸比例约束:尺寸信息为 ${sizeInfo}。生成场景时必须依据该尺寸信息判断产品在空间中的真实体量、长宽高关系和与周边家具/人物/通道的比例,不得随意放大、缩小或改变产品结构比例。`;
}
function buildGoodsInfoSummaryLine(params: {
label: string;
result?: AgentWorkflowMaterialSelections["primary"]["result"] | null;
}) {
const goodsInfo = params.result?.goodsInfo;
if (!goodsInfo) {
return "";
}
const parts = [
goodsInfo.productName || "",
goodsInfo.sizeInfo || "",
goodsInfo.materialInfo || "",
].filter(Boolean);
if (parts.length === 0) {
return "";
}
return `${params.label}:${parts.join(" / ")}`;
}
function normalizeWorkflowText(value: string) {
return String(value || "")
.replace(/\s+/g, " ")
.trim();
}
function buildGroupedSelectionLines(params: {
selections: AgentWorkflowComposerSelection[];
valueKey: "optionName" | "promptFragment";
separator: string;
}) {
const grouped = new Map<
string,
{
stepTitle: string;
values: string[];
valueSet: Set<string>;
}
>();
params.selections.forEach((item) => {
const rawValue =
params.valueKey === "promptFragment"
? item.promptFragment || item.optionName
: item.optionName;
const normalizedValue = normalizeWorkflowText(rawValue || "");
if (!normalizedValue) {
return;
}
const groupKey = item.stepKey;
const currentGroup = grouped.get(groupKey) || {
stepTitle: item.stepTitle,
values: [],
valueSet: new Set<string>(),
};
const dedupeKey = normalizedValue.toLowerCase();
if (!currentGroup.valueSet.has(dedupeKey)) {
currentGroup.valueSet.add(dedupeKey);
currentGroup.values.push(normalizedValue);
}
grouped.set(groupKey, currentGroup);
});
return Array.from(grouped.values())
.filter((group) => group.values.length > 0)
.map((group) => `${group.stepTitle}:${group.values.join(params.separator)}`);
}
export function buildAgentWorkflowComposerContext(params: {
config?: AgentWorkflowUiConfig | null;
inputs: AgentWorkflowInputsValue;
materials?: AgentWorkflowMaterialSelections | null;
selectedBrandModel?: AgentWorkflowSelectedBrandModel | null;
selections: Partial<Record<AgentWorkflowStepKey, string>>;
}): AgentWorkflowComposerContext | null {
const config = params.config || defaultAgentWorkflowUiConfig;
const steps = flattenVisibleAgentWorkflowSteps(
getEnabledAgentWorkflowStepTree(Array.isArray(config.steps) ? config.steps : []),
params.selections
)
.map(({ step }) => step)
.filter((step) => Array.isArray(step.options) && step.options.length > 0);
const selections = steps
.map((step) => {
const selectedOptions = getAgentWorkflowSelectedOptionPath({
step,
selectionValue: params.selections[step.key],
});
if (selectedOptions.length === 0) return [];
return selectedOptions.map((option) => ({
stepKey: step.key,
stepTitle: step.title,
optionKey: option.key,
optionName: option.buttonName,
promptFragment: option.promptFragment || option.buttonName,
}));
})
.flat() as AgentWorkflowComposerContext["selections"];
const referenceImages = getSelectedWorkflowReferenceImages({
inputs: params.inputs,
materials: params.materials,
selectedBrandModel: params.selectedBrandModel,
});
const summaryParts: string[] = [];
if (params.inputs.primarySku.trim()) {
summaryParts.push(`主SKU:${params.inputs.primarySku.trim()}`);
}
if (params.inputs.secondarySku.trim()) {
summaryParts.push(`搭配SKU:${params.inputs.secondarySku.trim()}`);
}
if (params.inputs.brand.trim()) {
summaryParts.push(`品牌:${params.inputs.brand.trim()}`);
}
if (params.inputs.store.trim()) {
summaryParts.push(`店铺:${params.inputs.store.trim()}`);
}
if (params.inputs.priceBand.trim()) {
summaryParts.push(`价格带:${params.inputs.priceBand.trim()}`);
}
const primaryGoodsInfoSummary = buildGoodsInfoSummaryLine({
label: "主SKU商品信息",
result: params.materials?.primary?.result || null,
});
if (primaryGoodsInfoSummary) {
summaryParts.push(primaryGoodsInfoSummary);
}
const secondaryGoodsInfoSummary = buildGoodsInfoSummaryLine({
label: "搭配SKU商品信息",
result: params.materials?.secondary?.result || null,
});
if (secondaryGoodsInfoSummary) {
summaryParts.push(secondaryGoodsInfoSummary);
}
if (referenceImages.some((item) => item.source === "primary")) {
summaryParts.push(
`主SPU图:${referenceImages.filter((item) => item.source === "primary").length}张`
);
}
if (referenceImages.some((item) => item.source === "secondary")) {
summaryParts.push(
`搭配SKU图:${referenceImages.filter((item) => item.source === "secondary").length}张`
);
}
if (referenceImages.some((item) => item.source === "brandModel")) {
summaryParts.push(
`品牌模特图:${referenceImages.filter((item) => item.source === "brandModel").length}张`
);
}
summaryParts.push(
...buildGroupedSelectionLines({
selections,
valueKey: "optionName",
separator: " / ",
})
);
if (summaryParts.length === 0) {
return null;
}
const promptLines: string[] = [];
if (params.inputs.primarySku.trim()) {
promptLines.push(`主SKU:${params.inputs.primarySku.trim()}`);
}
if (params.inputs.secondarySku.trim()) {
promptLines.push(`搭配SKU:${params.inputs.secondarySku.trim()}`);
}
if (params.inputs.brand.trim()) {
promptLines.push(`品牌:${params.inputs.brand.trim()}`);
}
if (params.inputs.store.trim()) {
promptLines.push(`店铺:${params.inputs.store.trim()}`);
}
if (params.inputs.priceBand.trim()) {
promptLines.push(`价格带:${params.inputs.priceBand.trim()}`);
}
const primaryGoodsInfoPrompt = buildGoodsInfoPromptLine({
label: "主SKU商品信息",
result: params.materials?.primary?.result || null,
});
if (primaryGoodsInfoPrompt) {
promptLines.push(primaryGoodsInfoPrompt);
}
const primaryGoodsInfoSizeConstraint = buildGoodsInfoSizeConstraintLine({
label: "主SKU",
result: params.materials?.primary?.result || null,
});
if (primaryGoodsInfoSizeConstraint) {
promptLines.push(primaryGoodsInfoSizeConstraint);
}
const secondaryGoodsInfoPrompt = buildGoodsInfoPromptLine({
label: "搭配SKU商品信息",
result: params.materials?.secondary?.result || null,
});
if (secondaryGoodsInfoPrompt) {
promptLines.push(secondaryGoodsInfoPrompt);
}
const secondaryGoodsInfoSizeConstraint = buildGoodsInfoSizeConstraintLine({
label: "搭配SKU",
result: params.materials?.secondary?.result || null,
});
if (secondaryGoodsInfoSizeConstraint) {
promptLines.push(secondaryGoodsInfoSizeConstraint);
}
if (referenceImages.some((item) => item.source === "primary")) {
const labels = referenceImages
.filter((item) => item.source === "primary")
.map((item) => item.label)
.filter(Boolean)
.slice(0, 8);
promptLines.push(
`主SPU参考图:已选${referenceImages.filter((item) => item.source === "primary").length}张${
labels.length > 0 ? `(${labels.join("、")})` : ""
}`
);
}
if (referenceImages.some((item) => item.source === "secondary")) {
const labels = referenceImages
.filter((item) => item.source === "secondary")
.map((item) => item.label)
.filter(Boolean)
.slice(0, 8);
promptLines.push(
`搭配SKU参考图:已选${referenceImages.filter((item) => item.source === "secondary").length}张${
labels.length > 0 ? `(${labels.join("、")})` : ""
}`
);
}
if (referenceImages.some((item) => item.source === "brandModel")) {
const labels = referenceImages
.filter((item) => item.source === "brandModel")
.map((item) => item.label)
.filter(Boolean)
.slice(0, 8);
promptLines.push(
`品牌模特图:已选${referenceImages.filter((item) => item.source === "brandModel").length}张${
labels.length > 0 ? `(${labels.join("、")})` : ""
}`
);
}
promptLines.push(
...buildGroupedSelectionLines({
selections,
valueKey: "promptFragment",
separator: ";",
})
);
return {
primarySku: params.inputs.primarySku.trim() || undefined,
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(" / "),
promptText: promptLines.join("\n"),
};
}
export function getSelectedWorkflowReferenceImages(params: {
inputs: AgentWorkflowInputsValue;
materials?: AgentWorkflowMaterialSelections | null;
selectedBrandModel?: AgentWorkflowSelectedBrandModel | null;
}): AgentWorkflowReferenceImage[] {
const materials = params.materials;
if (!materials) return [];
const collect = (
source: "primary" | "secondary",
sourceLabel: string,
fallbackSku: string
) => {
const state = materials[source];
const result = state?.result;
if (!result || !Array.isArray(result.images) || result.images.length === 0) {
return [] as AgentWorkflowReferenceImage[];
}
const selectedIds = new Set(
(state.selectedImageIds || []).map((item) => String(item || "").trim()).filter(Boolean)
);
if (selectedIds.size === 0) {
return [] as AgentWorkflowReferenceImage[];
}
return result.images
.filter((item) => selectedIds.has(item.id))
.map((item) => ({
id: item.id,
source,
sourceLabel,
sku: result.matchedSku || fallbackSku || undefined,
productModel: result.productModel || null,
label: item.label,
name: item.fileName || item.label,
url: item.imageUrl,
assetId: item.assetId || null,
mimeType: item.mimeType || null,
}));
};
return [
...collect("primary", "主SPU", params.inputs.primarySku.trim()),
...collect("secondary", "搭配SKU", params.inputs.secondarySku.trim()),
...(params.selectedBrandModel?.imageUrl
? [
{
id: params.selectedBrandModel.id,
source: "brandModel" as const,
sourceLabel: "品牌模特",
label: params.selectedBrandModel.displayName || params.selectedBrandModel.brandName,
name: params.selectedBrandModel.displayName || params.selectedBrandModel.brandName,
url: params.selectedBrandModel.imageUrl,
assetId: params.selectedBrandModel.assetId || null,
mimeType: params.selectedBrandModel.mimeType || null,
},
]
: []),
];
}
export function getAgentWorkflowProgress(params: {
config?: AgentWorkflowUiConfig | null;
selections: Partial<Record<AgentWorkflowStepKey, string>>;
}) {
const config = params.config || defaultAgentWorkflowUiConfig;
const enabledSteps = flattenVisibleAgentWorkflowSteps(
getEnabledAgentWorkflowStepTree(config.steps || []),
params.selections
)
.map(({ step }) => step)
.filter((step) => Array.isArray(step.options) && step.options.length > 0);
const completedSteps = enabledSteps.filter((step) => {
return isAgentWorkflowStepSelectionComplete({
step,
selectionValue: params.selections[step.key],
});
});
return {
total: enabledSteps.length,
completed: completedSteps.length,
isComplete: enabledSteps.length > 0 && completedSteps.length === enabledSteps.length,
};
}