From e616541ee1582834bafa7757bc04ef52b0ef0309 Mon Sep 17 00:00:00 2001 From: Ida Dittrich Date: Wed, 20 Aug 2025 12:21:54 +0200 Subject: [PATCH] added functionality for workflow selection across platform --- .../Dashboard/DashboardChat/DashboardChat.tsx | 29 +-- .../DashboardChat/DashboardChatArea.tsx | 23 ++- .../DashboardChat/DashboardChatAreaInput.tsx | 158 ++++++++++++-- .../DashboardChatAreaInput.module.css | 52 +++++ .../DashboardChat/useWorkflowManager.ts | 121 +++++++---- src/locales/de.ts | 9 + src/locales/en.ts | 9 + src/locales/fr.ts | 9 + src/pages/Home/Dashboard.tsx | 194 +++++++++++++++--- 9 files changed, 492 insertions(+), 112 deletions(-) diff --git a/src/components/Dashboard/DashboardChat/DashboardChat.tsx b/src/components/Dashboard/DashboardChat/DashboardChat.tsx index cb3691d..8ce12c3 100644 --- a/src/components/Dashboard/DashboardChat/DashboardChat.tsx +++ b/src/components/Dashboard/DashboardChat/DashboardChat.tsx @@ -1,8 +1,7 @@ -import React, { useState } from "react"; +import React from "react"; import { Prompt } from "../../../hooks/usePrompts"; -import { useLanguage } from '../../../contexts/LanguageContext'; -import DashboardChatArea from './DashboardChatArea'; +import DashboardChatArea from './DashboardChatArea.tsx'; import styles from './DashboardChatAreaStyles/DashboardChat.module.css'; @@ -13,7 +12,7 @@ interface DashboardChatProps { onPromptUsed?: () => void; onWorkflowIdChange?: (workflowId: string | null) => void; onWorkflowCompletedChange?: (completed: boolean) => void; - onWorkflowResume?: (workflowId: string) => void; + currentWorkflowId?: string | null; } const DashboardChat: React.FC = ({ @@ -21,23 +20,11 @@ const DashboardChat: React.FC = ({ onPromptUsed, onWorkflowIdChange, onWorkflowCompletedChange, - onWorkflowResume + currentWorkflowId }) => { - const { t } = useLanguage(); - const [resumeWorkflowId, setResumeWorkflowId] = useState(null); - - const handleWorkflowResume = (workflowId: string) => { - // Switch to Chat Area tab first - setResumeWorkflowId(workflowId); - // Then call the parent's resume handler - if (onWorkflowResume) { - onWorkflowResume(workflowId); - } - // Clear the resume ID after a short delay to allow processing - setTimeout(() => { - setResumeWorkflowId(null); - }, 100); - }; + // Pass the current workflow ID directly to the chat area + // This handles both dropdown selection and workflow resume scenarios + const effectiveWorkflowId = currentWorkflowId; return (
@@ -46,7 +33,7 @@ const DashboardChat: React.FC = ({ onPromptUsed={onPromptUsed} onWorkflowIdChange={onWorkflowIdChange} onWorkflowCompletedChange={onWorkflowCompletedChange} - resumeWorkflowId={resumeWorkflowId} + resumeWorkflowId={effectiveWorkflowId} />
); diff --git a/src/components/Dashboard/DashboardChat/DashboardChatArea.tsx b/src/components/Dashboard/DashboardChat/DashboardChatArea.tsx index da5c9b2..c93e05f 100644 --- a/src/components/Dashboard/DashboardChat/DashboardChatArea.tsx +++ b/src/components/Dashboard/DashboardChat/DashboardChatArea.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useRef } from "react"; import MessageList from "./DashboardChatAreaMessageList"; import FilePreview from "./DashboardChatAreaFilePreview"; import InputArea from "./DashboardChatAreaInput"; @@ -20,14 +20,23 @@ const DashboardChatArea: React.FC = ({ const [selectedFile, setSelectedFile] = useState(null); const [attachedFiles, setAttachedFiles] = useState([]); + // Track the last resumeWorkflowId to prevent feedback loops + const lastResumeWorkflowIdRef = useRef(resumeWorkflowId); + // Centralized workflow management const [workflowState, workflowActions] = useWorkflowManager(resumeWorkflowId); - // Notify parent when workflow ID changes + // Only notify parent when a NEW workflow is created internally (not when selecting existing ones) + // For workflow selection via dropdown, the Dashboard already manages the currentWorkflowId React.useEffect(() => { - if (onWorkflowIdChange && workflowState.currentWorkflowId !== resumeWorkflowId) { + // Only notify when a workflow is created from scratch (currentWorkflowId exists but resumeWorkflowId was null) + if (onWorkflowIdChange && workflowState.currentWorkflowId && !resumeWorkflowId && !lastResumeWorkflowIdRef.current) { + console.log(`🔔 Notifying parent of NEW workflow created: ${workflowState.currentWorkflowId}`); onWorkflowIdChange(workflowState.currentWorkflowId); } + + // Update the ref to track the current resumeWorkflowId + lastResumeWorkflowIdRef.current = resumeWorkflowId; }, [workflowState.currentWorkflowId, onWorkflowIdChange, resumeWorkflowId]); // Notify parent when workflow is completed @@ -38,13 +47,7 @@ const DashboardChatArea: React.FC = ({ } }, [workflowState.workflow?.status, onWorkflowCompletedChange]); - // Auto-load workflow when resumeWorkflowId changes externally - React.useEffect(() => { - if (resumeWorkflowId && resumeWorkflowId !== workflowState.currentWorkflowId) { - console.log(`🔄 Loading workflow from external prop: ${resumeWorkflowId}`); - workflowActions.loadWorkflow(resumeWorkflowId); - } - }, [resumeWorkflowId, workflowState.currentWorkflowId, workflowActions]); + // Note: useWorkflowManager handles resumeWorkflowId changes automatically // No resizing functionality needed diff --git a/src/components/Dashboard/DashboardChat/DashboardChatAreaInput.tsx b/src/components/Dashboard/DashboardChat/DashboardChatAreaInput.tsx index ce1eb5b..4fe2288 100644 --- a/src/components/Dashboard/DashboardChat/DashboardChatAreaInput.tsx +++ b/src/components/Dashboard/DashboardChat/DashboardChatAreaInput.tsx @@ -39,7 +39,9 @@ const InputArea: React.FC = ({ const [isSending, setIsSending] = useState(false); const [sendError, setSendError] = useState(null); const [isFocused, setIsFocused] = useState(false); + const [isDragOver, setIsDragOver] = useState(false); const textareaRef = useRef(null); + const dropZoneRef = useRef(null); // Always use external attached files from parent component const currentAttachedFiles = externalAttachedFiles; @@ -119,6 +121,25 @@ const InputArea: React.FC = ({ } }; + const handleStop = async () => { + setIsSending(true); + setSendError(null); + + try { + console.log('🛑 Stopping workflow...'); + const success = await workflowActions.stopWorkflow(); + + if (!success) { + setSendError('Failed to stop workflow. Please try again.'); + } + } catch (error: any) { + console.error('Failed to stop workflow:', error); + setSendError(error.message || 'Failed to stop workflow. Please try again.'); + } finally { + setIsSending(false); + } + }; + const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); @@ -133,10 +154,73 @@ const InputArea: React.FC = ({ } }; + // Drag and drop handlers + const handleDragEnter = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (e.dataTransfer.types.includes('Files')) { + setIsDragOver(true); + } + }; + + const handleDragLeave = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + // Only hide drag over if we're leaving the drop zone entirely + if (dropZoneRef.current && !dropZoneRef.current.contains(e.relatedTarget as Node)) { + setIsDragOver(false); + } + }; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + // Set the drop effect to copy + e.dataTransfer.dropEffect = 'copy'; + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setIsDragOver(false); + + if (shouldShowStopButton) { + // Don't allow file drops when workflow is active + return; + } + + const files = Array.from(e.dataTransfer.files); + if (files.length > 0) { + // Convert File objects to AttachedFile format + const attachedFiles: AttachedFile[] = files.map((file, index) => ({ + id: Date.now() + index, // Simple ID generation + name: file.name, + size: file.size, + type: file.type, + fileData: file, + objectUrl: URL.createObjectURL(file) + })); + + // Add to existing attached files + const updatedFiles = [...currentAttachedFiles, ...attachedFiles]; + if (onAttachedFilesChange) { + onAttachedFilesChange(updatedFiles); + } + } + }; + + // Check if workflow is in an active state where we should show stop button const isWorkflowActive = workflowState.workflow && ['running', 'processing', 'started'].includes(workflowState.workflow.status); + + + + // Use polling as an indicator that workflow might be active + const shouldShowStopButton = isWorkflowActive || (workflowState.isPolling && workflowState.currentWorkflowId); + + // Determine if label should be in focused/moved state const shouldLabelBeFocused = isFocused || inputValue.trim().length > 0; @@ -147,7 +231,14 @@ const InputArea: React.FC = ({ : t('chat.input.enter_message'); return ( -
+
{/* Error messages */} {(sendError || workflowState.error) && ( @@ -156,6 +247,23 @@ const InputArea: React.FC = ({
)} + {/* Drag and drop overlay */} + {isDragOver && ( +
+
+
+ {shouldShowStopButton ? '🚫' : '📁'} +
+
+ {shouldShowStopButton + ? t('chat.input.drop_disabled') + : t('chat.input.drop_files_here') + } +
+
+
+ )} + {/* Show attached files count */} {currentAttachedFiles.length > 0 && (
@@ -175,14 +283,13 @@ const InputArea: React.FC = ({ value={inputValue} onChange={(e) => { setInputValue(e.target.value); - // Trigger resize on next frame to ensure DOM is updated setTimeout(adjustTextareaHeight, 0); }} onKeyPress={handleKeyPress} onFocus={() => setIsFocused(true)} onBlur={() => setIsFocused(false)} placeholder="" - disabled={isSending || isWorkflowActive} + disabled={isSending || shouldShowStopButton} className={styles.message_textarea} rows={4} /> @@ -191,27 +298,40 @@ const InputArea: React.FC = ({
- + {shouldShowStopButton ? ( + + ) : ( + + )} - {workflowState.currentWorkflowId && !isWorkflowActive && ( + {workflowState.currentWorkflowId && !shouldShowStopButton && ( + + ) : ( +
+ + + {isDropdownOpen && !workflowsLoading && !workflowsError && ( +
+
+ {t('dashboard.workflow_dropdown.available_workflows')} +
+ + {workflows.length === 0 ? ( +
+ {t('dashboard.workflow_dropdown.no_workflows')} +
+ ) : ( + workflows.map((workflow) => ( + + )) + )} +
+ )}
)} - {currentWorkflowId && ( - - )}
@@ -78,7 +226,7 @@ function Dashboard () { selectedPrompt={selectedPrompt} onPromptUsed={() => setSelectedPrompt(null)} onWorkflowIdChange={handleWorkflowIdChange} - onWorkflowResume={handleWorkflowResume} + currentWorkflowId={currentWorkflowId} />