feat: improve sku material loading feedback
This commit is contained in:
parent
5d1d2eb653
commit
ae5f1309b1
@ -61,6 +61,9 @@ export function AgentWorkflowMaterialPicker({
|
||||
const selectedCount = result?.images
|
||||
? result.images.filter((item) => selectedSet.has(item.id)).length
|
||||
: 0;
|
||||
const hasSelectableImages = Boolean(
|
||||
result?.images?.some((item) => Boolean(String(item.imageUrl || "").trim()))
|
||||
);
|
||||
|
||||
const handleToggleImage = (imageId: string) => {
|
||||
const next = new Set(selectedImageIds);
|
||||
@ -73,7 +76,11 @@ export function AgentWorkflowMaterialPicker({
|
||||
};
|
||||
|
||||
const handleSelectAll = () => {
|
||||
onSelectedImageIdsChange(result?.images?.map((item) => item.id) || []);
|
||||
onSelectedImageIdsChange(
|
||||
result?.images
|
||||
?.filter((item) => Boolean(String(item.imageUrl || "").trim()))
|
||||
.map((item) => item.id) || []
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -229,7 +236,7 @@ export function AgentWorkflowMaterialPicker({
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleSelectAll}
|
||||
disabled={!result.images?.length}
|
||||
disabled={!hasSelectableImages}
|
||||
className={cn(
|
||||
isDark &&
|
||||
"border-white/12 bg-white/[0.04] text-white/82 hover:bg-white/[0.10] hover:text-white disabled:text-white/35"
|
||||
@ -242,7 +249,7 @@ export function AgentWorkflowMaterialPicker({
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onSelectedImageIdsChange([])}
|
||||
disabled={selectedCount === 0}
|
||||
disabled={selectedCount === 0 && !hasSelectableImages}
|
||||
className={cn(
|
||||
isDark &&
|
||||
"border-white/12 bg-white/[0.04] text-white/82 hover:bg-white/[0.10] hover:text-white disabled:text-white/35"
|
||||
@ -316,16 +323,22 @@ export function AgentWorkflowMaterialPicker({
|
||||
) : null}
|
||||
|
||||
{result.images?.length ? (
|
||||
<div className="grid grid-cols-2 gap-3 xl:grid-cols-3">
|
||||
<div className="grid grid-cols-2 gap-3 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{result.images.map((image) => {
|
||||
const selected = selectedSet.has(image.id);
|
||||
const isPlaceholder = !String(image.imageUrl || "").trim();
|
||||
return (
|
||||
<button
|
||||
key={image.id}
|
||||
type="button"
|
||||
onClick={() => handleToggleImage(image.id)}
|
||||
onClick={() => {
|
||||
if (isPlaceholder) return;
|
||||
handleToggleImage(image.id);
|
||||
}}
|
||||
disabled={isPlaceholder}
|
||||
className={cn(
|
||||
"group overflow-hidden rounded-[24px] border bg-background text-left transition-all",
|
||||
isPlaceholder && "cursor-wait",
|
||||
selected
|
||||
? isDark
|
||||
? "border-emerald-400/70 bg-white/[0.03] shadow-[0_0_0_1px_rgba(52,211,153,0.2)]"
|
||||
@ -341,43 +354,55 @@ export function AgentWorkflowMaterialPicker({
|
||||
isDark ? "bg-white/[0.04]" : "bg-muted/40"
|
||||
)}
|
||||
>
|
||||
<img
|
||||
src={image.imageUrl}
|
||||
alt={image.label}
|
||||
className="h-full w-full object-cover"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
onError={(event) => {
|
||||
event.currentTarget.style.display = "none";
|
||||
}}
|
||||
/>
|
||||
{isPlaceholder ? (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center gap-3 bg-[radial-gradient(circle_at_top,rgba(255,255,255,0.08),transparent_55%),linear-gradient(180deg,rgba(255,255,255,0.05),rgba(255,255,255,0.02))]">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-white/55" />
|
||||
<div className="h-2 w-20 rounded-full bg-white/10" />
|
||||
<div className="h-2 w-14 rounded-full bg-white/10" />
|
||||
</div>
|
||||
) : (
|
||||
<img
|
||||
src={image.imageUrl}
|
||||
alt={image.label}
|
||||
className="h-full w-full object-cover"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
onError={(event) => {
|
||||
event.currentTarget.style.display = "none";
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="absolute inset-x-0 top-0 flex items-center justify-between px-2 py-2">
|
||||
<span className="rounded-full bg-black/60 px-2 py-1 text-[11px] text-white">
|
||||
{image.group === "fixed" ? "固定角度" : "其他图片"}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"flex h-6 w-6 items-center justify-center rounded-full border text-white transition-colors",
|
||||
selected
|
||||
? isDark
|
||||
? "border-emerald-400 bg-emerald-400 text-black"
|
||||
: "border-primary bg-primary"
|
||||
: "border-white/60 bg-black/35 opacity-0 group-hover:opacity-100"
|
||||
)}
|
||||
>
|
||||
<Check className="h-3.5 w-3.5" />
|
||||
{isPlaceholder ? "加载中" : image.group === "fixed" ? "固定角度" : "其他图片"}
|
||||
</span>
|
||||
{!isPlaceholder ? (
|
||||
<span
|
||||
className={cn(
|
||||
"flex h-6 w-6 items-center justify-center rounded-full border text-white transition-colors",
|
||||
selected
|
||||
? isDark
|
||||
? "border-emerald-400 bg-emerald-400 text-black"
|
||||
: "border-primary bg-primary"
|
||||
: "border-white/60 bg-black/35 opacity-0 group-hover:opacity-100"
|
||||
)}
|
||||
>
|
||||
<Check className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/75 via-black/25 to-transparent px-3 py-2 text-[11px] text-white">
|
||||
{image.label}
|
||||
{isPlaceholder ? "白底图加载中..." : image.label}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1 px-3 py-2">
|
||||
<div className={cn("truncate text-xs font-medium", isDark ? "text-white/88" : "")}>
|
||||
{image.fileName}
|
||||
{isPlaceholder ? "正在获取 SKU 图片..." : image.fileName}
|
||||
</div>
|
||||
<div className={cn("text-[11px]", isDark ? "text-white/60" : "text-muted-foreground")}>
|
||||
点击{selected ? "取消" : "选择"},可多选带入生图模式
|
||||
{isPlaceholder
|
||||
? "已先展示预览卡位,图片会逐步加载出来。"
|
||||
: `点击${selected ? "取消" : "选择"},可多选带入生图模式`}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@ -7,6 +7,7 @@ import { createEmptyAgentWorkflowMaterialSelection } from "@/lib/agents/workflow
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { AgentWorkflowInputs } from "@/components/agents/AgentWorkflowPanel";
|
||||
import type {
|
||||
AgentWorkflowMaterialSearchResult,
|
||||
AgentWorkflowMaterialQueryType,
|
||||
AgentWorkflowMaterialSelections,
|
||||
} from "@/types/agent";
|
||||
@ -34,6 +35,38 @@ export function AgentWorkflowMaterialSelectionPanel({
|
||||
disabledHint = "请先在下方选择一个生图模式,再启用 SKU / SPU 搜图。",
|
||||
theme = "light",
|
||||
}: AgentWorkflowMaterialSelectionPanelProps) {
|
||||
const buildLoadingMaterialResult = (params: {
|
||||
keyword: string;
|
||||
currentResult?: AgentWorkflowMaterialSearchResult | null;
|
||||
placeholderCount: number;
|
||||
}): AgentWorkflowMaterialSearchResult => {
|
||||
const count = Math.max(4, Math.min(params.placeholderCount || 8, 16));
|
||||
return {
|
||||
query: params.keyword,
|
||||
queryType: "sku",
|
||||
matchedSku: params.keyword,
|
||||
matchedSpu: params.currentResult?.matchedSpu || null,
|
||||
materialId: params.currentResult?.materialId || null,
|
||||
productModel: params.currentResult?.productModel || null,
|
||||
zipUrl: null,
|
||||
fixedCount: count,
|
||||
otherCount: 0,
|
||||
totalCount: count,
|
||||
skuOptions: [],
|
||||
images: Array.from({ length: count }, (_, index) => ({
|
||||
id: `loading-${params.keyword}-${index + 1}`,
|
||||
key: `loading-${params.keyword}-${index + 1}`,
|
||||
label: `加载中 ${index + 1}`,
|
||||
fileName: `正在加载白底图 ${index + 1}`,
|
||||
imageUrl: "",
|
||||
assetId: null,
|
||||
mimeType: null,
|
||||
group: "fixed",
|
||||
sortOrder: index,
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
||||
const [materialSearchState, setMaterialSearchState] = useState<{
|
||||
primary: { isLoading: boolean; errorText: string | null };
|
||||
secondary: { isLoading: boolean; errorText: string | null };
|
||||
@ -186,10 +219,25 @@ export function AgentWorkflowMaterialSelectionPanel({
|
||||
void handleSearchMaterials("primary");
|
||||
}}
|
||||
onSkuOptionSelect={(skuName) => {
|
||||
const selectedOption = materials.primary.result?.skuOptions?.find(
|
||||
(option) => option.skuName === skuName
|
||||
);
|
||||
const placeholderCount = selectedOption?.whiteBackgroundCount || 8;
|
||||
onInputsChange({
|
||||
...inputs,
|
||||
primarySku: skuName,
|
||||
});
|
||||
onMaterialsChange({
|
||||
...materials,
|
||||
primary: {
|
||||
result: buildLoadingMaterialResult({
|
||||
keyword: skuName,
|
||||
currentResult: materials.primary.result,
|
||||
placeholderCount,
|
||||
}),
|
||||
selectedImageIds: [],
|
||||
},
|
||||
});
|
||||
setMaterialSearchMode((prev) => ({
|
||||
...prev,
|
||||
primary: "sku",
|
||||
@ -240,10 +288,25 @@ export function AgentWorkflowMaterialSelectionPanel({
|
||||
void handleSearchMaterials("secondary");
|
||||
}}
|
||||
onSkuOptionSelect={(skuName) => {
|
||||
const selectedOption = materials.secondary.result?.skuOptions?.find(
|
||||
(option) => option.skuName === skuName
|
||||
);
|
||||
const placeholderCount = selectedOption?.whiteBackgroundCount || 8;
|
||||
onInputsChange({
|
||||
...inputs,
|
||||
secondarySku: skuName,
|
||||
});
|
||||
onMaterialsChange({
|
||||
...materials,
|
||||
secondary: {
|
||||
result: buildLoadingMaterialResult({
|
||||
keyword: skuName,
|
||||
currentResult: materials.secondary.result,
|
||||
placeholderCount,
|
||||
}),
|
||||
selectedImageIds: [],
|
||||
},
|
||||
});
|
||||
setMaterialSearchMode((prev) => ({
|
||||
...prev,
|
||||
secondary: "sku",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user