diff --git a/frontend/src/core/components/tools/shared/createToolFlow.tsx b/frontend/src/core/components/tools/shared/createToolFlow.tsx index 5e42e73ab..4ddaae67c 100644 --- a/frontend/src/core/components/tools/shared/createToolFlow.tsx +++ b/frontend/src/core/components/tools/shared/createToolFlow.tsx @@ -87,6 +87,7 @@ export function createToolFlow(config: ToolFlowConfig steps.create(stepConfig.title, { isVisible: stepConfig.isVisible, + isCollapsed: stepConfig.isCollapsed, onCollapsedClick: stepConfig.onCollapsedClick, tooltip: stepConfig.tooltip }, stepConfig.content) diff --git a/frontend/src/core/hooks/tools/shared/useBaseTool.ts b/frontend/src/core/hooks/tools/shared/useBaseTool.ts index aad6556ea..1b8db3bd1 100644 --- a/frontend/src/core/hooks/tools/shared/useBaseTool.ts +++ b/frontend/src/core/hooks/tools/shared/useBaseTool.ts @@ -47,6 +47,10 @@ export function useBaseTool(''); + // Tool-specific hooks const params = useParams(); const operation = useOperation(); @@ -54,19 +58,45 @@ export function useBaseTool= minFiles; + const hasResults = operation.files.length > 0 || operation.downloadUrl !== null; + const settingsCollapsed = !hasFiles || hasResults; + // Reset results when parameters change useEffect(() => { operation.resetResults(); onPreviewFile?.(null); }, [params.parameters]); - // Reset results when selected files change + // When operation completes, flag the next selection change to skip reset + // (consumeFiles auto-selects outputs immediately after processing) useEffect(() => { - if (selectedFiles.length > 0) { - operation.resetResults(); - onPreviewFile?.(null); + if (hasResults) { + skipNextSelectionResetRef.current = true; } - }, [selectedFiles.length]); + }, [hasResults]); + + // Reset results when user manually changes file selection + useEffect(() => { + if (selectedFiles.length === 0) return; + + const currentSelection = selectedFiles.map(f => f.fileId).sort().join(','); + + if (currentSelection === previousSelectionRef.current) return; // No change + + // Skip reset if this is the auto-selection after operation completed + if (skipNextSelectionResetRef.current) { + skipNextSelectionResetRef.current = false; + previousSelectionRef.current = currentSelection; + return; + } + + // User manually selected different files - reset results + previousSelectionRef.current = currentSelection; + operation.resetResults(); + onPreviewFile?.(null); + }, [selectedFiles]); // Reset parameters when transitioning from 0 files to at least 1 file useEffect(() => { @@ -101,6 +131,7 @@ export function useBaseTool { + skipNextSelectionResetRef.current = false; operation.resetResults(); onPreviewFile?.(null); }, [operation, onPreviewFile]); @@ -110,11 +141,6 @@ export function useBaseTool= minFiles; - const hasResults = operation.files.length > 0 || operation.downloadUrl !== null; - const settingsCollapsed = !hasFiles || hasResults; - return { // File management selectedFiles, diff --git a/frontend/src/core/tools/Convert.tsx b/frontend/src/core/tools/Convert.tsx index 51353cad4..82852f36d 100644 --- a/frontend/src/core/tools/Convert.tsx +++ b/frontend/src/core/tools/Convert.tsx @@ -23,6 +23,10 @@ const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled(convertParams.getEndpointName()); + // Prevent reset immediately after operation completes (when consumeFiles auto-selects outputs) + const skipNextSelectionResetRef = useRef(false); + const previousSelectionRef = useRef(''); + const scrollToBottom = () => { if (scrollContainerRef.current) { scrollContainerRef.current.scrollTo({ @@ -33,24 +37,49 @@ const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { }; const hasFiles = selectedFiles.length > 0; - const hasResults = convertOperation.downloadUrl !== null; + const hasResults = convertOperation.files.length > 0 || convertOperation.downloadUrl !== null; const settingsCollapsed = hasResults; + // When operation completes, flag the next selection change to skip reset useEffect(() => { + if (hasResults) { + skipNextSelectionResetRef.current = true; + } + }, [hasResults]); + + // Reset results when user manually changes file selection + useEffect(() => { + const currentSelection = selectedFiles.map(f => f.fileId).sort().join(','); + + if (currentSelection === previousSelectionRef.current) return; // No change + + // Skip reset if this is the auto-selection after operation completed + // Don't analyze file types - would change parameters and trigger another reset + if (skipNextSelectionResetRef.current) { + skipNextSelectionResetRef.current = false; + previousSelectionRef.current = currentSelection; + return; + } + + // User manually selected different files if (selectedFiles.length > 0) { + previousSelectionRef.current = currentSelection; convertParams.analyzeFileTypes(selectedFiles); + if (hasResults) { + convertOperation.resetResults(); + onPreviewFile?.(null); + } } else { - // Only reset when there are no active files at all - // If there are active files but no selected files, keep current format (user filtered by format) + previousSelectionRef.current = ''; if (activeFiles.length === 0) { convertParams.resetParameters(); } } - }, [selectedFiles, activeFiles, convertParams.analyzeFileTypes, convertParams.resetParameters]); + }, [selectedFiles]); useEffect(() => { - // Only clear results if we're not currently processing and parameters changed - if (!convertOperation.isLoading) { + // Reset when user changes conversion parameters (but not during operation) + if (!convertOperation.isLoading && !skipNextSelectionResetRef.current) { convertOperation.resetResults(); onPreviewFile?.(null); } @@ -87,6 +116,7 @@ const Convert = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { }; const handleSettingsReset = () => { + skipNextSelectionResetRef.current = false; convertOperation.resetResults(); onPreviewFile?.(null); };