diff --git a/app/canvas/[id]/page.tsx b/app/canvas/[id]/page.tsx index b8834441f..1d77b83b8 100644 --- a/app/canvas/[id]/page.tsx +++ b/app/canvas/[id]/page.tsx @@ -334,6 +334,8 @@ export default function CanvasEditorPage({ params }: PageProps) { const [agentProjectThumbnail, setAgentProjectThumbnail] = useState(null); const [agentProjectResultCollections, setAgentProjectResultCollections] = useState({}); + const [agentProjectDeletedResultCollections, setAgentProjectDeletedResultCollections] = + useState({}); const [isAgentProjectLoading, setIsAgentProjectLoading] = useState(false); const [isAgentProjectSaving, setIsAgentProjectSaving] = useState(false); const [isAgentProjectSheetOpen, setIsAgentProjectSheetOpen] = useState(false); @@ -509,6 +511,7 @@ export default function CanvasEditorPage({ params }: PageProps) { setAgentProjectTitle("未命名生图项目"); setAgentProjectThumbnail(null); setAgentProjectResultCollections({}); + setAgentProjectDeletedResultCollections({}); // 🆕 先清空当前画布状态,确保切换时不会显示旧数据 useCanvasStore.getState().clearCanvas(); @@ -658,6 +661,17 @@ export default function CanvasEditorPage({ params }: PageProps) { agentProjectResultCollections.sku_replacement ), }, + deletedResultCollections: { + scene_generation: normalizeResultCollectionUrls( + agentProjectDeletedResultCollections.scene_generation + ), + detail_replacement: normalizeResultCollectionUrls( + agentProjectDeletedResultCollections.detail_replacement + ), + sku_replacement: normalizeResultCollectionUrls( + agentProjectDeletedResultCollections.sku_replacement + ), + }, resultImageUrls: Array.from( new Set([ ...normalizeResultCollectionUrls(agentProjectResultCollections.scene_generation), @@ -668,6 +682,7 @@ export default function CanvasEditorPage({ params }: PageProps) { }; }, [ agentModeState, + agentProjectDeletedResultCollections, agentProjectResultCollections, agentWorkspaceState, currentAgentStage, @@ -885,6 +900,9 @@ export default function CanvasEditorPage({ params }: PageProps) { setAgentProjectTitle(result.data.title || "未命名生图项目"); setAgentProjectThumbnail(result.data.thumbnail || null); setAgentProjectResultCollections(persistedState.resultCollections || {}); + setAgentProjectDeletedResultCollections( + persistedState.deletedResultCollections || {} + ); } catch (error) { if (!cancelled) { toast.error(error instanceof Error ? error.message : "加载生图项目失败"); @@ -1117,6 +1135,48 @@ export default function CanvasEditorPage({ params }: PageProps) { [agentProjectResultCollections] ); + const handleAgentResultImageDelete = useCallback( + (modeType: AgentGenerationResultModeType, imageUrl: string) => { + const normalizedUrl = String(imageUrl || "").trim(); + if (!/^https?:\/\//i.test(normalizedUrl)) { + return; + } + + const nextCollections: AgentGenerationProjectResultCollections = { + ...agentProjectResultCollections, + [modeType]: normalizeResultCollectionUrls( + (agentProjectResultCollections[modeType] || []).filter( + (url) => url !== normalizedUrl + ) + ), + }; + const nextDeletedCollections: AgentGenerationProjectResultCollections = { + ...agentProjectDeletedResultCollections, + [modeType]: normalizeResultCollectionUrls([ + ...(agentProjectDeletedResultCollections[modeType] || []), + normalizedUrl, + ]), + }; + + setAgentProjectResultCollections((current) => + areResultCollectionsEqual(current, nextCollections) ? current : nextCollections + ); + setAgentProjectDeletedResultCollections((current) => + areResultCollectionsEqual(current, nextDeletedCollections) + ? current + : nextDeletedCollections + ); + setAgentProjectThumbnail((current) => { + const nextThumbnail = + current === normalizedUrl + ? getFirstResultUrlFromCollections(nextCollections) + : current; + return current === nextThumbnail ? current : nextThumbnail; + }); + }, + [agentProjectDeletedResultCollections, agentProjectResultCollections] + ); + useEffect(() => { const normalizedBrand = workflowInputs.brand.trim().toLowerCase(); if (!normalizedBrand) { @@ -1795,8 +1855,10 @@ export default function CanvasEditorPage({ params }: PageProps) { onAgentModeStateChange={setAgentModeState} workspaceState={agentWorkspaceState} resultCollections={agentProjectResultCollections} + deletedResultCollections={agentProjectDeletedResultCollections} onWorkspaceStateChange={setAgentWorkspaceState} onResultPreviewUrlsChange={handleAgentResultPreviewUrlsChange} + onDeleteResultImage={handleAgentResultImageDelete} siteName={siteSettingsData?.siteName} siteLogo={siteSettingsData?.siteLogo} siteLogoDark={siteSettingsData?.siteLogoDark} diff --git a/components/agents/AgentGenerationWorkspaceDarkV2.tsx b/components/agents/AgentGenerationWorkspaceDarkV2.tsx index 81fea7aca..abadf10be 100644 --- a/components/agents/AgentGenerationWorkspaceDarkV2.tsx +++ b/components/agents/AgentGenerationWorkspaceDarkV2.tsx @@ -16,6 +16,7 @@ import { Sparkles, Upload, UserRound, + Trash2, X, Wand2, Settings2, @@ -93,10 +94,15 @@ interface AgentGenerationWorkspaceDarkV2Props { workspaceState?: AgentGenerationProjectWorkspaceState | null; onWorkspaceStateChange?: (next: AgentGenerationProjectWorkspaceState) => void; resultCollections?: AgentGenerationProjectResultCollections | null; + deletedResultCollections?: AgentGenerationProjectResultCollections | null; onResultPreviewUrlsChange?: ( modeType: AgentGenerationResultModeType, urls: string[] ) => void; + onDeleteResultImage?: ( + modeType: AgentGenerationResultModeType, + imageUrl: string + ) => void; topNavRightSlot?: ReactNode; showcaseConfig?: AgentWorkflowShowcaseConfig | null; workflowStylePanel?: ReactNode; @@ -145,6 +151,14 @@ function normalizeResultCollectionUrls(urls?: string[] | null) { ); } +function excludeDeletedResultUrls( + urls: string[] | undefined | null, + deletedUrls: string[] | undefined | null +) { + const deletedUrlSet = new Set(normalizeResultCollectionUrls(deletedUrls)); + return normalizeResultCollectionUrls(urls).filter((url) => !deletedUrlSet.has(url)); +} + function getResultModeLabel(modeType: AgentGenerationResultModeType) { return modeType === "detail_replacement" ? "详情替换" @@ -677,8 +691,10 @@ export function AgentGenerationWorkspaceDarkV2({ onOpenCanvasEditor, workspaceState = null, resultCollections = null, + deletedResultCollections = null, onWorkspaceStateChange, onResultPreviewUrlsChange, + onDeleteResultImage, topNavRightSlot, showcaseConfig, workflowStylePanel, @@ -759,6 +775,20 @@ export function AgentGenerationWorkspaceDarkV2({ }); const [pendingGeneration, setPendingGeneration] = useState(null); + const normalizedDeletedResultCollections = useMemo( + () => ({ + scene_generation: normalizeResultCollectionUrls( + deletedResultCollections?.scene_generation + ), + detail_replacement: normalizeResultCollectionUrls( + deletedResultCollections?.detail_replacement + ), + sku_replacement: normalizeResultCollectionUrls( + deletedResultCollections?.sku_replacement + ), + }), + [deletedResultCollections] + ); useEffect(() => { const restored = getStoredAgentPendingGenerations().find( @@ -1310,8 +1340,20 @@ export function AgentGenerationWorkspaceDarkV2({ }, [assistantMessagesAfterLastUser, generatedGroups]); const allGeneratedImages = useMemo( - () => Array.from(new Set(generatedGroups.flatMap((group) => group.imageUrls))), - [generatedGroups] + () => + Array.from( + new Set( + generatedGroups + .flatMap((group) => group.imageUrls) + .filter( + (url) => + !normalizedDeletedResultCollections.scene_generation.includes(url) && + !normalizedDeletedResultCollections.detail_replacement.includes(url) && + !normalizedDeletedResultCollections.sku_replacement.includes(url) + ) + ) + ), + [generatedGroups, normalizedDeletedResultCollections] ); const currentSceneGeneratedUrls = useMemo( () => Array.from(new Set(generatedGroupsAfterLastUser.flatMap((group) => group.imageUrls))), @@ -1357,8 +1399,11 @@ export function AgentGenerationWorkspaceDarkV2({ () => latestReplacementResults .map((item) => item.url) - .filter((url): url is string => Boolean(url)), - [latestReplacementResults] + .filter((url): url is string => Boolean(url)) + .filter( + (url) => !normalizedDeletedResultCollections[currentResultModeType].includes(url) + ), + [currentResultModeType, latestReplacementResults, normalizedDeletedResultCollections] ); const currentLiveResultUrls = isReplacementPreset ? latestReplacementResultUrls @@ -1400,9 +1445,18 @@ export function AgentGenerationWorkspaceDarkV2({ const mergedResultCollections = useMemo( () => { const baseCollections = { - scene_generation: normalizeResultCollectionUrls(resultCollections?.scene_generation), - detail_replacement: normalizeResultCollectionUrls(resultCollections?.detail_replacement), - sku_replacement: normalizeResultCollectionUrls(resultCollections?.sku_replacement), + scene_generation: excludeDeletedResultUrls( + resultCollections?.scene_generation, + normalizedDeletedResultCollections.scene_generation + ), + detail_replacement: excludeDeletedResultUrls( + resultCollections?.detail_replacement, + normalizedDeletedResultCollections.detail_replacement + ), + sku_replacement: excludeDeletedResultUrls( + resultCollections?.sku_replacement, + normalizedDeletedResultCollections.sku_replacement + ), }; return { @@ -1410,12 +1464,20 @@ export function AgentGenerationWorkspaceDarkV2({ [currentResultModeType]: Array.from( new Set([ ...baseCollections[currentResultModeType], - ...normalizeResultCollectionUrls(currentLiveResultUrls), + ...excludeDeletedResultUrls( + currentLiveResultUrls, + normalizedDeletedResultCollections[currentResultModeType] + ), ]) ), } as Record; }, - [currentLiveResultUrls, currentResultModeType, resultCollections] + [ + currentLiveResultUrls, + currentResultModeType, + normalizedDeletedResultCollections, + resultCollections, + ] ); const availableResultModeTypes = useMemo( () => @@ -1434,43 +1496,61 @@ export function AgentGenerationWorkspaceDarkV2({ () => detailReplacementReferenceCards.map((card, index) => { const resultMeta = latestReplacementResults[index]; - const resultUrl = resultMeta?.url || null; + const resultUrl = + resultMeta?.url && + !normalizedDeletedResultCollections.detail_replacement.includes(resultMeta.url) + ? resultMeta.url + : null; return { ...card, index, resultUrl, - resultText: resultMeta?.text || "", + resultText: resultUrl ? resultMeta?.text || "" : "", status: getReplacementCardStatus({ index, resultUrl, - resultText: resultMeta?.text || "", + resultText: resultUrl ? resultMeta?.text || "" : "", generatedCount: latestReplacementResults.length, isGenerating: isDetailResultModeGenerating, }), }; }), - [detailReplacementReferenceCards, isDetailResultModeGenerating, latestReplacementResults] + [ + detailReplacementReferenceCards, + isDetailResultModeGenerating, + latestReplacementResults, + normalizedDeletedResultCollections.detail_replacement, + ] ); const skuResultCards = useMemo( () => skuReplacementReferenceCards.map((card, index) => { const resultMeta = latestReplacementResults[index]; - const resultUrl = resultMeta?.url || null; + const resultUrl = + resultMeta?.url && + !normalizedDeletedResultCollections.sku_replacement.includes(resultMeta.url) + ? resultMeta.url + : null; return { ...card, index, resultUrl, - resultText: resultMeta?.text || "", + resultText: resultUrl ? resultMeta?.text || "" : "", status: getReplacementCardStatus({ index, resultUrl, - resultText: resultMeta?.text || "", + resultText: resultUrl ? resultMeta?.text || "" : "", generatedCount: latestReplacementResults.length, isGenerating: isSkuResultModeGenerating, }), }; }), - [isSkuResultModeGenerating, latestReplacementResults, skuReplacementReferenceCards] + [ + isSkuResultModeGenerating, + latestReplacementResults, + normalizedDeletedResultCollections.sku_replacement, + skuReplacementReferenceCards, + ] ); const detailProcessedCount = useMemo( () => @@ -1719,10 +1799,7 @@ export function AgentGenerationWorkspaceDarkV2({ return; } - const hasProgress = - pendingGeneration.modeType === "scene_generation" - ? currentSceneGeneratedUrls.length > 0 - : latestReplacementResults.length > 0; + const hasProgress = latestReplacementResults.length > 0; if (!hasProgress) { setPendingGeneration(null); @@ -2090,6 +2167,25 @@ export function AgentGenerationWorkspaceDarkV2({ toast.message("已打开原图,可右键另存为"); } }, []); + const handleDeleteResultImage = useCallback( + (modeType: AgentGenerationResultModeType, imageUrl: string) => { + const normalizedUrl = String(imageUrl || "").trim(); + if (!normalizedUrl || !onDeleteResultImage) { + return; + } + + if (selectedResultUrl === normalizedUrl) { + setSelectedResultUrl(null); + } + if (fullscreenPreview === normalizedUrl) { + setFullscreenPreview(null); + } + + onDeleteResultImage(modeType, normalizedUrl); + toast.success("结果图已删除"); + }, + [fullscreenPreview, onDeleteResultImage, selectedResultUrl] + ); const handleToolStepChange = useCallback( (nextKey: ToolStepKey) => { @@ -3370,6 +3466,16 @@ export function AgentGenerationWorkspaceDarkV2({ 当前联动 ) : null} + + ) : (