ai-chatbot/components/agents/AgentWorkflowMaterialSelectionPanel.tsx
Your Name b977242dc7
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: show workflow material picker in mode stage
2026-03-17 20:52:44 +08:00

180 lines
6.3 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.

"use client";
import { useState } from "react";
import { Sparkles } from "lucide-react";
import { AgentWorkflowMaterialPicker } from "@/components/agents/AgentWorkflowMaterialPicker";
import { createEmptyAgentWorkflowMaterialSelection } from "@/lib/agents/workflow-material-config";
import type { AgentWorkflowInputs } from "@/components/agents/AgentWorkflowPanel";
import type { AgentWorkflowMaterialSelections } from "@/types/agent";
interface AgentWorkflowMaterialSelectionPanelProps {
inputs: AgentWorkflowInputs;
onInputsChange: (next: AgentWorkflowInputs) => void;
materials: AgentWorkflowMaterialSelections;
onMaterialsChange: (next: AgentWorkflowMaterialSelections) => void;
title?: string;
description?: string;
disabled?: boolean;
disabledHint?: string;
}
export function AgentWorkflowMaterialSelectionPanel({
inputs,
onInputsChange,
materials,
onMaterialsChange,
title = "SKU / SPU 选图",
description = "先确定生图模式,再搜索主 SKU 或搭配 SKU 的白底图。选中的图片会自动进入对应槽位。",
disabled = false,
disabledHint = "请先在下方选择一个生图模式,再启用 SKU / SPU 搜图。",
}: AgentWorkflowMaterialSelectionPanelProps) {
const [materialSearchState, setMaterialSearchState] = useState<{
primary: { isLoading: boolean; errorText: string | null };
secondary: { isLoading: boolean; errorText: string | null };
}>({
primary: { isLoading: false, errorText: null },
secondary: { isLoading: false, errorText: null },
});
const updateMaterialSearchState = (
source: "primary" | "secondary",
updates: Partial<{ isLoading: boolean; errorText: string | null }>
) => {
setMaterialSearchState((prev) => ({
...prev,
[source]: {
...prev[source],
...updates,
},
}));
};
const handleMaterialInputChange = (source: "primary" | "secondary", value: string) => {
const inputKey = source === "primary" ? "primarySku" : "secondarySku";
const trimmedValue = value.trim().toUpperCase();
const currentQuery = String(materials[source]?.result?.query || "").trim().toUpperCase();
onInputsChange({
...inputs,
[inputKey]: value,
});
if (
trimmedValue !== currentQuery &&
(materials[source]?.result || materials[source]?.selectedImageIds.length)
) {
onMaterialsChange({
...materials,
[source]: createEmptyAgentWorkflowMaterialSelection(),
});
}
if (materialSearchState[source].errorText) {
updateMaterialSearchState(source, { errorText: null });
}
};
const handleSearchMaterials = async (source: "primary" | "secondary") => {
const keyword = (source === "primary" ? inputs.primarySku : inputs.secondarySku).trim();
if (!keyword) {
updateMaterialSearchState(source, { errorText: "请先输入搜索关键词" });
return;
}
try {
updateMaterialSearchState(source, { isLoading: true, errorText: null });
const response = await fetch(
`/api/agent-workflow/material-search?keyword=${encodeURIComponent(keyword)}&source=${source}`,
{ cache: "no-store" }
);
const result = await response.json().catch(() => ({}));
if (!response.ok || !result?.success || !result?.data) {
throw new Error(result?.error?.message || result?.error || "搜索失败");
}
onMaterialsChange({
...materials,
[source]: {
result: result.data,
selectedImageIds: [],
},
});
} catch (error) {
updateMaterialSearchState(source, {
errorText: error instanceof Error ? error.message : "搜索失败",
});
} finally {
updateMaterialSearchState(source, { isLoading: false });
}
};
return (
<div className="space-y-3 rounded-2xl border border-[#dbe3ff] bg-[#f8faff] p-3">
<div className="space-y-1">
<div className="flex items-center gap-2 text-sm font-medium text-[#334155]">
<Sparkles className="h-4 w-4 text-[#4f46e5]" />
<span>{title}</span>
</div>
<div className="text-xs leading-5 text-[#5b6472]">{description}</div>
</div>
<AgentWorkflowMaterialPicker
title="主 SKU / SPU"
description="主商品图会优先自动填充到“产品图1”;如果没有搭配图,再继续补到“产品图2”。"
value={inputs.primarySku}
placeholder="输入主 SKU / SPU"
buttonLabel="搜索主图"
searchNote="当前上游接口实际按可命中的 SKU 查图;若提供了 SPU 接口,后续可直接无缝切换。"
disabled={disabled}
disabledHint={disabledHint}
isLoading={materialSearchState.primary.isLoading}
errorText={materialSearchState.primary.errorText}
result={materials.primary.result}
selectedImageIds={materials.primary.selectedImageIds}
onValueChange={(value) => handleMaterialInputChange("primary", value)}
onSearch={() => {
void handleSearchMaterials("primary");
}}
onSelectedImageIdsChange={(next) =>
onMaterialsChange({
...materials,
primary: {
...materials.primary,
selectedImageIds: next,
},
})
}
/>
<AgentWorkflowMaterialPicker
title="搭配 SKU"
description="搭配商品图会优先自动填充到“产品图2”;模特图仍保留手动上传。"
value={inputs.secondarySku}
placeholder="输入搭配 SKU"
buttonLabel="搜索搭配图"
searchNote="选中的搭配图会进入第二个产品槽位,你也可以在下面继续手动替换。"
disabled={disabled}
disabledHint={disabledHint}
isLoading={materialSearchState.secondary.isLoading}
errorText={materialSearchState.secondary.errorText}
result={materials.secondary.result}
selectedImageIds={materials.secondary.selectedImageIds}
onValueChange={(value) => handleMaterialInputChange("secondary", value)}
onSearch={() => {
void handleSearchMaterials("secondary");
}}
onSelectedImageIdsChange={(next) =>
onMaterialsChange({
...materials,
secondary: {
...materials.secondary,
selectedImageIds: next,
},
})
}
/>
</div>
);
}