ai-chatbot/lib/agents/workflow-composer.ts
Your Name 98fe210b64
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: separate agent projects from canvas storage
2026-03-19 13:07:39 +08:00

244 lines
8.2 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,
getEnabledAgentWorkflowStepTree,
} from "@/lib/agents/workflow-ui-config";
import type {
AgentWorkflowComposerContext,
AgentWorkflowMaterialSelections,
AgentWorkflowReferenceImage,
AgentWorkflowSelectedBrandModel,
AgentWorkflowStepKey,
AgentWorkflowUiConfig,
} from "@/types/agent";
export interface AgentWorkflowInputsValue {
primarySku: string;
secondarySku: string;
brand: string;
store: string;
}
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 selectedKey = params.selections[step.key];
if (!selectedKey) return null;
const option = (step.options || []).find(
(item) => item.key === selectedKey && item.enabled !== false
);
if (!option) return null;
return {
stepKey: step.key,
stepTitle: step.title,
optionKey: option.key,
optionName: option.buttonName,
promptFragment: option.promptFragment || option.buttonName,
};
})
.filter(Boolean) 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 (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}张`
);
}
selections.forEach((item) => {
summaryParts.push(`${item.stepTitle}:${item.optionName}`);
});
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 (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("、")})` : ""
}`
);
}
selections.forEach((item) => {
promptLines.push(`${item.stepTitle}:${item.promptFragment || item.optionName}`);
});
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,
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) => {
const value = params.selections[step.key];
return Boolean(String(value || "").trim());
});
return {
total: enabledSteps.length,
completed: completedSteps.length,
isComplete: enabledSteps.length > 0 && completedSteps.length === enabledSteps.length,
};
}