feat: limit workflow material selection by mode
This commit is contained in:
parent
2ae376bdfd
commit
c608882f9f
@ -105,6 +105,7 @@ import {
|
||||
} from "@/lib/agents/generation-project-state";
|
||||
import { agentProjectService } from "@/services/agent-projects";
|
||||
import type {
|
||||
AgentPreset,
|
||||
AgentGenerationProjectWorkspaceState,
|
||||
AgentModeWorkspaceState,
|
||||
AgentWorkflowComposerContext,
|
||||
@ -1354,6 +1355,36 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
workflowMaterials,
|
||||
workflowSelections,
|
||||
]);
|
||||
const { data: selectedAgentPresetResponse } = useSWR<{
|
||||
success?: boolean;
|
||||
data?: AgentPreset;
|
||||
}>(selectedAgentPresetId ? `/api/agents/${selectedAgentPresetId}` : null, fetcher);
|
||||
const selectedAgentPresetData = useMemo(
|
||||
() =>
|
||||
selectedAgentPresetResponse?.success && selectedAgentPresetResponse.data
|
||||
? selectedAgentPresetResponse.data
|
||||
: null,
|
||||
[selectedAgentPresetResponse]
|
||||
);
|
||||
const selectedAgentPresetModeConfig = useMemo(
|
||||
() => normalizeAgentPresetModeConfig(selectedAgentPresetData?.defaultParams),
|
||||
[selectedAgentPresetData?.defaultParams]
|
||||
);
|
||||
const materialProductSelectionLimit = useMemo(() => {
|
||||
if (selectedAgentPresetData?.type !== "image") {
|
||||
return null;
|
||||
}
|
||||
if (selectedAgentPresetModeConfig.modeType === "scene_generation") {
|
||||
return selectedAgentPresetModeConfig.scene.productImageSlotCount;
|
||||
}
|
||||
if (selectedAgentPresetModeConfig.modeType === "detail_replacement") {
|
||||
return selectedAgentPresetModeConfig.detail.productImageSlotCount;
|
||||
}
|
||||
if (selectedAgentPresetModeConfig.modeType === "sku_replacement") {
|
||||
return selectedAgentPresetModeConfig.sku.productImageSlotCount;
|
||||
}
|
||||
return 2;
|
||||
}, [selectedAgentPresetData?.type, selectedAgentPresetModeConfig]);
|
||||
const workflowProgress = useMemo(
|
||||
() =>
|
||||
getAgentWorkflowProgress({
|
||||
@ -1535,6 +1566,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
onInputsChange={setWorkflowInputs}
|
||||
materials={workflowMaterials}
|
||||
onMaterialsChange={setWorkflowMaterials}
|
||||
productSelectionLimit={materialProductSelectionLimit}
|
||||
disabled={!selectedAgentPresetId}
|
||||
theme="dark"
|
||||
/>
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { Check, ImageIcon, Loader2, RefreshCw, Search } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { cn } from "@/lib/utils";
|
||||
@ -24,6 +25,8 @@ interface AgentWorkflowMaterialPickerProps {
|
||||
errorText?: string | null;
|
||||
result?: AgentWorkflowMaterialSearchResult | null;
|
||||
selectedImageIds: string[];
|
||||
selectionLimit?: number | null;
|
||||
selectionLimitLabel?: string;
|
||||
onValueChange: (value: string) => void;
|
||||
onQueryModeChange: (value: AgentWorkflowMaterialQueryType) => void;
|
||||
onSearch: () => void;
|
||||
@ -46,6 +49,8 @@ export function AgentWorkflowMaterialPicker({
|
||||
errorText,
|
||||
result,
|
||||
selectedImageIds,
|
||||
selectionLimit = null,
|
||||
selectionLimitLabel = "当前生图模式",
|
||||
onValueChange,
|
||||
onQueryModeChange,
|
||||
onSearch,
|
||||
@ -61,26 +66,41 @@ export function AgentWorkflowMaterialPicker({
|
||||
const selectedCount = result?.images
|
||||
? result.images.filter((item) => selectedSet.has(item.id)).length
|
||||
: 0;
|
||||
const resolvedSelectionLimit =
|
||||
typeof selectionLimit === "number" && Number.isFinite(selectionLimit) && selectionLimit > 0
|
||||
? Math.max(1, Math.round(selectionLimit))
|
||||
: null;
|
||||
const hasSelectableImages = Boolean(
|
||||
result?.images?.some((item) => Boolean(String(item.imageUrl || "").trim()))
|
||||
);
|
||||
const remainingSelectableCount =
|
||||
resolvedSelectionLimit === null ? null : Math.max(resolvedSelectionLimit - selectedCount, 0);
|
||||
|
||||
const handleToggleImage = (imageId: string) => {
|
||||
const next = new Set(selectedImageIds);
|
||||
if (next.has(imageId)) {
|
||||
next.delete(imageId);
|
||||
} else {
|
||||
if (resolvedSelectionLimit !== null && next.size >= resolvedSelectionLimit) {
|
||||
toast.error(`${selectionLimitLabel}最多可选择 ${resolvedSelectionLimit} 张图片`);
|
||||
return;
|
||||
}
|
||||
next.add(imageId);
|
||||
}
|
||||
onSelectedImageIdsChange(Array.from(next));
|
||||
};
|
||||
|
||||
const handleSelectAll = () => {
|
||||
onSelectedImageIdsChange(
|
||||
const allIds =
|
||||
result?.images
|
||||
?.filter((item) => Boolean(String(item.imageUrl || "").trim()))
|
||||
.map((item) => item.id) || []
|
||||
);
|
||||
.map((item) => item.id) || [];
|
||||
const nextIds =
|
||||
resolvedSelectionLimit !== null ? allIds.slice(0, resolvedSelectionLimit) : allIds;
|
||||
onSelectedImageIdsChange(nextIds);
|
||||
if (resolvedSelectionLimit !== null && allIds.length > resolvedSelectionLimit) {
|
||||
toast.message(`${selectionLimitLabel}最多可选择 ${resolvedSelectionLimit} 张图片,已按上限选取`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@ -168,6 +188,19 @@ export function AgentWorkflowMaterialPicker({
|
||||
{searchNote}
|
||||
</div>
|
||||
) : null}
|
||||
{resolvedSelectionLimit !== null ? (
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-[20px] border px-3 py-2 text-xs",
|
||||
isDark
|
||||
? "border-emerald-500/18 bg-emerald-500/[0.08] text-emerald-100"
|
||||
: "border-emerald-200 bg-emerald-50 text-emerald-700"
|
||||
)}
|
||||
>
|
||||
{selectionLimitLabel}最多可选择 {resolvedSelectionLimit} 张图片
|
||||
{remainingSelectableCount !== null ? `,当前还可选择 ${remainingSelectableCount} 张。` : "。"}
|
||||
</div>
|
||||
) : null}
|
||||
{disabled && disabledHint ? (
|
||||
<div
|
||||
className={cn(
|
||||
@ -235,6 +268,16 @@ export function AgentWorkflowMaterialPicker({
|
||||
>
|
||||
已选 {selectedCount} 张
|
||||
</span>
|
||||
{resolvedSelectionLimit !== null ? (
|
||||
<span
|
||||
className={cn(
|
||||
"rounded-full px-2 py-1",
|
||||
isDark ? "bg-emerald-500/[0.12] text-emerald-200" : "bg-emerald-50 text-emerald-700"
|
||||
)}
|
||||
>
|
||||
最多 {resolvedSelectionLimit} 张
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
|
||||
@ -17,6 +17,7 @@ interface AgentWorkflowMaterialSelectionPanelProps {
|
||||
onInputsChange: (next: AgentWorkflowInputs) => void;
|
||||
materials: AgentWorkflowMaterialSelections;
|
||||
onMaterialsChange: (next: AgentWorkflowMaterialSelections) => void;
|
||||
productSelectionLimit?: number | null;
|
||||
title?: string;
|
||||
description?: string;
|
||||
disabled?: boolean;
|
||||
@ -29,6 +30,7 @@ export function AgentWorkflowMaterialSelectionPanel({
|
||||
onInputsChange,
|
||||
materials,
|
||||
onMaterialsChange,
|
||||
productSelectionLimit = null,
|
||||
title,
|
||||
description,
|
||||
disabled = false,
|
||||
@ -89,6 +91,16 @@ export function AgentWorkflowMaterialSelectionPanel({
|
||||
const [isSecondaryMaterialVisible, setIsSecondaryMaterialVisible] = useState(
|
||||
hasSecondaryMaterialContent
|
||||
);
|
||||
const primarySelectedCount = materials.primary.selectedImageIds.length;
|
||||
const secondarySelectedCount = materials.secondary.selectedImageIds.length;
|
||||
const primarySelectionLimit =
|
||||
typeof productSelectionLimit === "number" && Number.isFinite(productSelectionLimit) && productSelectionLimit > 0
|
||||
? Math.max(productSelectionLimit - secondarySelectedCount, primarySelectedCount, 0)
|
||||
: null;
|
||||
const secondarySelectionLimit =
|
||||
typeof productSelectionLimit === "number" && Number.isFinite(productSelectionLimit) && productSelectionLimit > 0
|
||||
? Math.max(productSelectionLimit - primarySelectedCount, secondarySelectedCount, 0)
|
||||
: null;
|
||||
|
||||
useEffect(() => {
|
||||
if (hasSecondaryMaterialContent) {
|
||||
@ -226,6 +238,12 @@ export function AgentWorkflowMaterialSelectionPanel({
|
||||
errorText={materialSearchState.primary.errorText}
|
||||
result={materials.primary.result}
|
||||
selectedImageIds={materials.primary.selectedImageIds}
|
||||
selectionLimit={primarySelectionLimit}
|
||||
selectionLimitLabel={
|
||||
typeof productSelectionLimit === "number" && Number.isFinite(productSelectionLimit) && productSelectionLimit > 0
|
||||
? `当前模式产品图总上限 ${productSelectionLimit} 张`
|
||||
: undefined
|
||||
}
|
||||
onValueChange={(value) => handleMaterialInputChange("primary", value)}
|
||||
onQueryModeChange={(nextMode) =>
|
||||
setMaterialSearchMode((prev) => ({
|
||||
@ -294,6 +312,12 @@ export function AgentWorkflowMaterialSelectionPanel({
|
||||
errorText={materialSearchState.secondary.errorText}
|
||||
result={materials.secondary.result}
|
||||
selectedImageIds={materials.secondary.selectedImageIds}
|
||||
selectionLimit={secondarySelectionLimit}
|
||||
selectionLimitLabel={
|
||||
typeof productSelectionLimit === "number" && Number.isFinite(productSelectionLimit) && productSelectionLimit > 0
|
||||
? `当前模式产品图总上限 ${productSelectionLimit} 张`
|
||||
: undefined
|
||||
}
|
||||
onValueChange={(value) => handleMaterialInputChange("secondary", value)}
|
||||
onQueryModeChange={(nextMode) =>
|
||||
setMaterialSearchMode((prev) => ({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user