244 lines
8.2 KiB
TypeScript
244 lines
8.2 KiB
TypeScript
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,
|
||
};
|
||
}
|