-
File Preview
+
{!selectedFile && (
diff --git a/src/components/Dashboard/DashboardChat/DashboardChatAreaInput.tsx b/src/components/Dashboard/DashboardChat/DashboardChatAreaInput.tsx
index 6202509..ce1eb5b 100644
--- a/src/components/Dashboard/DashboardChat/DashboardChatAreaInput.tsx
+++ b/src/components/Dashboard/DashboardChat/DashboardChatAreaInput.tsx
@@ -1,187 +1,243 @@
-import React, { useState, useEffect } from 'react';
-import { useWorkflowOperations } from '../../../hooks/useWorkflows';
+import React, { useState, useEffect, useRef } from 'react';
import { Prompt } from '../../../hooks/usePrompts';
import FileAttachmentPopup from './FileAttachmentPopup';
+import { WorkflowManagerState, WorkflowManagerActions } from './useWorkflowManager';
+import { useLanguage } from '../../../contexts/LanguageContext';
+
+import styles from './DashboardChatAreaStyles/DashboardChatAreaInput.module.css';
+import sharedStyles from './DashboardChatAreaStyles/DashboardChat.module.css';
interface InputAreaProps {
- selectedPrompt?: Prompt | null;
- onPromptUsed?: () => void;
- onWorkflowIdChange?: (workflowId: string | null) => void;
- onAttachedFilesChange?: (files: AttachedFile[]) => void;
- attachedFiles?: AttachedFile[];
+ selectedPrompt?: Prompt | null;
+ onPromptUsed?: () => void;
+ workflowState: WorkflowManagerState;
+ workflowActions: WorkflowManagerActions;
+ onAttachedFilesChange?: (files: AttachedFile[]) => void;
+ attachedFiles?: AttachedFile[];
}
interface AttachedFile {
- id: number;
- name: string;
- size: number;
- type: string;
- fileData?: File;
- objectUrl?: string;
+ id: number;
+ name: string;
+ size: number;
+ type: string;
+ fileData?: File;
+ objectUrl?: string;
}
const InputArea: React.FC = ({
- selectedPrompt,
- onPromptUsed,
- onWorkflowIdChange,
- onAttachedFilesChange,
- attachedFiles: externalAttachedFiles = []
+ selectedPrompt,
+ onPromptUsed,
+ workflowState,
+ workflowActions,
+ onAttachedFilesChange,
+ attachedFiles: externalAttachedFiles = []
}) => {
- const [inputValue, setInputValue] = useState('');
- const [showFilePopup, setShowFilePopup] = useState(false);
+ const { t } = useLanguage();
+ const [inputValue, setInputValue] = useState('');
+ const [showFilePopup, setShowFilePopup] = useState(false);
+ const [isSending, setIsSending] = useState(false);
+ const [sendError, setSendError] = useState(null);
+ const [isFocused, setIsFocused] = useState(false);
+ const textareaRef = useRef(null);
- // Always use external attached files from parent component
- const currentAttachedFiles = externalAttachedFiles;
- const { startWorkflow, startingWorkflow, startError } = useWorkflowOperations();
+ // Always use external attached files from parent component
+ const currentAttachedFiles = externalAttachedFiles;
- // Auto-fill input when prompt is selected
- useEffect(() => {
- if (selectedPrompt) {
- setInputValue(selectedPrompt.content);
- }
- }, [selectedPrompt]);
+ // Auto-resize textarea function
+ const adjustTextareaHeight = () => {
+ const textarea = textareaRef.current;
+ if (!textarea) return;
- const handleSend = async () => {
- if (!inputValue.trim() || startingWorkflow) return;
+ // Reset height to auto to get the actual scroll height
+ textarea.style.height = 'auto';
+
+ // Calculate the height based on content
+ const scrollHeight = textarea.scrollHeight;
+ const lineHeight = 1.5 * 14; // 1.5em * 14px font size
+ const padding = 32; // 16px top + 16px bottom padding
+ const minHeight = lineHeight * 4 + padding; // 4 rows
+ const maxHeight = lineHeight * 8 + padding; // 8 rows
+
+ // Set height within constraints
+ const newHeight = Math.min(Math.max(scrollHeight, minHeight), maxHeight);
+ textarea.style.height = `${newHeight}px`;
+ };
- try {
- const result = await startWorkflow({
- prompt: inputValue,
- listFileId: currentAttachedFiles.map(f => f.id)
- });
+ // Auto-fill input when prompt is selected
+ useEffect(() => {
+ if (selectedPrompt) {
+ setInputValue(selectedPrompt.content);
+ }
+ }, [selectedPrompt]);
- if (result.success) {
- setInputValue('');
- if (onAttachedFilesChange) {
- onAttachedFilesChange([]);
- }
- if (onPromptUsed) onPromptUsed();
- if (onWorkflowIdChange && result.data?.id) {
- onWorkflowIdChange(result.data.id);
- }
- }
- } catch (error) {
- console.error('Failed to start workflow:', error);
- }
- };
+ // Adjust height when input value changes
+ useEffect(() => {
+ adjustTextareaHeight();
+ }, [inputValue]);
- const handleKeyPress = (e: React.KeyboardEvent) => {
- if (e.key === 'Enter' && !e.shiftKey) {
- e.preventDefault();
- handleSend();
- }
- };
+ // Initial resize on mount
+ useEffect(() => {
+ adjustTextareaHeight();
+ }, []);
- const handleFilesAttached = (files: AttachedFile[]) => {
- setShowFilePopup(false);
+ const handleSend = async () => {
+ if (!inputValue.trim() || isSending) return;
+
+ setIsSending(true);
+ setSendError(null);
+
+ try {
+ const fileIds = currentAttachedFiles.map(f => f.id);
+ let success = false;
+
+ if (workflowState.currentWorkflowId) {
+ // Continue existing workflow
+ console.log(`➡️ Continuing workflow ${workflowState.currentWorkflowId}`);
+ success = await workflowActions.continueWorkflow(inputValue, fileIds);
+ } else {
+ // Start new workflow
+ console.log('🚀 Starting new workflow');
+ const newWorkflowId = await workflowActions.startNewWorkflow(inputValue, fileIds);
+ success = !!newWorkflowId;
+ }
+
+ if (success) {
+ setInputValue('');
if (onAttachedFilesChange) {
- onAttachedFilesChange(files);
+ onAttachedFilesChange([]);
}
- };
+ if (onPromptUsed) onPromptUsed();
+ } else {
+ setSendError('Failed to send message. Please try again.');
+ }
+ } catch (error: any) {
+ console.error('Failed to send message:', error);
+ setSendError(error.message || 'Failed to send message. Please try again.');
+ } finally {
+ setIsSending(false);
+ }
+ };
- const formatFileSize = (bytes: number): string => {
- if (bytes < 1024) return bytes + ' B';
- if (bytes < 1024 * 1024) return Math.round(bytes / 1024) + ' KB';
- return Math.round(bytes / (1024 * 1024)) + ' MB';
- };
+ const handleKeyPress = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ handleSend();
+ }
+ };
- return (
-
-
Input
-
- {startError && (
-
- Error: {startError}
-
- )}
+ const handleFilesAttached = (files: AttachedFile[]) => {
+ setShowFilePopup(false);
+ if (onAttachedFilesChange) {
+ onAttachedFilesChange(files);
+ }
+ };
- {/* Show attached files count */}
- {currentAttachedFiles.length > 0 && (
-
- 📎 {currentAttachedFiles.length} file{currentAttachedFiles.length !== 1 ? 's' : ''} attached
-
- )}
-
- {/* File Attachment Popup */}
- {showFilePopup && (
-
setShowFilePopup(false)}
- onFilesSelected={handleFilesAttached}
- currentAttachedFiles={currentAttachedFiles}
- />
- )}
+ const isWorkflowActive = workflowState.workflow &&
+ ['running', 'processing', 'started'].includes(workflowState.workflow.status);
+
+ // Determine if label should be in focused/moved state
+ const shouldLabelBeFocused = isFocused || inputValue.trim().length > 0;
+
+ // Get placeholder text
+ const placeholderText = workflowState.currentWorkflowId
+ ? t('chat.input.continue_workflow')
+ : t('chat.input.enter_message');
+
+ return (
+
+
+ {/* Error messages */}
+ {(sendError || workflowState.error) && (
+
+ {t('chat.input.error_prefix')} {sendError || workflowState.error}
- );
+ )}
+
+ {/* Show attached files count */}
+ {currentAttachedFiles.length > 0 && (
+
+ {currentAttachedFiles.length} {currentAttachedFiles.length !== 1 ? t('chat.input.files_attached_plural') : t('chat.input.files_attached')} {t('chat.input.files_attached_label')}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ {workflowState.currentWorkflowId && !isWorkflowActive && (
+
+ )}
+
+ {selectedPrompt && (
+
+ {t('chat.input.using_prompt')} {selectedPrompt.name}
+
+ )}
+
+
+
+ {/* File Attachment Popup */}
+ {showFilePopup && (
+
setShowFilePopup(false)}
+ onFilesSelected={handleFilesAttached}
+ currentAttachedFiles={currentAttachedFiles}
+ />
+ )}
+
+ );
};
export default InputArea;
diff --git a/src/components/Dashboard/DashboardChat/DashboardChatAreaMessageItem.tsx b/src/components/Dashboard/DashboardChat/DashboardChatAreaMessageItem.tsx
index 9f3ee3b..8ee37ab 100644
--- a/src/components/Dashboard/DashboardChat/DashboardChatAreaMessageItem.tsx
+++ b/src/components/Dashboard/DashboardChat/DashboardChatAreaMessageItem.tsx
@@ -69,6 +69,9 @@ const MessageItem: React.FC = ({ message, index, onFilePreview
console.log(`🎭 MessageItem rendering:`, {
messageId: message.id,
messageRole: message.role,
+ content: message.content?.substring(0, 50) + (message.content?.length > 50 ? '...' : ''),
+ contentLength: message.content?.length || 0,
+ hasContent: !!message.content,
hasDocuments: !!(message.documents),
documentsArray: message.documents,
documentsLength: message.documents?.length || 0,
@@ -171,7 +174,14 @@ const MessageItem: React.FC = ({ message, index, onFilePreview
lineHeight: '1.5',
whiteSpace: 'pre-wrap'
}}>
- {message.content}
+ {message.content || (
+
+ [No message content]
+
+ )}
{hasDocuments && (
diff --git a/src/components/Dashboard/DashboardChat/DashboardChatAreaMessageList.tsx b/src/components/Dashboard/DashboardChat/DashboardChatAreaMessageList.tsx
index bac92ed..f5d7d9a 100644
--- a/src/components/Dashboard/DashboardChat/DashboardChatAreaMessageList.tsx
+++ b/src/components/Dashboard/DashboardChat/DashboardChatAreaMessageList.tsx
@@ -1,255 +1,348 @@
-import React, { useEffect, useRef, useState, useCallback } from 'react';
-import { useWorkflowStatus } from '../../../hooks/useWorkflows';
-import { Prompt } from '../../../hooks/usePrompts';
+import React from 'react';
import { useApiRequest } from '../../../hooks/useApi';
import MessageItem from './DashboardChatAreaMessageItem';
-import { Message, Document, WorkflowMessage } from './dashboardChatAreaTypes';
+import { WorkflowMessage, Document } from './dashboardChatAreaTypes';
+import { WorkflowManagerState } from './useWorkflowManager';
interface MessageListProps {
- selectedPrompt?: Prompt | null;
- onPromptUsed?: () => void;
- resumeWorkflowId?: string | null;
- onFilePreview?: (file: any) => void;
+ workflowState: WorkflowManagerState;
+ onFilePreview?: (file: any) => void;
}
-// Custom hook to fetch and transform messages like the old code
-const useTransformedMessages = (workflowId: string | null) => {
- const [messages, setMessages] = useState([]);
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState(null);
- const { request } = useApiRequest();
+// Helper function to transform WorkflowMessage to display Message
+const transformWorkflowMessage = async (workflowMessage: WorkflowMessage, request: any): Promise => {
+ let documents: Document[] = [];
- const fetchMessages = useCallback(async () => {
- if (!workflowId) {
- setMessages([]);
- return;
- }
+ // Fetch file metadata if fileIds exist
+ if (workflowMessage.fileIds && workflowMessage.fileIds.length > 0) {
+ console.log(`📎 Processing ${workflowMessage.fileIds.length} files for message ${workflowMessage.id}`);
+
+ const documentPromises = workflowMessage.fileIds.map(async (fileId, fileIndex) => {
+ try {
+ console.log(`📁 Fetching metadata for file ${fileIndex + 1}/${workflowMessage.fileIds!.length}: ${fileId}`);
+ const response = await request({
+ url: `/api/workflows/files/${fileId}/preview`,
+ method: 'get'
+ });
+
+ const document: Document = {
+ id: fileId.toString(),
+ fileId: fileId,
+ name: response.name || response.fileName || `File_${fileId}`,
+ ext: response.extension || response.ext || (response.name ? response.name.split('.').pop() : 'txt'),
+ type: response.mimeType || response.type || 'application/octet-stream',
+ size: response.size || 0,
+ downloadUrl: response.downloadUrl || response.url
+ };
- setLoading(true);
- setError(null);
+ console.log(`✅ File ${fileId} metadata processed:`, document.name);
+ return document;
+ } catch (error) {
+ console.error(`❌ Failed to fetch metadata for file ${fileId}:`, error);
+ // Return a fallback object for failed requests
+ return {
+ id: fileId.toString(),
+ fileId: fileId,
+ name: `File_${fileId}`,
+ ext: 'unknown',
+ type: 'application/octet-stream',
+ size: 0
+ };
+ }
+ });
- try {
- console.log(`🔍 Fetching messages for workflow: ${workflowId}`);
-
- // Fetch workflow messages
- const workflowMessages: WorkflowMessage[] = await request({
- url: `/api/workflows/${workflowId}/messages`,
- method: 'get'
- });
+ documents = await Promise.all(documentPromises);
+ }
- console.log(`📨 Received ${workflowMessages.length} messages from API:`, workflowMessages);
+ // Try different possible field names for content
+ const possibleContent = workflowMessage.content ||
+ (workflowMessage as any).message ||
+ (workflowMessage as any).text ||
+ (workflowMessage as any).body ||
+ '';
- // Debug each message structure
- workflowMessages.forEach((msg, index) => {
- console.log(`📄 Message ${index + 1}:`, {
- id: msg.id,
- role: msg.role,
- content: msg.content?.substring(0, 50) + '...',
- fileIds: msg.fileIds,
- hasFileIds: !!(msg.fileIds && msg.fileIds.length > 0),
- fileIdsLength: msg.fileIds?.length || 0
- });
- });
+ const transformedMessage = {
+ id: workflowMessage.id,
+ role: workflowMessage.role,
+ agentName: workflowMessage.role === 'user' ? 'You' : 'Assistant',
+ content: possibleContent,
+ timestamp: workflowMessage.timestamp,
+ documents: documents
+ };
- // Transform each message
- const transformedMessages = await Promise.all(
- workflowMessages.map(async (workflowMessage: WorkflowMessage, msgIndex) => {
- console.log(`🔄 Transforming message ${msgIndex + 1} (${workflowMessage.id})`);
- let documents: Document[] = [];
+ console.log(`🔄 Transformation result for ${workflowMessage.id}:`, {
+ originalMessage: workflowMessage,
+ originalContent: workflowMessage.content,
+ originalContentType: typeof workflowMessage.content,
+ possibleContent: possibleContent,
+ possibleContentType: typeof possibleContent,
+ transformedContent: transformedMessage.content,
+ transformedContentType: typeof transformedMessage.content,
+ allOriginalKeys: Object.keys(workflowMessage),
+ // Check for alternative field names
+ alternativeFields: {
+ message: (workflowMessage as any).message,
+ text: (workflowMessage as any).text,
+ body: (workflowMessage as any).body,
+ data: (workflowMessage as any).data
+ }
+ });
- // Fetch file metadata if fileIds exist
- if (workflowMessage.fileIds && workflowMessage.fileIds.length > 0) {
- console.log(`📎 Message ${workflowMessage.id} has ${workflowMessage.fileIds.length} fileIds:`, workflowMessage.fileIds);
-
- const documentPromises = workflowMessage.fileIds.map(async (fileId, fileIndex) => {
- try {
- console.log(`📁 Fetching metadata for file ${fileIndex + 1}/${workflowMessage.fileIds!.length}: ${fileId}`);
- const response = await request({
- url: `/api/workflows/files/${fileId}/preview`,
- method: 'get'
- });
-
- console.log(`✅ File ${fileId} metadata received:`, response);
-
- const document: Document = {
- id: fileId.toString(),
- fileId: fileId,
- name: response.name || response.fileName || `File_${fileId}`,
- ext: response.extension || response.ext || (response.name ? response.name.split('.').pop() : 'txt'),
- type: response.mimeType || response.type || 'application/octet-stream',
- size: response.size || 0,
- downloadUrl: response.downloadUrl || response.url
- };
-
- console.log(`🗂️ Created document object:`, document);
- return document;
- } catch (error) {
- console.error(`❌ Failed to fetch metadata for file ${fileId}:`, error);
- // Return a fallback object for failed requests
- const fallbackDoc: Document = {
- id: fileId.toString(),
- fileId: fileId,
- name: `File_${fileId}`,
- ext: 'unknown',
- type: 'application/octet-stream',
- size: 0
- };
- console.log(`🔧 Created fallback document:`, fallbackDoc);
- return fallbackDoc;
- }
- });
-
- documents = await Promise.all(documentPromises);
- console.log(`📋 All files processed for message ${workflowMessage.id}. Total documents: ${documents.length}`);
- } else {
- console.log(`📭 Message ${workflowMessage.id} has no fileIds`);
- }
-
- // Transform to old Message format
- const message: Message = {
- id: workflowMessage.id,
- role: workflowMessage.role,
- agentName: workflowMessage.role === 'user' ? 'You' : 'Assistant',
- content: workflowMessage.content,
- timestamp: workflowMessage.timestamp,
- documents: documents
- };
-
- console.log(`✨ Final transformed message:`, {
- id: message.id,
- role: message.role,
- documentsCount: message.documents?.length || 0,
- hasDocuments: !!(message.documents && message.documents.length > 0)
- });
-
- return message;
- })
- );
-
- console.log(`🎉 Successfully transformed all ${transformedMessages.length} messages`);
- console.log(`📊 Summary:`, transformedMessages.map(msg => ({
- id: msg.id,
- role: msg.role,
- documentsCount: msg.documents?.length || 0
- })));
-
- setMessages(transformedMessages);
- } catch (err: any) {
- console.error('💥 Error fetching messages:', err);
- setError(err.message || 'Failed to fetch messages');
- } finally {
- setLoading(false);
- }
- }, [workflowId, request]);
-
- useEffect(() => {
- fetchMessages();
- }, [fetchMessages]);
-
- return { messages, loading, error, refetch: fetchMessages };
+ return transformedMessage;
};
const MessageList: React.FC = ({
- selectedPrompt,
- onPromptUsed,
- resumeWorkflowId,
- onFilePreview
+ workflowState,
+ onFilePreview
}) => {
- const { messages, loading, error, refetch } = useTransformedMessages(resumeWorkflowId || null);
- const { status } = useWorkflowStatus(resumeWorkflowId || null);
- const intervalRef = useRef(null);
- const [isInitialLoad, setIsInitialLoad] = React.useState(true);
+ const { request } = useApiRequest();
+ const [transformedMessages, setTransformedMessages] = React.useState([]);
+ const [isTransforming, setIsTransforming] = React.useState(false);
+ const scrollContainerRef = React.useRef(null);
+ const [isUserScrolledUp, setIsUserScrolledUp] = React.useState(false);
+ const lastMessageCountRef = React.useRef(0);
- // Auto-refresh messages every 3 seconds when workflow is active
- useEffect(() => {
- if (resumeWorkflowId && status?.status &&
- (status.status === 'running' || status.status === 'processing' || status.status === 'started')) {
- intervalRef.current = window.setInterval(() => {
- console.log('🔄 Auto-refreshing messages due to active workflow');
- refetch();
- }, 3000);
- } else {
- // Stop polling when completed, failed, or no workflow
- if (intervalRef.current) {
- clearInterval(intervalRef.current);
- intervalRef.current = null;
- }
- }
+ // Transform messages when workflow messages change
+ React.useEffect(() => {
+ const transformMessages = async () => {
+ if (!workflowState.messages || workflowState.messages.length === 0) {
+ setTransformedMessages([]);
+ return;
+ }
- return () => {
- if (intervalRef.current) {
- clearInterval(intervalRef.current);
- }
- };
- }, [resumeWorkflowId, status?.status, refetch]);
+ setIsTransforming(true);
+ console.log(`🔄 Transforming ${workflowState.messages.length} workflow messages...`);
- // Initial load when workflow ID changes
- useEffect(() => {
- if (resumeWorkflowId) {
- console.log(`🚀 Starting initial load for workflow: ${resumeWorkflowId}`);
- setIsInitialLoad(true);
- refetch().finally(() => setIsInitialLoad(false));
- } else {
- setIsInitialLoad(false);
- }
- }, [resumeWorkflowId, refetch]);
-
- return (
-
-
Messages
+ try {
+ const transformed = await Promise.all(
+ workflowState.messages.map(async (msg: WorkflowMessage, index: number) => {
+ console.log(`🔄 Transforming message ${index + 1}/${workflowState.messages.length}: ${msg.id}`);
+ console.log(`📝 RAW API Message (${msg.role}):`, {
+ id: msg.id,
+ rawMessage: msg,
+ contentType: typeof msg.content,
+ contentValue: msg.content,
+ contentLength: msg.content?.length || 0,
+ hasContent: !!msg.content,
+ contentPreview: msg.content?.substring(0, 100) + (msg.content?.length > 100 ? '...' : ''),
+ fileCount: msg.fileIds?.length || 0,
+ allKeys: Object.keys(msg)
+ });
- {error &&
Error: {error}
}
-
- {status && (
-
- Status: {status.status}
- {status.currentRound && ` (Round ${status.currentRound})`}
- {/* Show a small indicator when polling for updates */}
- {intervalRef.current && (
-
- 🔄 Live updates
-
- )}
-
- )}
+ return await transformWorkflowMessage(msg, request);
+ })
+ );
-
- {messages.map((message, index) => {
- console.log(`🎨 Rendering message ${message.id} with ${message.documents?.length || 0} documents`);
- return (
-
- );
- })}
-
+ console.log(`✅ Successfully transformed ${transformed.length} messages`);
+ setTransformedMessages(transformed);
+ } catch (error) {
+ console.error('❌ Error transforming messages:', error);
+ setTransformedMessages([]);
+ } finally {
+ setIsTransforming(false);
+ }
+ };
- {loading && (
-
- )}
+ transformMessages();
+ }, [workflowState.messages, request]);
- {messages.length === 0 && !isInitialLoad && !loading && (
-
- No messages yet. Start a workflow to see messages here.
-
- )}
+ // Check if user is scrolled near the bottom
+ const checkScrollPosition = React.useCallback(() => {
+ const container = scrollContainerRef.current;
+ if (!container) return;
+
+ const { scrollTop, scrollHeight, clientHeight } = container;
+ const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
+
+ // Consider "near bottom" if within 100px of the bottom
+ const isNearBottom = distanceFromBottom < 100;
+ setIsUserScrolledUp(!isNearBottom);
+
+ console.log('📏 Scroll position:', {
+ scrollTop,
+ scrollHeight,
+ clientHeight,
+ distanceFromBottom,
+ isNearBottom,
+ isUserScrolledUp: !isNearBottom
+ });
+ }, []);
+
+ // Scroll to bottom function
+ const scrollToBottom = React.useCallback(() => {
+ const container = scrollContainerRef.current;
+ if (container) {
+ console.log('⬇️ Auto-scrolling to bottom');
+ container.scrollTop = container.scrollHeight;
+ }
+ }, []);
+
+ // Auto-scroll when new messages arrive (only if user is near bottom)
+ React.useEffect(() => {
+ const currentMessageCount = transformedMessages.length;
+ const hadMessages = lastMessageCountRef.current > 0;
+ const hasNewMessages = currentMessageCount > lastMessageCountRef.current;
+
+ if (hasNewMessages && hadMessages && !isUserScrolledUp) {
+ console.log('🆕 New messages detected, auto-scrolling to bottom');
+ // Small delay to ensure DOM is updated
+ setTimeout(scrollToBottom, 100);
+ }
+
+ lastMessageCountRef.current = currentMessageCount;
+ }, [transformedMessages.length, isUserScrolledUp, scrollToBottom]);
+
+ // Scroll to bottom on initial load
+ React.useEffect(() => {
+ if (transformedMessages.length > 0 && lastMessageCountRef.current === 0) {
+ console.log('📜 Initial load, scrolling to bottom');
+ setTimeout(scrollToBottom, 100);
+ }
+ }, [transformedMessages.length, scrollToBottom]);
+
+ const { currentWorkflowId, workflow, isLoading, error, isPolling } = workflowState;
+
+ return (
+ <>
+ {/* Add CSS animations */}
+
+
+ {error && (
+
+ Error: {error}
- );
+ )}
+
+ {workflow && (
+
+ Workflow: {workflow.id}
+
+ Status: {workflow.status}
+ {workflow.currentRound && ` (Round ${workflow.currentRound})`}
+ {/* Show polling indicator */}
+ {isPolling && (
+
+ 🔄 Live updates (2s)
+
+ )}
+
+ )}
+
+
+ {transformedMessages.map((message, index) => {
+ console.log(`🎨 Rendering transformed message ${message.id}:`, {
+ role: message.role,
+ contentLength: message.content?.length || 0,
+ hasContent: !!message.content,
+ documentsCount: message.documents?.length || 0
+ });
+
+ return (
+
+ );
+ })}
+
+
+ {(isLoading || isTransforming) && (
+
+
+ {isTransforming ? 'Processing messages...' : 'Loading messages...'}
+
+
+ )}
+
+ {transformedMessages.length === 0 && !isLoading && !isTransforming && !currentWorkflowId && (
+
+ No workflow selected. Start a conversation to create a new workflow.
+
+ )}
+
+ {transformedMessages.length === 0 && !isLoading && !isTransforming && currentWorkflowId && (
+
+ No messages in this workflow yet.
+
+ )}
+
+
+
+ {/* Scroll to bottom button - positioned relative to parent container, not scrollable area */}
+
+ >
+ );
};
export default MessageList;
diff --git a/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/DashboardChat.module.css b/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/DashboardChat.module.css
index 3df453d..f6777e1 100644
--- a/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/DashboardChat.module.css
+++ b/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/DashboardChat.module.css
@@ -1,16 +1,271 @@
.dashboard_chat {
display: flex;
- padding: 20px;
- flex-direction: column; /* Fixed: was 'space-between' which is invalid */
+ flex-direction: column;
align-self: stretch;
background: var(--color-bg);
position: relative;
- box-shadow: 0px 2px 6px 0px rgba(194, 194, 194, 0.10);
+ height: 100%;
+ flex: 1;
min-height: 0;
- height: 100%; /* Fill parent height */
- flex: 1; /* Take all available space from parent */
overflow: hidden;
font-family: var(--font-family);
}
+/* Grid Layout */
+.chat_grid {
+ display: grid;
+ width: 100%;
+ height: 100%;
+ grid-template-rows: 1fr auto;
+ grid-template-columns: 2fr 1fr;
+ gap: 0px;
+ overflow: hidden;
+ box-sizing: border-box;
+ position: relative;
+}
+
+.quadrant {
+ overflow: hidden;
+ background-color: var(--color-bg);
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+ min-width: 0;
+ box-sizing: border-box;
+}
+
+
+/* Quadrant specific styles */
+.messages_quadrant {
+ grid-row: 1;
+ grid-column: 1;
+}
+
+.file_preview_quadrant {
+ grid-row: 1;
+ grid-column: 2;
+}
+
+.input_quadrant {
+ grid-row: 2;
+ grid-column: 1;
+}
+
+.connected_files_quadrant {
+ grid-row: 2;
+ grid-column: 2;
+}
+
+/* Chat Messages */
+.chat_messages {
+ flex: 1;
+ padding: 15px;
+ border-radius: 15px;
+ margin-bottom: 15px;
+ min-height: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+ scroll-behavior: smooth;
+}
+
+.chat_messages::-webkit-scrollbar {
+ width: 6px;
+}
+
+.chat_messages::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.chat_messages::-webkit-scrollbar-thumb {
+ background: var(--color-gray-disabled);
+ border-radius: 3px;
+}
+
+.chat_messages::-webkit-scrollbar-thumb:hover {
+ background: var(--color-gray);
+}
+
+.messages_container {
+ min-height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+}
+
+.messages_spacer {
+ flex: 1;
+ min-height: 20px;
+}
+
+/* Chat Input */
+.chat_input {
+ width: 100%;
+ height: 40px;
+ padding: 6px 10px;
+ border: 1px solid var(--color-primary);
+ border-radius: 25px;
+ font-size: 14px;
+ font-family: var(--font-family);
+ background: var(--color-bg);
+ color: var(--color-text);
+ opacity: 0.6;
+ min-width: 120px;
+ transition: all 0.2s ease;
+ box-sizing: border-box;
+}
+
+.chat_input.drag_over {
+ background-color: var(--color-secondary-disabled);
+ border: 2px dashed var(--color-secondary);
+ border-radius: 12px;
+ padding: 8px;
+}
+
+.input_row {
+ display: flex;
+ gap: 10px;
+ align-items: flex-end;
+ width: 100%;
+}
+
+.message_input {
+ flex: 1;
+ padding: 12px 16px;
+ border: 1px solid var(--color-gray-disabled);
+ border-radius: 12px;
+ outline: none;
+ font-size: 14px;
+ font-family: var(--font-family);
+ background-color: var(--color-bg);
+ color: var(--color-text);
+}
+
+.message_input:focus {
+ border-color: var(--color-secondary);
+}
+
+.message_input:disabled {
+ background-color: var(--color-surface);
+ cursor: not-allowed;
+ opacity: 0.6;
+}
+
+.button_secondary {
+ border: 2px dashed var(--color-secondary);
+ border-radius: 30px;
+ padding: 10px 20px;
+ background: var(--color-bg);
+ cursor: pointer;
+ transition: all 0.2s ease;
+ min-width: 100px;
+ text-align: center;
+ font-family: var(--font-family);
+ color: var(--color-secondary);
+}
+
+.button_secondary:hover {
+ background-color: var(--color-secondary);
+ color: var(--color-bg);
+}
+
+.button_secondary:disabled {
+ background-color: var(--color-bg);
+ cursor: not-allowed;
+ opacity: 0.6;
+ color: var(--color-secondary);
+}
+
+.button_primary {
+ border-radius: 30px;
+ border: 1px solid var(--color-secondary);
+ background: var(--color-secondary);
+ color: var(--color-bg);
+ border: none;
+ outline: none;
+ padding: 10px 20px;
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ flex-shrink: 0;
+ transition: background-color 0.2s ease;
+ font-family: var(--font-family);
+ cursor: pointer;
+ min-width: 100px;
+ align-items: center;
+ justify-content: center;
+}
+
+.button_primary:hover {
+ background-color: var(--color-secondary-hover);
+}
+
+.button_primary:disabled {
+ background-color: var(--color-gray-disabled);
+ border: 1px solid var(--color-gray-disabled);
+ cursor: not-allowed;
+ opacity: 0.6;
+ border: 1px solid var(--color-gray-disabled);
+}
+
+
+/* Attached Files */
+.attached_files {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ width: 100%;
+ margin-bottom: 5px;
+}
+
+.attached_file {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px 8px;
+ background-color: var(--color-secondary-disabled);
+ border: 1px solid var(--color-secondary);
+ border-radius: 8px;
+ font-size: 12px;
+ color: var(--color-secondary);
+ font-family: var(--font-family);
+}
+
+.attached_file_icon {
+ font-size: 16px;
+}
+
+.attached_file_name {
+ max-width: 150px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.attached_file_remove {
+ background: none;
+ border: none;
+ color: var(--color-gray);
+ cursor: pointer;
+ padding: 0;
+ margin-left: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ transition: background-color 0.2s ease, color 0.2s ease;
+}
+
+.attached_file_remove:hover {
+ background-color: var(--color-gray-disabled);
+ color: var(--color-text);
+}
+
+/* Input Area styles moved to DashboardChatAreaInput.module.css */
+
+
+
+
+
diff --git a/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/DashboardChatArea.module.css b/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/DashboardChatArea.module.css
deleted file mode 100644
index fc9784a..0000000
--- a/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/DashboardChatArea.module.css
+++ /dev/null
@@ -1,843 +0,0 @@
-.chat_area {
- display: flex;
- flex-direction: column;
- align-items: stretch;
- flex: 1;
- height: 100%;
- overflow: hidden;
- font-family: var(--font-family);
-}
-
-/* Grid Container for Four Quadrants */
-.grid_container {
- display: grid;
- width: 100%;
- height: 100%;
- overflow: hidden;
- gap: 0;
-}
-
-/* Quadrant Base Styles */
-.quadrant {
- overflow: hidden;
- background-color: var(--color-bg);
- display: flex;
- flex-direction: column;
- min-width: 0;
- min-height: 0;
-}
-
-.quadrant_header {
- padding: 10px 15px;
- border-bottom: 1px solid var(--color-gray-disabled);
- background-color: var(--color-surface);
- flex-shrink: 0;
-}
-
-.quadrant_header h3 {
- margin: 0;
- font-size: 14px;
- font-weight: 500;
- color: var(--color-text);
-}
-
-.quadrant_content {
- flex: 1;
- padding: 15px;
- overflow-y: auto;
- overflow-x: hidden;
-}
-
-/* Specific Quadrant Styles */
-.messages_quadrant {
- border-right: 1px solid var(--color-primary);
- border-bottom: 1px solid var(--color-primary);
-}
-
-.file_preview_quadrant {
- border-bottom: 1px solid var(--color-primary);
-}
-
-.input_quadrant {
- border-right: 1px solid var(--color-primary);
-}
-
-.connected_files_quadrant {
- /* No additional borders needed */
-}
-
-/* Resizable Dividers */
-.vertical_divider {
- background-color: var(--color-primary);
- cursor: ew-resize;
- grid-row: 1 / -1;
- width: 1px;
- transition: background-color 0.2s ease;
-}
-
-.vertical_divider:hover {
- background-color: var(--color-secondary);
- width: 3px;
-}
-
-.horizontal_divider {
- background-color: var(--color-primary);
- cursor: ns-resize;
- height: 1px;
- transition: background-color 0.2s ease;
-}
-
-.horizontal_divider:hover {
- background-color: var(--color-secondary);
- height: 3px;
-}
-
-/* Messages Quadrant - Remove old styles that conflict */
-.messages_quadrant .quadrant_content {
- padding: 0;
-}
-
-/* Override for MessageList component */
-.messages_quadrant :global(.chat_messages) {
- border-radius: 0;
- margin-bottom: 0;
- height: 100%;
- padding: 15px;
-}
-
-/* Input Quadrant - Remove old styles that conflict */
-.input_quadrant .quadrant_content {
- padding: 15px;
-}
-
-.chat_messages {
- flex: 1;
- padding: 15px;
- border-radius: 15px;
- margin-bottom: 15px;
- min-height: 0;
- overflow-y: auto;
- overflow-x: hidden;
- scroll-behavior: smooth;
-}
-
-.chat_messages::-webkit-scrollbar {
- width: 6px;
-}
-
-.chat_messages::-webkit-scrollbar-track {
- background: transparent;
-}
-
-.chat_messages::-webkit-scrollbar-thumb {
- background: var(--color-gray-disabled);
- border-radius: 3px;
-}
-
-.chat_messages::-webkit-scrollbar-thumb:hover {
- background: var(--color-gray);
-}
-
-.messages_container {
- min-height: 100%;
- display: flex;
- flex-direction: column;
- justify-content: flex-start;
-
-}
-
-.messages_spacer {
- flex: 1;
- min-height: 20px;
-}
-
-.chat_input {
- display: flex;
- gap: 10px;
- align-items: flex-end;
- flex-shrink: 0;
- flex-direction: column;
- transition: all 0.2s ease;
-}
-
-.chat_input.drag_over {
- background-color: var(--color-secondary-disabled);
- border: 2px dashed var(--color-secondary);
- border-radius: 12px;
- padding: 8px;
-}
-
-.input_row {
- display: flex;
- gap: 10px;
- align-items: flex-end;
- width: 100%;
-}
-
-.message_input {
- flex: 1;
- padding: 12px 16px;
- border: 1px solid var(--color-gray-disabled);
- border-radius: 12px;
- outline: none;
- font-size: 14px;
- font-family: var(--font-family);
- background-color: var(--color-bg);
- color: var(--color-text);
-}
-
-.message_input:focus {
- border-color: var(--color-secondary);
-}
-
-.message_input:disabled {
- background-color: var(--color-surface);
- cursor: not-allowed;
- opacity: 0.6;
-}
-
-.attachment_button {
- height: 48px;
- width: 48px;
- background-color: var(--color-secondary-disabled);
- color: var(--color-secondary);
- border: none;
- border-radius: 12px;
- cursor: pointer;
- font-size: 14px;
- font-weight: 500;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: background-color 0.2s ease, border-color 0.2s ease;
- font-family: var(--font-family);
-}
-
-.attachment_button:hover {
- background-color: var(--color-secondary-hover);
- color: var(--color-bg);
-}
-
-.attachment_button:disabled {
- background-color: var(--color-surface);
- cursor: not-allowed;
- opacity: 0.6;
-}
-
-.attached_files {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- width: 100%;
- margin-bottom: 5px;
-}
-
-.attached_file {
- display: flex;
- align-items: center;
- gap: 6px;
- padding: 6px 8px;
- background-color: var(--color-secondary-disabled);
- border: 1px solid var(--color-secondary);
- border-radius: 8px;
- font-size: 12px;
- color: var(--color-secondary);
- font-family: var(--font-family);
-}
-
-.attached_file_icon {
- font-size: 16px;
-}
-
-.attached_file_name {
- max-width: 150px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.attached_file_remove {
- background: none;
- border: none;
- color: var(--color-gray);
- cursor: pointer;
- padding: 0;
- margin-left: 4px;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 16px;
- height: 16px;
- border-radius: 50%;
- transition: background-color 0.2s ease, color 0.2s ease;
-}
-
-.attached_file_remove:hover {
- background-color: var(--color-gray-disabled);
- color: var(--color-text);
-}
-
-.send_button {
- height: 48px;
- width: 48px;
- background-color: var(--color-secondary);
- color: var(--color-bg);
- border: 1px solid var(--color-secondary);
- border-radius: 12px;
- cursor: pointer;
- font-size: 14px;
- font-weight: 500;
- display: flex;
- align-items: center;
- justify-content: center;
- font-family: var(--font-family);
-}
-
-.send_button:hover {
- background-color: var(--color-secondary-hover);
-}
-
-.send_button_icon {
- height: 60%;
- width: 60%;
- margin: none;
- padding: none;
-}
-
-.send_button:disabled {
- background-color: var(--color-gray-disabled);
- cursor: not-allowed;
- opacity: 0.6;
- border: 1px solid var(--color-gray-disabled);
-}
-
-.stop_button {
- padding: 12px 12px;
- height: 48px;
- width: 48px;
- background-color: var(--color-red);
- color: var(--color-bg);
- border: none;
- border-radius: 12px;
- cursor: pointer;
- font-size: 14px;
- font-weight: 500;
- display: flex;
- align-items: center;
- justify-content: center;
- font-family: var(--font-family);
-}
-
-.stop_button:hover {
- background-color: var(--color-red-hover);
-}
-
-.stop_button:disabled {
- background-color: var(--color-gray-disabled);
- cursor: not-allowed;
- opacity: 0.6;
-}
-
-.loading_message {
- padding: 10px;
- background-color: var(--color-secondary-disabled);
- border-left: 4px solid var(--color-secondary);
- border-radius: 4px;
- margin-bottom: 10px;
-}
-
-.loading_message p {
- margin: 0;
- color: var(--color-secondary);
- font-size: 14px;
- font-family: var(--font-family);
-}
-
-.error_message {
- padding: 10px;
- background-color: var(--color-red-disabled);
- border-left: 4px solid var(--color-red);
- border-radius: 4px;
- margin-bottom: 10px;
-}
-
-.error_message p {
- margin: 0;
- color: var(--color-red);
- font-size: 14px;
- font-family: var(--font-family);
-}
-
-.message {
- margin-bottom: 15px;
- padding: 12px;
- border-radius: 12px;
- max-width: 80%;
- font-family: var(--font-family);
-}
-
-.message_user {
- background-color: var(--color-secondary);
- color: var(--color-bg);
- margin-left: auto;
- margin-right: 0;
-}
-
-.message_assistant {
- background-color: var(--color-surface);
- color: var(--color-text);
- margin-left: 0;
- margin-right: auto;
-}
-
-.message_system {
- background-color: var(--color-primary-disabled);
- color: var(--color-primary);
- margin-left: auto;
- margin-right: auto;
- text-align: center;
-}
-
-.message_role {
- font-size: 12px;
- font-weight: 600;
- margin-bottom: 4px;
- opacity: 0.8;
- font-family: var(--font-family);
-}
-
-.message_content {
- font-size: 14px;
- line-height: 1.4;
- white-space: pre-wrap;
- word-wrap: break-word;
- font-family: var(--font-family);
-}
-
-.message_timestamp {
- font-size: 11px;
- margin-top: 4px;
- opacity: 0.6;
- font-family: var(--font-family);
-}
-
-.placeholder_text {
- text-align: center;
- color: var(--color-gray);
- font-style: italic;
- margin: 20px 0;
- font-family: var(--font-family);
-}
-
-.workflow_status {
- display: flex;
- justify-content: center;
- align-items: center;
- padding: 10px;
- margin: 5px 0;
-}
-
-.workflow_status p {
- margin: 0;
- font-size: 14px;
- color: var(--color-gray);
- font-family: var(--font-family);
-}
-
-.retry_container {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 12px 16px;
- background-color: var(--color-red-disabled);
- border: 1px solid var(--color-red);
- border-radius: 12px;
-}
-
-.failed_message {
- font-size: 14px;
- color: var(--color-red);
- font-family: var(--font-family);
- font-weight: 500;
-}
-
-.retry_button {
- background-color: var(--color-primary);
- color: var(--color-bg);
- border: none;
- border-radius: 8px;
- padding: 8px 16px;
- font-size: 12px;
- font-weight: 500;
- cursor: pointer;
- font-family: var(--font-family);
- transition: background-color 0.2s ease, transform 0.2s ease;
- white-space: nowrap;
-}
-
-.retry_button:hover {
- background-color: var(--color-primary-hover);
- transform: translateY(-1px);
-}
-
-.retry_button:disabled {
- background-color: var(--color-gray-disabled);
- cursor: not-allowed;
- transform: none;
- opacity: 0.6;
-}
-
-.completion_message {
- padding: 10px 12px;
- background-color: var(--color-secondary-disabled);
- border-left: 4px solid var(--color-secondary);
- border-radius: 4px;
- margin-bottom: 10px;
- text-align: center;
-}
-
-.completion_message p {
- margin: 0 0 10px 0;
- color: var(--color-secondary);
- font-size: 14px;
- font-weight: 600;
- font-family: var(--font-family);
-}
-
-.new_workflow_button {
- background-color: var(--color-secondary);
- color: var(--color-bg);
- border: none;
- border-radius: 8px;
- padding: 8px 16px;
- font-size: 12px;
- font-weight: 500;
- cursor: pointer;
- transition: background-color 0.2s ease;
- font-family: var(--font-family);
-}
-
-.new_workflow_button:hover {
- background-color: var(--color-secondary-hover);
-}
-
-.message_documents {
- margin-top: 8px;
- display: flex;
- flex-direction: column;
- gap: 6px;
-}
-
-.document_item {
- display: flex;
- align-items: center;
- gap: 8px;
- padding: 8px 10px;
- background-color: var(--color-bg);
- border: 1px solid var(--color-gray-disabled);
- border-radius: 6px;
- cursor: pointer;
- transition: all 0.2s ease;
- font-size: 13px;
-}
-
-.document_item:hover {
- border-color: var(--color-primary);
- background-color: var(--color-surface);
-}
-
-.message_assistant .document_item {
- background-color: var(--color-bg);
- border-color: var(--color-gray-disabled);
-}
-
-.message_assistant .document_item:hover {
- border-color: var(--color-primary);
- background-color: var(--color-surface);
-}
-
-.document_icon {
- font-size: 16px;
- color: var(--color-secondary);
- flex-shrink: 0;
-
-
-}
-
-.document_info {
- flex: 1;
- min-width: 0;
- display: flex;
- flex-direction: column;
- gap: 2px;
-}
-
-.document_name {
- font-weight: 500;
- color: var(--color-text);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- font-family: var(--font-family);
-}
-
-.document_meta {
- display: flex;
- gap: 8px;
- font-size: 11px;
- color: var(--color-gray);
- font-family: var(--font-family);
-}
-
-.document_size {
- font-size: 11px;
- color: var(--color-gray);
-}
-
-.document_type {
- font-size: 11px;
- color: var(--color-gray);
-}
-
-.document_actions {
- display: flex;
- gap: 4px;
- opacity: 1;
- transition: opacity 0.2s ease;
-}
-
-.document_item:hover .document_actions {
- opacity: 1;
-}
-
-.document_action_button {
- background: none;
- border: none;
- color: var(--color-gray);
- cursor: pointer;
- padding: 4px;
- border-radius: 4px;
- font-size: 14px;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s ease;
-}
-
-.document_action_button:hover {
- background-color: var(--color-surface);
- color: var(--color-text);
-}
-
-.message_assistant .document_action_button {
- color: var(--color-gray);
-}
-
-.message_assistant .document_action_button:hover {
- background-color: var(--color-surface);
- color: var(--color-text);
-}
-
-/* File Preview Quadrant Styles */
-.empty_state {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- height: 100%;
- text-align: center;
- color: var(--color-gray);
-}
-
-.empty_state small {
- margin-top: 8px;
- font-size: 12px;
- opacity: 0.7;
-}
-
-.loading_state {
- display: flex;
- align-items: center;
- justify-content: center;
- height: 100%;
- color: var(--color-gray);
-}
-
-.file_preview {
- display: flex;
- flex-direction: column;
- gap: 15px;
- height: 100%;
-}
-
-.file_info h4 {
- margin: 0 0 8px 0;
- font-size: 16px;
- font-weight: 500;
- color: var(--color-text);
-}
-
-.file_metadata {
- display: flex;
- gap: 15px;
- font-size: 12px;
- color: var(--color-gray);
-}
-
-.preview_modes {
- display: flex;
- gap: 8px;
- border-bottom: 1px solid var(--color-gray-disabled);
- padding-bottom: 8px;
-}
-
-.mode_button {
- padding: 6px 12px;
- border: 1px solid var(--color-gray-disabled);
- background: var(--color-bg);
- color: var(--color-text);
- border-radius: 6px;
- cursor: pointer;
- font-size: 12px;
- transition: all 0.2s ease;
-}
-
-.mode_button:hover {
- background: var(--color-surface);
-}
-
-.mode_button.active {
- background: var(--color-secondary);
- color: white;
- border-color: var(--color-secondary);
-}
-
-.preview_content {
- flex: 1;
- overflow: auto;
-}
-
-.image_preview {
- max-width: 100%;
- max-height: 100%;
- object-fit: contain;
- border-radius: 8px;
-}
-
-.text_preview {
- background: var(--color-surface);
- border-radius: 8px;
- padding: 15px;
- font-family: 'Courier New', monospace;
- font-size: 12px;
- line-height: 1.4;
- overflow: auto;
-}
-
-.document_preview {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 15px;
- padding: 20px;
-}
-
-.download_link {
- display: inline-flex;
- align-items: center;
- gap: 8px;
- padding: 8px 16px;
- background: var(--color-secondary);
- color: white;
- text-decoration: none;
- border-radius: 6px;
- font-size: 14px;
- transition: background-color 0.2s ease;
-}
-
-.download_link:hover {
- background: var(--color-primary);
-}
-
-/* Connected Files Quadrant Styles */
-.files_count {
- font-size: 12px;
- color: var(--color-gray);
- margin-left: auto;
-}
-
-.files_list {
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-.file_item {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 10px;
- border: 1px solid var(--color-gray-disabled);
- border-radius: 8px;
- cursor: pointer;
- transition: all 0.2s ease;
- background: var(--color-bg);
-}
-
-.file_item:hover {
- background: var(--color-surface);
- border-color: var(--color-secondary);
-}
-
-.file_item.selected {
- background: var(--color-secondary-disabled);
- border-color: var(--color-secondary);
-}
-
-.file_icon {
- font-size: 20px;
- flex-shrink: 0;
-}
-
-.file_details {
- flex: 1;
- min-width: 0;
-}
-
-.file_name {
- font-size: 14px;
- font-weight: 500;
- color: var(--color-text);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.file_meta {
- font-size: 12px;
- color: var(--color-gray);
- margin-top: 2px;
-}
-
-.file_actions {
- display: flex;
- gap: 4px;
-}
-
-.action_button {
- width: 28px;
- height: 28px;
- border: none;
- background: transparent;
- cursor: pointer;
- border-radius: 4px;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: background-color 0.2s ease;
- font-size: 14px;
-}
-
-.action_button:hover {
- background: var(--color-gray-disabled);
-}
\ No newline at end of file
diff --git a/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/DashboardChatAreaInput.module.css b/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/DashboardChatAreaInput.module.css
new file mode 100644
index 0000000..434091a
--- /dev/null
+++ b/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/DashboardChatAreaInput.module.css
@@ -0,0 +1,137 @@
+/* Input Area Specific Styles */
+.input_area_container {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ box-sizing: border-box;
+ padding: 16px 16px 16px 0;
+ min-height: fit-content;
+}
+
+.workflow_status {
+ margin-bottom: 12px;
+ padding: 8px;
+ background-color: var(--color-surface);
+ border-radius: 4px;
+ font-size: 12px;
+ color: var(--color-gray);
+}
+
+.error_message {
+ padding: 8px;
+ background-color: var(--color-error, #ffe6e6);
+ color: var(--color-error-text, #d00);
+ border-radius: 4px;
+ margin-bottom: 12px;
+}
+
+.attached_files_count {
+ margin-bottom: 8px;
+ padding: 6px 10px;
+ background-color: var(--color-secondary-disabled);
+ border-radius: 25px;
+ font-size: 12px;
+ color: var(--color-bg);
+ text-align: center;
+}
+
+.input_form_container {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.floating_label_textarea {
+ position: relative;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.textarea_label {
+ position: absolute;
+ left: 16px;
+ top: 16px;
+ color: var(--color-text);
+ opacity: 0.6;
+ font-size: 14px;
+ pointer-events: none;
+ transition: all 0.3s ease;
+ background-color: transparent;
+ font-family: var(--font-family);
+ z-index: 1;
+}
+
+.textarea_label_focused {
+ position: absolute;
+ left: 12px;
+ top: -8px;
+ transform: translateY(0);
+ color: var(--color-secondary);
+ font-size: 12px;
+ pointer-events: none;
+ transition: all 0.3s ease;
+ background-color: var(--color-bg);
+ padding: 0 4px;
+ font-family: var(--font-family);
+ font-weight: 500;
+ z-index: 2;
+}
+
+.message_textarea {
+ resize: none;
+ width: 100%;
+ min-height: calc(1.5em * 4 + 32px); /* 4 rows + padding */
+ max-height: calc(1.5em * 8 + 32px); /* 8 rows + padding */
+ height: calc(1.5em * 4 + 32px); /* Start with 4 rows */
+
+ padding:16px;
+ border: 1px solid var(--color-primary);
+ border-radius: 25px;
+ font-size: 14px;
+ font-family: var(--font-family);
+ background: var(--color-bg);
+ color: var(--color-text);
+ opacity: 0.6;
+ transition: border-color 0.2s ease, opacity 0.2s ease, box-shadow 0.2s ease;
+ box-sizing: border-box;
+ line-height: 1.5;
+ overflow-y: auto;
+}
+
+.message_textarea:focus {
+ outline: none;
+ border-color: var(--color-secondary);
+ opacity: 1;
+ box-shadow: 0 0 0 2px rgba(var(--color-secondary), 0.1);
+}
+
+.message_textarea::placeholder {
+ color: transparent;
+}
+
+.message_textarea:disabled {
+ opacity: 0.6;
+}
+
+.input_actions_row {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+}
+
+
+.new_chat_button {
+ padding: 8px 12px;
+ background-color: var(--color-surface);
+ border: 1px solid var(--color-gray-disabled);
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 12px;
+ color: var(--color-gray);
+}
+
+.prompt_indicator {
+ font-size: 12px;
+ color: var(--color-gray);
+}
diff --git a/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/grid.css b/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/grid.css
deleted file mode 100644
index 224493d..0000000
--- a/src/components/Dashboard/DashboardChat/DashboardChatAreaStyles/grid.css
+++ /dev/null
@@ -1,68 +0,0 @@
-.chat-grid {
- display: grid;
- width: 100%;
- height: 100%;
- gap: 0;
- overflow: hidden;
-}
-
-.quadrant {
- overflow: hidden;
- background-color: var(--color-bg);
- border: 1px solid var(--color-gray-disabled);
- display: flex;
- flex-direction: column;
- min-height: 0;
- min-width: 0;
-}
-
-.divider {
- background-color: var(--color-primary);
- cursor: ns-resize;
- transition: background-color 0.2s ease;
-}
-
-.vertical-divider {
- cursor: ew-resize;
- grid-row: 1 / -1;
- grid-column: 2;
-}
-
-.horizontal-divider {
- cursor: ns-resize;
- grid-row: 2;
- grid-column: 1 / -1;
-}
-
-.divider:hover {
- background-color: var(--color-secondary);
-}
-
-/* Quadrant specific styles */
-.messages-quadrant {
- grid-row: 1;
- grid-column: 1;
- border-right: none;
- border-bottom: none;
- background-color: #e8f5e8 !important; /* Light green */
-}
-
-.file-preview-quadrant {
- grid-row: 1;
- grid-column: 3;
- border-bottom: none;
- background-color: #ffe8e8 !important; /* Light red */
-}
-
-.input-quadrant {
- grid-row: 3;
- grid-column: 1;
- border-right: none;
- background-color: #f0e8ff !important; /* Light purple */
-}
-
-.connected-files-quadrant {
- grid-row: 3;
- grid-column: 3;
- background-color: #e8f0ff !important; /* Light blue */
-}
\ No newline at end of file
diff --git a/src/components/Dashboard/DashboardChat/README.md b/src/components/Dashboard/DashboardChat/README.md
deleted file mode 100644
index 499aec7..0000000
--- a/src/components/Dashboard/DashboardChat/README.md
+++ /dev/null
@@ -1,109 +0,0 @@
-# DashboardChatArea - Modular Structure
-
-This directory contains the refactored `DashboardChatArea` component, broken down into manageable modules for better maintainability and separation of concerns.
-
-## File Structure
-
-```
-DashboardChatArea/
-├── index.ts # Main export file
-├── types.ts # TypeScript interfaces and types
-├── DashboardChatArea.tsx # Main orchestrating component
-├── useChatLogic.ts # Custom hook with all business logic
-├── MessageList.tsx # Component for displaying messages
-├── MessageItem.tsx # Individual message component
-├── ChatInput.tsx # Input field and send button component
-├── WorkflowStatusDisplay.tsx # Workflow status and completion UI
-├── DashboardChatArea.module.css # Shared styles
-└── README.md # This documentation
-```
-
-## Component Responsibilities
-
-### `DashboardChatArea.tsx` (Main Component)
-- **Purpose**: Orchestrates all child components
-- **Responsibilities**:
- - Uses the `useChatLogic` hook
- - Renders `MessageList` and `ChatInput` components
- - Passes props between components
-- **Size**: ~73 lines (reduced from 278 lines)
-
-### `useChatLogic.ts` (Custom Hook)
-- **Purpose**: Contains all business logic and state management
-- **Responsibilities**:
- - State management (input value, workflow ID, completion status)
- - Effects for polling, auto-scroll, prompt handling
- - Workflow operations (send messages, start workflows)
- - Event handlers
-- **Size**: ~196 lines
-
-### `MessageList.tsx` (Message Display)
-- **Purpose**: Handles the display of all messages and status indicators
-- **Responsibilities**:
- - Renders loading and error states
- - Maps through messages using `MessageItem`
- - Includes `WorkflowStatusDisplay`
- - Handles auto-scroll reference
-- **Size**: ~73 lines
-
-### `MessageItem.tsx` (Individual Message)
-- **Purpose**: Renders a single message
-- **Responsibilities**:
- - Message content display
- - Role-based styling
- - Timestamp formatting
-- **Size**: ~32 lines
-
-### `ChatInput.tsx` (Input Interface)
-- **Purpose**: Handles user input and send functionality
-- **Responsibilities**:
- - Input field with ref handling
- - Send button with animations
- - Keyboard event handling
- - Disabled states
-- **Size**: ~46 lines
-
-### `WorkflowStatusDisplay.tsx` (Status UI)
-- **Purpose**: Shows workflow status and completion states
-- **Responsibilities**:
- - Running workflow status
- - Completion message
- - "Start New Workflow" button
-- **Size**: ~38 lines
-
-### `types.ts` (Type Definitions)
-- **Purpose**: Centralized TypeScript interfaces
-- **Responsibilities**:
- - Component prop interfaces
- - Data structure types
- - Shared type definitions
-- **Size**: ~50 lines
-
-## Benefits of This Structure
-
-1. **Separation of Concerns**: Each file has a single, clear responsibility
-2. **Reusability**: Components can be easily reused or tested independently
-3. **Maintainability**: Easier to locate and modify specific functionality
-4. **Readability**: Smaller files are easier to understand and navigate
-5. **Testing**: Individual components can be unit tested in isolation
-6. **Type Safety**: Centralized types ensure consistency across components
-
-## Usage
-
-Import the main component as before:
-
-```typescript
-import DashboardChatArea from './DashboardChatArea';
-// or
-import DashboardChatArea, { DashboardChatAreaProps } from './DashboardChatArea';
-```
-
-The API remains exactly the same - this refactoring is purely internal and doesn't affect how the component is used by parent components.
-
-## Development Guidelines
-
-- **Adding new features**: Consider which component/file is most appropriate
-- **State changes**: Most state logic should go in `useChatLogic.ts`
-- **UI changes**: Modify the relevant component file
-- **New types**: Add to `types.ts`
-- **Styling**: All styles remain in `DashboardChatArea.module.css`
\ No newline at end of file
diff --git a/src/components/Dashboard/DashboardChat/useWorkflowManager.ts b/src/components/Dashboard/DashboardChat/useWorkflowManager.ts
new file mode 100644
index 0000000..8c930cb
--- /dev/null
+++ b/src/components/Dashboard/DashboardChat/useWorkflowManager.ts
@@ -0,0 +1,194 @@
+import { useState, useEffect, useCallback, useRef } from 'react';
+import { useWorkflow, useWorkflowMessages, useWorkflowOperations, StartWorkflowRequest } from '../../../hooks/useWorkflows';
+
+export interface WorkflowManagerState {
+ currentWorkflowId: string | null;
+ workflow: any;
+ messages: any[];
+ isLoading: boolean;
+ error: string | null;
+ isPolling: boolean;
+}
+
+export interface WorkflowManagerActions {
+ loadWorkflow: (workflowId: string) => Promise
;
+ startNewWorkflow: (prompt: string, fileIds: number[]) => Promise;
+ continueWorkflow: (prompt: string, fileIds: number[]) => Promise;
+ clearWorkflow: () => void;
+ refreshMessages: () => Promise;
+ setPolling: (enabled: boolean) => void;
+}
+
+export function useWorkflowManager(initialWorkflowId?: string | null): [WorkflowManagerState, WorkflowManagerActions] {
+ // Core state
+ const [currentWorkflowId, setCurrentWorkflowId] = useState(initialWorkflowId || null);
+ const [isPolling, setIsPolling] = useState(false);
+ const pollingIntervalRef = useRef(null);
+
+ // Hook-based data fetching
+ const { workflow, loading: workflowLoading, error: workflowError, refetch: refetchWorkflow } = useWorkflow(currentWorkflowId);
+ const { messages, loading: messagesLoading, error: messagesError, refetch: refetchMessages } = useWorkflowMessages(currentWorkflowId);
+ const { startWorkflow } = useWorkflowOperations();
+
+ // Combined loading and error states
+ const isLoading = workflowLoading || messagesLoading;
+ const error = workflowError || messagesError;
+
+ // Auto-polling for active workflows and message updates
+ useEffect(() => {
+ if (isPolling && currentWorkflowId) {
+ console.log(`🔄 Starting auto-polling for workflow: ${currentWorkflowId}`);
+
+ pollingIntervalRef.current = window.setInterval(() => {
+ console.log('🔄 Auto-polling workflow and messages...');
+ // Always poll for messages when workflow is active
+ refetchMessages();
+
+ // Also poll workflow status if we have workflow data
+ if (workflow) {
+ const isActive = ['running', 'processing', 'started'].includes(workflow.status);
+ if (isActive) {
+ refetchWorkflow();
+ }
+ }
+ }, 2000); // Poll every 2 seconds for smoother updates
+ }
+
+ return () => {
+ if (pollingIntervalRef.current) {
+ console.log('🛑 Stopping auto-polling');
+ clearInterval(pollingIntervalRef.current);
+ pollingIntervalRef.current = null;
+ }
+ };
+ }, [isPolling, currentWorkflowId, workflow?.status, refetchWorkflow, refetchMessages]);
+
+ // Actions
+ const loadWorkflow = useCallback(async (workflowId: string) => {
+ console.log(`📂 Loading workflow: ${workflowId}`);
+ setCurrentWorkflowId(workflowId);
+ // The hooks will automatically fetch data when workflowId changes
+ }, []);
+
+ const startNewWorkflow = useCallback(async (prompt: string, fileIds: number[] = []): Promise => {
+ console.log('🚀 Starting new workflow with prompt:', prompt.substring(0, 50) + '...');
+
+ const workflowData: StartWorkflowRequest = {
+ prompt,
+ listFileId: fileIds
+ };
+
+ const result = await startWorkflow(workflowData);
+
+ if (result.success && result.data) {
+ const newWorkflowId = result.data.id;
+ console.log(`✅ New workflow started: ${newWorkflowId}`);
+ setCurrentWorkflowId(newWorkflowId);
+ setIsPolling(true); // Start polling immediately for new workflows
+
+ // Also immediately fetch messages to get the user's message
+ setTimeout(() => {
+ refetchMessages();
+ }, 500);
+
+ return newWorkflowId;
+ } else {
+ console.error('❌ Failed to start workflow:', result.error);
+ return null;
+ }
+ }, [startWorkflow, refetchMessages]);
+
+ const continueWorkflow = useCallback(async (prompt: string, fileIds: number[] = []): Promise => {
+ if (!currentWorkflowId) {
+ console.error('❌ Cannot continue workflow: no current workflow ID');
+ return false;
+ }
+
+ console.log(`➡️ Continuing workflow ${currentWorkflowId} with prompt:`, prompt.substring(0, 50) + '...');
+
+ const workflowData: StartWorkflowRequest = {
+ prompt,
+ listFileId: fileIds
+ };
+
+ const result = await startWorkflow(workflowData, currentWorkflowId);
+
+ if (result.success) {
+ console.log(`✅ Workflow ${currentWorkflowId} continued`);
+ setIsPolling(true); // Ensure polling is enabled
+
+ // Immediately start polling for new messages
+ setTimeout(() => {
+ refetchMessages();
+ }, 500);
+
+ return true;
+ } else {
+ console.error('❌ Failed to continue workflow:', result.error);
+ return false;
+ }
+ }, [currentWorkflowId, startWorkflow, refetchMessages]);
+
+ const clearWorkflow = useCallback(() => {
+ console.log('🧹 Clearing workflow');
+ setCurrentWorkflowId(null);
+ setIsPolling(false);
+ }, []);
+
+ const refreshMessages = useCallback(async () => {
+ console.log('🔄 Manually refreshing messages');
+ await refetchMessages();
+ }, [refetchMessages]);
+
+ const setPollingEnabled = useCallback((enabled: boolean) => {
+ console.log(`🔄 Setting polling to: ${enabled}`);
+ setIsPolling(enabled);
+ }, []);
+
+ // Initialize workflow on mount if provided
+ useEffect(() => {
+ if (initialWorkflowId && initialWorkflowId !== currentWorkflowId) {
+ loadWorkflow(initialWorkflowId);
+ }
+ }, [initialWorkflowId, currentWorkflowId, loadWorkflow]);
+
+ // Auto-enable polling when workflow becomes active, keep polling until completed
+ useEffect(() => {
+ if (currentWorkflowId && workflow) {
+ const isActive = ['running', 'processing', 'started'].includes(workflow.status);
+ const isCompleted = ['completed', 'failed', 'stopped'].includes(workflow.status);
+
+ if (isActive) {
+ console.log(`🟢 Workflow ${currentWorkflowId} is active (${workflow.status}), enabling polling`);
+ setIsPolling(true);
+ } else if (isCompleted) {
+ console.log(`🔴 Workflow ${currentWorkflowId} is completed (${workflow.status}), disabling polling`);
+ setIsPolling(false);
+ }
+ } else if (currentWorkflowId && !workflow) {
+ // If we have a workflow ID but no workflow data yet, start polling to get updates
+ console.log(`⏳ Workflow ${currentWorkflowId} loaded, starting polling for updates`);
+ setIsPolling(true);
+ }
+ }, [currentWorkflowId, workflow?.status]);
+
+ const state: WorkflowManagerState = {
+ currentWorkflowId,
+ workflow,
+ messages,
+ isLoading,
+ error,
+ isPolling
+ };
+
+ const actions: WorkflowManagerActions = {
+ loadWorkflow,
+ startNewWorkflow,
+ continueWorkflow,
+ clearWorkflow,
+ refreshMessages,
+ setPolling: setPollingEnabled
+ };
+
+ return [state, actions];
+}
diff --git a/src/components/Dashboard/DashboardLog/DashboardLog.module.css b/src/components/Dashboard/DashboardLog/DashboardLog.module.css
deleted file mode 100644
index b07b3a9..0000000
--- a/src/components/Dashboard/DashboardLog/DashboardLog.module.css
+++ /dev/null
@@ -1,338 +0,0 @@
-.dashboard_log {
- display: flex;
- padding: 20px;
- flex-direction: column;
- align-self: stretch;
- border-radius: 30px;
- background: var(--color-bg);
- position: relative;
- box-shadow: 0px 2px 6px 0px rgba(194, 194, 194, 0.10);
- height: 100%;
- width: 100%;
- min-height: 0;
- overflow: hidden;
-}
-
-.dashboard_log.expanded {
- width: 100%;
-}
-
-.log_header {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- width: 100%;
- flex-shrink: 0;
-}
-
-.log_title_div {
- display: flex;
- flex-direction: column;
-}
-
-.log_title {
- text-align: center;
- font-size: 14px;
- font-style: normal;
- font-weight: 500;
- line-height: normal;
- border: none;
- background: none;
- outline: none;
- color: var(--color-text);
- cursor: default;
-}
-
-.log_title_collapsed {
- opacity: 50%;
- color: var(--color-gray);
-}
-
-.collapseIcon {
- cursor: pointer;
- display: flex;
- align-items: center;
- color: var(--color-gray);
-}
-
-.collapseIcon:hover {
- color: var(--color-gray);
-}
-
-.horizontalLine {
- width: 100%;
- height: 2px;
- margin-top: 19px;
-}
-
-.horizontalLineLight {
- width: calc(100%);
- background-color: var(--color-gray-disabled);
- height: 2px;
- margin-top: 39px;
- margin-left: -20px;
- position: absolute;
- flex-shrink: 0;
-}
-
-.log_content {
- margin-top: 20px;
- flex: 1;
- min-height: 0;
- overflow: hidden;
- display: flex;
- flex-direction: column;
-}
-
-.log_entries {
- display: flex;
- flex-direction: column;
- gap: 4px;
- scroll-behavior: smooth;
-}
-
-.log_entries::-webkit-scrollbar {
- display: none;
-}
-
-.log_entry {
- display: flex;
- align-items: flex-start;
- gap: 8px;
- padding: 4px 0;
- animation: fadeIn 0.3s ease-in;
-}
-
-.log_entry:last-child {
- border-bottom: none;
-}
-
-.log_timestamp {
- color: #888;
- font-size: 11px;
- min-width: 80px;
- font-weight: bold;
-}
-
-.log_level {
- color: #00aaff;
- font-size: 11px;
- font-weight: bold;
- min-width: 60px;
-}
-
-.log_message {
- color: #00ff00;
- font-size: 12px;
- flex: 1;
- word-break: break-word;
- line-height: 1.3;
-}
-
-.log_level_info {
- background-color: #4CAF50;
- color: white;
- padding: 2px 8px;
- border-radius: 30px;
- font-size: 10px;
- font-weight: bold;
- min-width: 45px;
- text-align: center;
-}
-
-.log_level_warning {
- background-color: #FF9800;
- color: white;
- padding: 2px 8px;
- border-radius: 30px;
- font-size: 10px;
- font-weight: bold;
- min-width: 45px;
- text-align: center;
-}
-
-.log_level_error {
- background-color: #F44336;
- color: white;
- padding: 2px 8px;
- border-radius: 12px;
- font-size: 10px;
- font-weight: bold;
- min-width: 45px;
- text-align: center;
-}
-
-/* Hacker-style console styles */
-.console_container {
- background-color: #0a0a0a;
- border-radius: 15px;
- height: 100%;
- padding: 15px;
- flex: 1;
- min-height: 0;
- max-height: 100%;
- overflow-y: auto;
- font-family: 'Courier New', 'Monaco', 'Menlo', monospace;
- font-size: 12px;
- line-height: 1.4;
- color: #00ff00;
- border: 1px solid #333;
- box-shadow: inset 0 0 10px rgba(0, 255, 0, 0.1);
- display: flex;
- flex-direction: column;
-}
-
-.console_container::-webkit-scrollbar {
- width: 8px;
-}
-
-.console_container::-webkit-scrollbar-track {
- background: #1a1a1a;
- border-radius: 4px;
-}
-
-.console_container::-webkit-scrollbar-thumb {
- background: #333;
- border-radius: 4px;
-}
-
-.console_container::-webkit-scrollbar-thumb:hover {
- background: #555;
-}
-
-.console_content {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 2px;
-}
-
-.console_line {
- display: flex;
- flex-wrap: wrap;
- margin-bottom: 2px;
- animation: fadeIn 0.3s ease-in;
-}
-
-.console_timestamp {
- color: #888;
- margin-right: 8px;
- font-weight: bold;
- min-width: 80px;
-}
-
-.console_level {
- margin-right: 8px;
- font-weight: bold;
- min-width: 60px;
-}
-
-.console_message {
- color: #00ff00;
- flex: 1;
- word-break: break-word;
-}
-
-.console_data {
- width: 100%;
- margin-top: 4px;
- margin-left: 20px;
- color: #00aaff;
- font-size: 11px;
- white-space: pre-wrap;
- background-color: rgba(0, 170, 255, 0.1);
- padding: 8px;
- border-radius: 4px;
- border-left: 3px solid #00aaff;
-}
-
-.console_prompt {
- color: #00ff00;
- margin-right: 8px;
- font-weight: bold;
-}
-
-.console_text {
- color: #00ff00;
-}
-
-.console_placeholder {
- display: flex;
- align-items: center;
- opacity: 0.7;
- font-style: italic;
- padding: 10px 0;
- min-height: 30px;
-}
-
-.console_loading {
- display: flex;
- align-items: center;
- padding: 10px 0;
- min-height: 30px;
-}
-
-.console_cursor {
- color: #00ff00;
- animation: blink 1s infinite;
- margin-left: 4px;
-}
-
-.console_error {
- display: flex;
- align-items: center;
- padding: 10px 0;
- min-height: 30px;
-}
-
-.console_error .console_text {
- color: #ff4444;
-}
-
-.console_empty {
- display: flex;
- align-items: center;
- opacity: 0.7;
- font-style: italic;
- padding: 10px 0;
- min-height: 30px;
-}
-
-@keyframes fadeIn {
- from {
- opacity: 0;
- transform: translateY(5px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-@keyframes blink {
- 0%, 50% {
- opacity: 1;
- }
- 51%, 100% {
- opacity: 0;
- }
-}
-
-/* Live indicator styling */
-.live_indicator {
- color: #00ff00;
- font-size: 12px;
- font-weight: bold;
- animation: pulse 1.5s ease-in-out infinite;
-}
-
-@keyframes pulse {
- 0%, 100% {
- opacity: 1;
- transform: scale(1);
- }
- 50% {
- opacity: 0.7;
- transform: scale(1.05);
- }
-}
diff --git a/src/components/Dashboard/DashboardLog/DashboardLog.tsx b/src/components/Dashboard/DashboardLog/DashboardLog.tsx
deleted file mode 100644
index 85326a7..0000000
--- a/src/components/Dashboard/DashboardLog/DashboardLog.tsx
+++ /dev/null
@@ -1,215 +0,0 @@
-import React, { useState, useEffect, useRef } from "react";
-import { motion } from "framer-motion";
-
-import styles from './DashboardLog.module.css';
-import { useApiRequest } from '../../../hooks/useApi';
-import { useLanguage } from '../../../contexts/LanguageContext';
-
-interface DashboardLogProps {
- isExpanded: boolean;
- workflowId: string | null;
- workflowCompleted?: boolean;
-}
-
-const DashboardLog: React.FC = ({ isExpanded, workflowId, workflowCompleted = false }) => {
- const { t } = useLanguage();
- const [logs, setLogs] = useState([]);
- const [isPolling, setIsPolling] = useState(false);
- const [logsError, setLogsError] = useState(null);
- const intervalRef = useRef(null);
- const consoleContainerRef = useRef(null);
-
- const { request, isLoading: logsLoading } = useApiRequest();
-
- // Function to fetch logs directly
- const fetchLogs = async (workflowIdToFetch: string) => {
- try {
- console.log('Fetching logs for workflow:', workflowIdToFetch);
- const data = await request({
- url: `/api/workflows/${workflowIdToFetch}/logs`,
- method: 'get'
- });
-
- console.log('Logs fetched:', data);
- setLogs(data || []);
- setLogsError(null);
- } catch (error: any) {
- console.error('Error fetching logs:', error);
- setLogsError(error.message || t('dashboard.log.fetch_failed'));
- }
- };
-
- // Auto-scroll to bottom when new logs arrive during polling
- useEffect(() => {
- if (isPolling && consoleContainerRef.current && logs.length > 0) {
- consoleContainerRef.current.scrollTop = consoleContainerRef.current.scrollHeight;
- }
- }, [logs, isPolling]);
-
- // Start/stop log polling based on workflow state
- useEffect(() => {
- console.log('Log polling effect triggered:', { workflowId, workflowCompleted, isPolling });
-
- // Clear any existing interval
- if (intervalRef.current) {
- clearInterval(intervalRef.current);
- intervalRef.current = null;
- }
-
- if (!workflowId) {
- // No workflow - stop everything
- console.log('No workflow ID, stopping polling');
- setIsPolling(false);
- setLogs([]);
- setLogsError(null);
- return;
- }
-
- if (workflowCompleted) {
- // Workflow completed - stop polling but fetch final logs
- console.log('Workflow completed, stopping polling but fetching final logs');
- setIsPolling(false);
- fetchLogs(workflowId);
- } else {
- // Workflow is running - start polling immediately
- console.log('Workflow running, starting polling');
- setIsPolling(true);
-
- // Fetch logs immediately
- fetchLogs(workflowId);
-
- // Start polling every second
- intervalRef.current = setInterval(() => {
- console.log('Polling for logs...');
- fetchLogs(workflowId);
- }, 1000);
- }
-
- // Cleanup on unmount or dependency change
- return () => {
- if (intervalRef.current) {
- clearInterval(intervalRef.current);
- intervalRef.current = null;
- }
- };
- }, [workflowId, workflowCompleted]);
-
- // Debug logs data
- useEffect(() => {
- console.log('Logs data updated:', {
- logsCount: logs.length,
- logs: logs.slice(0, 3), // Show first 3 logs
- logsLoading,
- logsError,
- isPolling
- });
- }, [logs, logsLoading, logsError, isPolling]);
-
- const renderLogContent = () => {
- if (!workflowId) {
- return (
-
- $
- {t('dashboard.log.no_workflow')}
-
- );
- }
-
- if (logsLoading && logs.length === 0) {
- return (
-
- $
- {t('dashboard.log.loading')}
-
- );
- }
-
- if (logsError) {
- return (
-
- $
- {t('dashboard.log.error')}: {logsError}
-
- );
- }
-
- if (logs.length === 0) {
- const statusText = workflowCompleted
- ? t('dashboard.log.no_logs')
- : t('dashboard.log.waiting');
-
- return (
-
- $
- {statusText}
- {isPolling && |}
-
- );
- }
-
- return (
-
- {logs.map((log, index) => (
-
-
- {log.timestamp ? new Date(log.timestamp).toLocaleTimeString() : ''}
-
- [{log.level || t('dashboard.log.level.info')}]
- {log.message || log.content || JSON.stringify(log)}
-
- ))}
-
- );
- };
-
- return (
-
-
-
-
- {t('dashboard.log.title')} {workflowId && `- ${t('dashboard.log.workflow')} ${workflowId.substring(0, 8)}...`}
-
-
-
-
-
-
-
-
- {renderLogContent()}
-
-
-
-
- );
-};
-
-export default DashboardLog;
diff --git a/src/components/Dashboard/DashboardPrompt/DashboardPrompt.module.css b/src/components/Dashboard/DashboardPrompt/DashboardPrompt.module.css
deleted file mode 100644
index cbe517c..0000000
--- a/src/components/Dashboard/DashboardPrompt/DashboardPrompt.module.css
+++ /dev/null
@@ -1,157 +0,0 @@
-.dashboard_prompt {
- display: flex;
- padding: 20px;
- flex-direction: column;
- align-self: stretch;
- border-radius: 30px;
- background: var(--color-bg);
- position: relative;
- box-shadow: 0px 2px 6px 0px rgba(194, 194, 194, 0.10);
- width: 100%;
- transition: height 0.3s ease;
- font-family: var(--font-family);
-}
-
-.dashboard_prompt:not(.collapsed) {
- height: 100%;
- overflow: hidden;
- display: flex;
- flex-direction: column;
-}
-
-.dashboard_prompt.collapsed {
- height: auto;
- min-height: auto;
-}
-
-.prompt_header {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- width: 100%;
-}
-
-.prompt_button_div {
- display: flex;
- align-self: stretch;
- gap: 30px;
-}
-
-.prompt_button {
- text-align: center;
- font-size: 14px;
- font-style: normal;
- font-weight: 500;
- line-height: normal;
- border: none;
- background: none;
- outline: none;
- color: var(--color-text);
- transition: opacity 0.3s ease, color 0.3s ease;
- font-family: var(--font-family);
-}
-
-.prompt_button_inactive {
- opacity: 50%;
-}
-
-.prompt_button_collapsed {
- opacity: 50%;
- color: var(--color-gray);
-}
-
-.buttonWrapper {
- display: flex;
- flex-direction: column;
-}
-
-.expandIcon {
- cursor: pointer;
- display: flex;
- align-items: center;
- color: var(--color-gray);
- transition: color 0.3s ease;
-}
-
-.expandIcon:hover {
- color: var(--color-text);
-}
-
-.horizontalLine {
- width: 100%;
- background-color: var(--color-text);
- height: 2px;
- margin-top: 19px;
- transition: opacity 0.3s ease;
-}
-
-.horizontalLineLight {
- width: calc(100%);
- background-color: var(--color-gray-disabled);
- height: 2px;
- margin-top: 39px;
- margin-left: -20px;
- position: absolute;
- transition: opacity 0.3s ease;
-}
-
-.content_wrapper {
- flex: 1;
- min-height: 0;
- display: flex;
- flex-direction: column;
- position: relative;
-}
-
-.content_area {
- flex: 1;
- min-height: 0;
- display: flex;
- flex-direction: column;
- transition: all 0.3s ease;
- opacity: 1;
- position: relative;
-}
-
-.content_collapsed {
- opacity: 0;
- max-height: 0;
- overflow: hidden;
- padding: 0;
- margin: 0;
-}
-
-.scrollableContent {
- flex: 1;
- min-height: 0;
- overflow-y: auto;
- overflow-x: hidden;
- padding: 0 0.5rem 2rem 0;
-}
-
-.container {
- display: flex;
- flex-direction: column;
- flex: 1;
- min-height: 0;
-}
-
-.collapseUp {
- flex-direction: column-reverse;
-}
-
-.collapseContent {
- will-change: transform, opacity;
- transition: transform 0.3s cubic-bezier(0.4,0,0.2,1), opacity 0.3s cubic-bezier(0.4,0,0.2,1);
-}
-
-.collapseContent.collapsed {
- transform: translateY(-100%);
- opacity: 0;
-}
-
-.collapseContent.expanded {
- transform: translateY(0);
- opacity: 1;
-}
-
diff --git a/src/components/Dashboard/DashboardPrompt/DashboardPrompt.tsx b/src/components/Dashboard/DashboardPrompt/DashboardPrompt.tsx
deleted file mode 100644
index 91d4705..0000000
--- a/src/components/Dashboard/DashboardPrompt/DashboardPrompt.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import React, { useState, useEffect } from "react";
-import { useSearchParams } from "react-router-dom";
-import { MdExpandMore, MdExpandLess } from "react-icons/md";
-
-import DashboardPromptSettings from './DashboardPromptSettings/DashboardPromptSettings';
-import DashboardPromptSet from './DashboardPromptSet/DashboardPromptSet';
-import { Prompt } from '../../../hooks/usePrompts';
-import { useLanguage } from '../../../contexts/LanguageContext';
-
-import styles from './DashboardPrompt.module.css';
-
-interface DashboardPromptProps {
- onPromptRun: (prompt: Prompt) => void;
- isCollapsed: boolean;
- onToggleCollapse: () => void;
-}
-
-const DashboardPrompt: React.FC = ({
- onPromptRun,
- isCollapsed,
- onToggleCollapse
-}) => {
- const { t } = useLanguage();
- const [activeTab, setActiveTab] = useState(t('dashboard.prompt.template'));
- const [searchParams] = useSearchParams();
-
- useEffect(() => {
- const expandedPrompt = searchParams.get('expandedPrompt');
- const promptId = searchParams.get('promptId');
-
- if (expandedPrompt) {
- setActiveTab(t('dashboard.prompt.template'));
- } else if (promptId) {
- setActiveTab(t('dashboard.prompt.settings'));
- }
- }, [searchParams, t]);
-
- return (
-
-
-
- {[
- t('dashboard.prompt.template'),
- t('dashboard.prompt.settings')
- ].map((tab) => (
-
-
- {!isCollapsed && activeTab === tab && (
-
- )}
-
- ))}
-
-
-
-
-
- {!isCollapsed && (
-
- )}
- {!isCollapsed && (
-
-
- {activeTab === t('dashboard.prompt.template') ? (
-
- ) : (
-
- )}
-
-
- )}
-
- )
-}
-
-export default DashboardPrompt;
\ No newline at end of file
diff --git a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSet.module.css b/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSet.module.css
deleted file mode 100644
index 402abcb..0000000
--- a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSet.module.css
+++ /dev/null
@@ -1,127 +0,0 @@
-.container {
- display: flex;
- flex-direction: column;
- overflow: hidden;
- font-family: var(--font-family);
-}
-
-.header {
- display: flex;
- justify-content: left;
- align-items: center;
- margin-top: 1rem;
- margin-bottom: 1rem;
- flex-shrink: 0;
- gap: 20px;
-}
-
-.addButton {
- border-radius: 30px;
- background: var(--color-secondary);
- color: var(--color-bg);
- border: none;
- outline: none;
- text-align: left;
- padding-left: 20px;
- padding-right: 20px;
- padding-top: 10px;
- padding-bottom: 10px;
- display: flex;
- gap: 10px;
- align-items: center;
- font-family: var(--font-family);
- transition: background-color 0.2s ease;
-}
-
-.addButton:hover {
- cursor: pointer;
- background: var(--color-secondary-hover);
-}
-
-.promptCount {
- font-size: 0.875rem;
- color: var(--color-gray);
- font-family: var(--font-family);
-}
-
-.scrollableContent {
- flex: 1;
- overflow-y: auto;
- overflow-x: hidden;
- padding-right: 0.5rem;
- padding-bottom: 2rem;
- min-height: 0;
-}
-
-.scrollableContent::-webkit-scrollbar {
- width: 6px;
-}
-
-.scrollableContent::-webkit-scrollbar-track {
- background: transparent;
-}
-
-.scrollableContent::-webkit-scrollbar-thumb {
- background: var(--color-gray-disabled);
- border-radius: 3px;
-}
-
-.scrollableContent::-webkit-scrollbar-thumb:hover {
- background: var(--color-gray);
-}
-
-.promptsList {
- display: flex;
- flex-direction: column;
- gap: 14px;
- padding-bottom: 1rem;
-}
-
-.loadingContainer {
- display: flex;
- justify-content: center;
- align-items: center;
- padding: 2rem;
-}
-
-.loadingText {
- color: var(--color-gray);
- font-family: var(--font-family);
-}
-
-.errorContainer {
- padding: 1rem;
- background-color: var(--color-red-disabled);
- border: 1px solid var(--color-red);
- border-radius: 0.5rem;
-}
-
-.errorText {
- color: var(--color-red);
- font-family: var(--font-family);
-}
-
-.retryButton {
- margin-top: 0.5rem;
- padding: 0.5rem 1rem;
- background-color: var(--color-red);
- color: var(--color-bg);
- border-radius: 0.375rem;
- border: none;
- cursor: pointer;
- transition: background-color 0.2s;
- font-family: var(--font-family);
-}
-
-.retryButton:hover {
- background-color: var(--color-red-hover);
-}
-
-.emptyState {
- text-align: center;
- padding: 2rem;
- color: var(--color-gray);
- font-family: var(--font-family);
-}
-
-
diff --git a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSet.tsx b/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSet.tsx
deleted file mode 100644
index 02a3842..0000000
--- a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSet.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import React, { useState } from 'react';
-import { usePrompts, usePromptOperations, Prompt } from '../../../../hooks/usePrompts';
-import { useLanguage } from '../../../../contexts/LanguageContext';
-import DashboardPromptSetItem from './DashboardPromptSetItem';
-import DashboardPromptSetModal from './DashboardPromptSetModal';
-import styles from './DashboardPromptSet.module.css';
-import { FaPlus } from 'react-icons/fa';
-
-interface DashboardPromptSetProps {
- onPromptRun: (prompt: Prompt) => void;
-}
-
-function DashboardPromptSet({ onPromptRun }: DashboardPromptSetProps) {
- const { t } = useLanguage();
- const { prompts, loading, error, refetch } = usePrompts();
- const { handlePromptCreate, creatingPrompt } = usePromptOperations();
- const [isModalOpen, setIsModalOpen] = useState(false);
-
- const handleCreatePrompt = async (promptData: { name: string; content: string }) => {
- const result = await handlePromptCreate(promptData);
- if (result.success) {
- await refetch(); // Refresh the prompts list
- setIsModalOpen(false);
- } else {
- throw new Error(result.error);
- }
- };
-
- if (loading) {
- return (
-
-
{t('promptset.loading')}
-
- );
- }
-
- if (error) {
- return (
-
-
{t('promptset.error.loading')}: {error}
-
-
- );
- }
-
- return (
-
-
-
-
-
-
- {prompts.length} {prompts.length === 1 ? t('promptset.prompt_count') : t('promptset.prompt_count_plural')}
-
-
-
-
-
- {prompts.length === 0 ? (
-
- {t('promptset.no_prompts')}
-
- ) : (
-
- {prompts.map((prompt) => (
-
- ))}
-
- )}
-
-
-
setIsModalOpen(false)}
- onSubmit={handleCreatePrompt}
- isLoading={creatingPrompt}
- />
-
- );
-}
-
-export default DashboardPromptSet;
\ No newline at end of file
diff --git a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetItem.module.css b/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetItem.module.css
deleted file mode 100644
index 1c06dc1..0000000
--- a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetItem.module.css
+++ /dev/null
@@ -1,182 +0,0 @@
-.promptItem {
- background: var(--color-surface);
- border-radius: 30px;
- display: flex;
- padding: 20px;
- flex-direction: column;
- align-items: flex-start;
- align-self: stretch;
- justify-content: top;
- font-family: var(--font-family);
- gap: 11px;
- font-size: 14px;
-}
-
-
-.promptMain {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- align-self: stretch;
- flex: 1;
- min-height: 0;
-}
-
-.promptInfo {
- flex: 1;
-}
-
-.promptName {
- font-weight: 400;
- color: var(--color-text);
- margin:0;
- font-family: var(--font-family);
-}
-
-.promptDate {
- font: 14px;
- color: var(--color-gray);
- font-family: var(--font-family);
-}
-
-.promptText {
- overflow: hidden;
- height: auto;
- flex: 1;
- min-height: 0;
- opacity: 0.5;
- margin:0;
- color: var(--color-text);
- font-family: var(--font-family);
-}
-
-.promptText.p {
- margin:0;
-}
-
-.actionButtons {
- display: flex;
- gap: 0.5rem;
- align-self: flex-start;
- flex-shrink: 0;
-}
-
-.actionButton {
- padding: 0.5rem;
- border-radius: 12px;
- background: var(--color-secondary);
- color: var(--color-bg);
- cursor: pointer;
- border: none;
- font-family: var(--font-family);
- transition: background-color 0.2s ease;
-}
-
-.actionButton:disabled {
- opacity: 0.5;
- cursor: not-allowed;
-}
-
-.runButton {
- border-radius: 12px;
- background: var(--color-secondary);
- color: var(--color-bg);
-}
-
-.runButton:hover:not(:disabled) {
- border-radius: 12px;
- background: var(--color-secondary-hover);
- color: var(--color-bg);
- cursor: pointer;
-}
-
-.shareButton {
- border-radius: 12px;
- background: var(--color-secondary);
- color: var(--color-bg);
-}
-
-.shareButton:hover:not(:disabled) {
- border-radius: 12px;
- background: var(--color-secondary-hover);
- color: var(--color-bg);
-}
-
-.deleteButton {
- border-radius: 12px;
- background: var(--color-red);
- color: var(--color-bg);
-}
-
-.deleteButton:hover:not(:disabled) {
- border-radius: 12px;
- background: var(--color-red-hover);
- color: var(--color-bg);
-}
-
-.deleteButton.confirm {
- background-color: var(--color-red-disabled);
- color: var(--color-red);
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.deleteButton.confirm:hover:not(:disabled) {
- background-color: var(--color-red-hover);
- color: var(--color-bg);
-}
-
-.actionText {
- font-size: 12px;
- color: var(--color-gray);
- animation: pulse 1.5s infinite;
- white-space: nowrap;
- font-family: var(--font-family);
- margin-left: 4px;
-}
-
-.deleteButton.confirm .actionText {
- color: var(--color-red);
- animation: none;
-}
-
-@keyframes pulse {
- 0% {
- opacity: 0.6;
- }
- 50% {
- opacity: 1;
- }
- 100% {
- opacity: 0.6;
- }
-}
-
-.promptContent {
- display: flex;
- flex-direction: column;
- flex: 1;
- min-height: 0;
- gap: 11px;
- max-width: calc(100% - 120px);
-}
-
-.errorMessage {
- margin-top: 0.75rem;
- padding: 0.5rem;
- background-color: var(--color-red-disabled);
- border: 1px solid var(--color-red);
- border-radius: 0.25rem;
- font-size: 0.875rem;
- color: var(--color-red);
- font-family: var(--font-family);
-}
-
-.deletingMessage {
- margin-top: 0.75rem;
- font-size: 0.875rem;
- color: var(--color-gray);
- font-family: var(--font-family);
-}
diff --git a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetItem.tsx b/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetItem.tsx
deleted file mode 100644
index 3fd7cee..0000000
--- a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetItem.tsx
+++ /dev/null
@@ -1,155 +0,0 @@
-import React, { useState, useRef, useEffect } from 'react';
-import { FaArrowRight } from 'react-icons/fa';
-import { AiOutlineDelete } from 'react-icons/ai';
-import { BsShareFill } from 'react-icons/bs';
-import { usePromptOperations, Prompt } from '../../../../hooks/usePrompts';
-import { useLanguage } from '../../../../contexts/LanguageContext';
-import PromptShareModal from './PromptShareModal';
-import styles from './DashboardPromptSetItem.module.css';
-
-interface DashboardPromptSetItemProps {
- prompt: Prompt;
- onDelete?: () => void;
- onRun: (prompt: Prompt) => void;
- onShare?: () => void;
-}
-
-function DashboardPromptSetItem({ prompt, onDelete, onRun, onShare }: DashboardPromptSetItemProps) {
- const { t } = useLanguage();
- const { handlePromptDelete, handlePromptShare, deletingPrompts, sharingPrompts, deleteError, shareError } = usePromptOperations();
- const contentRef = useRef(null);
- const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
- const [showShareModal, setShowShareModal] = useState(false);
-
- const isDeleting = deletingPrompts.has(prompt.id);
- const isSharing = sharingPrompts.has(prompt.id);
-
- const handleDeleteClick = async () => {
- if (showDeleteConfirm) {
- const success = await handlePromptDelete(prompt.id);
- if (success && onDelete) {
- onDelete();
- }
- setShowDeleteConfirm(false);
- } else {
- setShowDeleteConfirm(true);
- }
- };
-
- const handleCancelDelete = () => {
- setShowDeleteConfirm(false);
- };
-
- const handleRun = () => {
- onRun(prompt);
- };
-
- const handleShare = () => {
- setShowShareModal(true);
- };
-
- const handleShareSubmit = async (shareData: { userIds: number[]; message?: string; title?: string }) => {
- const result = await handlePromptShare(prompt.id, shareData);
- if (result.success) {
- setShowShareModal(false);
- if (onShare) {
- onShare(); // Trigger refresh of prompts list
- }
- }
- // Error handling is done by the hook
- };
-
- const handleCloseShareModal = () => {
- setShowShareModal(false);
- };
-
- return (
- <>
-
-
-
-
-
- {prompt.name}
-
- {prompt.createdAt && (
-
- {t('promptset.created')}: {new Date(prompt.createdAt).toLocaleDateString('de-DE')}
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {deleteError && (
-
- {t('promptset.delete_error')}: {deleteError}
-
- )}
-
- {shareError && (
-
- {t('share_modal.share_error')}: {shareError}
-
- )}
-
- {isDeleting && (
-
- {t('promptset.deleting_message')}
-
- )}
-
-
- {/* Share Modal */}
-
- >
- );
-}
-
-export default DashboardPromptSetItem;
diff --git a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetModal.module.css b/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetModal.module.css
deleted file mode 100644
index d999125..0000000
--- a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetModal.module.css
+++ /dev/null
@@ -1,192 +0,0 @@
-.overlay {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(0, 0, 0, 0.5);
- display: flex;
- justify-content: center;
- align-items: center;
- z-index: 1000;
- padding: 20px;
-}
-
-.modal {
- background: var(--color-bg);
- border-radius: 12px;
- border: 1px solid var(--color-gray);
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
- width: 100%;
- max-width: 500px;
- max-height: 90vh;
- overflow: hidden;
- display: flex;
- flex-direction: column;
-}
-
-.header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 24px 24px 0 24px;
- border-bottom: 1px solid var(--color-gray);
- margin-bottom: 20px;
-}
-
-.title {
- font-size: 20px;
- font-weight: 600;
- color: var(--color-text);
- margin: 0;
-}
-
-.closeButton {
- background: none;
- border: none;
- padding: 8px;
- cursor: pointer;
- color: var(--color-text);
- border-radius: 6px;
- transition: all 0.2s;
-}
-
-.closeButton:hover {
- background-color: var(--color-gray-disabled);
- color: var(--color-text);
-}
-
-.closeButton:disabled {
- opacity: 0.5;
- cursor: not-allowed;
-}
-
-.form {
- padding: 0 24px 24px 24px;
- flex: 1;
- overflow-y: auto;
-}
-
-.formGroup {
- margin-bottom: 20px;
-}
-
-.label {
- display: block;
- font-size: 14px;
- font-weight: 500;
- color: var(--color-text);
- margin-bottom: 6px;
-}
-
-.input {
- width: 100%;
- padding: 12px 16px;
- border: 1px solid var(--color-gray);
- border-radius: 8px;
- font-size: 14px;
- transition: border-color 0.2s;
- box-sizing: border-box;
- background-color: var(--color-bg);
- color: var(--color-text);
-}
-
-.input:focus {
- outline: none;
- border-color: var(--color-secondary);
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
-}
-
-.input:disabled {
- background-color: var(--color-gray-disabled);
- opacity: 0.7;
-}
-
-.textarea {
- width: 100%;
- padding: 12px 16px;
- border: 1px solid var(--color-gray);
- border-radius: 8px;
- font-size: 14px;
- font-family: inherit;
- resize: vertical;
- min-height: 120px;
- transition: border-color 0.2s;
- box-sizing: border-box;
- background-color: var(--color-bg);
- color: var(--color-text);
-}
-
-.textarea:focus {
- outline: none;
- border-color: var(--color-secondary);
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
- background-color: var(--color-bg);
- color: var(--color-text);
-}
-
-.textarea:disabled {
- background-color: var(--color-gray-disabled);
- opacity: 0.7;
-}
-
-.error {
- background-color: var(--color-red-disabled);
- border: 1px solid var(--color-red);
- padding: 12px 16px;
- border-radius: 8px;
- font-size: 14px;
- margin-bottom: 20px;
-}
-
-.buttons {
- display: flex;
- gap: 12px;
- justify-content: flex-end;
- margin-top: 24px;
- padding-top: 20px;
- border-top: 1px solid var(--color-gray);
-}
-
-.cancelButton {
- padding: 10px 20px;
- border: 1px solid var(--color-red);
- background: var(--color-red);
- color: var(--color-text);
- border-radius: 8px;
- font-size: 14px;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.2s;
-}
-
-.cancelButton:hover {
- background-color: var(--color-red-hover);
- border-color: var(--color-red-hover);
-}
-
-.cancelButton:disabled {
- opacity: 0.5;
- cursor: not-allowed;
-}
-
-.submitButton {
- padding: 10px 20px;
- background: var(--color-secondary);
- color: white;
- border: none;
- border-radius: 8px;
- font-size: 14px;
- font-weight: 500;
- cursor: pointer;
- transition: background-color 0.2s;
-}
-
-.submitButton:hover {
- background: var(--color-secondary-hover);
-}
-
-.submitButton:disabled {
- background: var(--color-secondary-disabled);
- cursor: not-allowed;
-}
\ No newline at end of file
diff --git a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetModal.tsx b/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetModal.tsx
deleted file mode 100644
index cc2789e..0000000
--- a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/DashboardPromptSetModal.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-import React, { useState } from 'react';
-import { FaTimes } from 'react-icons/fa';
-import { useLanguage } from '../../../../contexts/LanguageContext';
-import styles from './DashboardPromptSetModal.module.css';
-
-interface DashboardPromptSetModalProps {
- isOpen: boolean;
- onClose: () => void;
- onSubmit: (promptData: { name: string; content: string }) => Promise;
- isLoading?: boolean;
-}
-
-function DashboardPromptSetModal({ isOpen, onClose, onSubmit, isLoading = false }: DashboardPromptSetModalProps) {
- const { t } = useLanguage();
- const [name, setName] = useState('');
- const [content, setContent] = useState('');
- const [error, setError] = useState(null);
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
-
- if (!name.trim()) {
- setError(t('modal.name_required'));
- return;
- }
-
- if (!content.trim()) {
- setError(t('modal.content_required'));
- return;
- }
-
- setError(null);
-
- try {
- await onSubmit({ name: name.trim(), content: content.trim() });
- // Reset form on success
- setName('');
- setContent('');
- onClose();
- } catch (err: any) {
- setError(err.message || t('modal.create_error'));
- }
- };
-
- const handleClose = () => {
- setName('');
- setContent('');
- setError(null);
- onClose();
- };
-
- if (!isOpen) return null;
-
- return (
-
-
-
-
{t('modal.create_prompt')}
-
-
-
-
-
-
- );
-}
-
-export default DashboardPromptSetModal;
\ No newline at end of file
diff --git a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/PromptShareModal.module.css b/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/PromptShareModal.module.css
deleted file mode 100644
index aa570ea..0000000
--- a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/PromptShareModal.module.css
+++ /dev/null
@@ -1,315 +0,0 @@
-.overlay {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(0, 0, 0, 0.5);
- display: flex;
- justify-content: center;
- align-items: center;
- z-index: 1000;
- padding: 20px;
-}
-
-.modal {
- background: var(--color-bg);
- border-radius: 12px;
- border: 1px solid var(--color-gray);
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
- width: 100%;
- max-width: 600px;
- max-height: 90vh;
- overflow: hidden;
- display: flex;
- flex-direction: column;
-}
-
-.header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 24px 24px 0 24px;
- border-bottom: 1px solid var(--color-gray);
- margin-bottom: 20px;
-}
-
-.title {
- font-size: 20px;
- font-weight: 600;
- color: var(--color-text);
- margin: 0;
- word-break: break-word;
-}
-
-.closeButton {
- background: none;
- border: none;
- padding: 8px;
- cursor: pointer;
- color: var(--color-text);
- border-radius: 6px;
- transition: all 0.2s;
- flex-shrink: 0;
-}
-
-.closeButton:hover {
- background-color: var(--color-gray-disabled);
- color: var(--color-text);
-}
-
-.closeButton:disabled {
- opacity: 0.5;
- cursor: not-allowed;
-}
-
-.form {
- padding: 0 24px 24px 24px;
- flex: 1;
- overflow-y: auto;
-}
-
-.formGroup {
- margin-bottom: 24px;
-}
-
-.sectionHeader {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 12px;
-}
-
-.label {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 14px;
- font-weight: 500;
- color: var(--color-text);
-}
-
-.labelIcon {
- font-size: 12px;
- color: var(--color-secondary);
-}
-
-.selectAllButton {
- background: none;
- border: 1px solid var(--color-secondary);
- color: var(--color-secondary);
- padding: 6px 12px;
- border-radius: 6px;
- font-size: 12px;
- cursor: pointer;
- transition: all 0.2s;
-}
-
-.selectAllButton:hover {
- background-color: var(--color-secondary);
- color: white;
-}
-
-.selectAllButton:disabled {
- opacity: 0.5;
- cursor: not-allowed;
-}
-
-.usersList {
- border: 1px solid var(--color-gray);
- border-radius: 8px;
- max-height: 200px;
- overflow-y: auto;
- background-color: var(--color-bg);
-}
-
-.userItem {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 12px 16px;
- cursor: pointer;
- transition: background-color 0.2s;
- border-bottom: 1px solid var(--color-gray);
-}
-
-.userItem:last-child {
- border-bottom: none;
-}
-
-.userItem:hover {
- background-color: var(--color-gray-disabled);
-}
-
-.userItem.selected {
- background-color: rgba(59, 130, 246, 0.1);
- border-color: var(--color-secondary);
-}
-
-.checkbox {
- cursor: pointer;
- width: 16px;
- height: 16px;
-}
-
-.userIcon {
- color: var(--color-secondary);
- font-size: 14px;
- flex-shrink: 0;
-}
-
-.userInfo {
- flex: 1;
- min-width: 0;
-}
-
-.userName {
- font-weight: 500;
- color: var(--color-text);
- font-size: 14px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.userUsername {
- font-size: 12px;
- color: var(--color-text-disabled);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.loading {
- padding: 20px;
- text-align: center;
- color: var(--color-text-disabled);
- font-style: italic;
-}
-
-.noUsers {
- padding: 20px;
- text-align: center;
- color: var(--color-text-disabled);
- font-style: italic;
-}
-
-.selectedCount {
- margin-top: 8px;
- font-size: 12px;
- color: var(--color-secondary);
- font-weight: 500;
-}
-
-.input {
- width: 100%;
- padding: 12px 16px;
- border: 1px solid var(--color-gray);
- border-radius: 8px;
- font-size: 14px;
- transition: border-color 0.2s;
- box-sizing: border-box;
- background-color: var(--color-bg);
- color: var(--color-text);
-}
-
-.input:focus {
- outline: none;
- border-color: var(--color-secondary);
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
-}
-
-.input:disabled {
- background-color: var(--color-gray-disabled);
- opacity: 0.7;
-}
-
-.textarea {
- width: 100%;
- padding: 12px 16px;
- border: 1px solid var(--color-gray);
- border-radius: 8px;
- font-size: 14px;
- font-family: inherit;
- resize: vertical;
- min-height: 80px;
- transition: border-color 0.2s;
- box-sizing: border-box;
- background-color: var(--color-bg);
- color: var(--color-text);
-}
-
-.textarea:focus {
- outline: none;
- border-color: var(--color-secondary);
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
- background-color: var(--color-bg);
- color: var(--color-text);
-}
-
-.textarea:disabled {
- background-color: var(--color-gray-disabled);
- opacity: 0.7;
-}
-
-.error {
- background-color: var(--color-red-disabled);
- border: 1px solid var(--color-red);
- padding: 12px 16px;
- border-radius: 8px;
- font-size: 14px;
- margin-bottom: 20px;
- color: var(--color-red);
-}
-
-.buttons {
- display: flex;
- gap: 12px;
- justify-content: flex-end;
- margin-top: 24px;
- padding-top: 20px;
- border-top: 1px solid var(--color-gray);
-}
-
-.cancelButton {
- padding: 10px 20px;
- border: 1px solid var(--color-red);
- background: var(--color-red);
- color: white;
- border-radius: 8px;
- font-size: 14px;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.2s;
-}
-
-.cancelButton:hover {
- background-color: var(--color-red-hover);
- border-color: var(--color-red-hover);
-}
-
-.cancelButton:disabled {
- opacity: 0.5;
- cursor: not-allowed;
-}
-
-.submitButton {
- padding: 10px 20px;
- background: var(--color-secondary);
- color: white;
- border: none;
- border-radius: 8px;
- font-size: 14px;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.2s;
-}
-
-.submitButton:hover:not(:disabled) {
- background-color: var(--color-secondary-hover);
-}
-
-.submitButton:disabled {
- opacity: 0.5;
- cursor: not-allowed;
-}
\ No newline at end of file
diff --git a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/PromptShareModal.tsx b/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/PromptShareModal.tsx
deleted file mode 100644
index 127afbe..0000000
--- a/src/components/Dashboard/DashboardPrompt/DashboardPromptSet/PromptShareModal.tsx
+++ /dev/null
@@ -1,249 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { FaTimes, FaUser, FaUsers } from 'react-icons/fa';
-import { useLanguage } from '../../../../contexts/LanguageContext';
-import { useUsers, User } from '../../../../hooks/usePrompts';
-import styles from './PromptShareModal.module.css';
-
-interface PromptShareModalProps {
- isOpen: boolean;
- onClose: () => void;
- onSubmit: (shareData: { userIds: number[]; message?: string; title?: string }) => Promise;
- promptName: string;
- isLoading?: boolean;
-}
-
-function PromptShareModal({ isOpen, onClose, onSubmit, promptName, isLoading = false }: PromptShareModalProps) {
- const { t } = useLanguage();
- const { users, loading: usersLoading, error: usersError } = useUsers();
- const [selectedUsers, setSelectedUsers] = useState>(new Set());
- const [message, setMessage] = useState('');
- const [title, setTitle] = useState('');
- const [error, setError] = useState(null);
-
- // Reset form when modal opens/closes
- useEffect(() => {
- if (isOpen) {
- setSelectedUsers(new Set());
- setMessage('');
- setTitle('');
- setError(null);
- }
- }, [isOpen]);
-
- const handleUserToggle = (userId: number) => {
- console.log('Toggling user:', userId); // Debug log
- setSelectedUsers(prev => {
- const newSet = new Set(prev);
- if (newSet.has(userId)) {
- newSet.delete(userId);
- console.log('Removed user:', userId); // Debug log
- } else {
- newSet.add(userId);
- console.log('Added user:', userId); // Debug log
- }
- console.log('New selected users:', Array.from(newSet)); // Debug log
- return newSet;
- });
- };
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
-
- if (selectedUsers.size === 0) {
- setError(t('share_modal.no_users_selected'));
- return;
- }
-
- setError(null);
-
- try {
- await onSubmit({
- userIds: Array.from(selectedUsers),
- message: message.trim() || undefined,
- title: title.trim() || undefined
- });
- onClose();
- } catch (err: any) {
- setError(err.message || t('share_modal.share_error'));
- }
- };
-
- const handleClose = () => {
- setSelectedUsers(new Set());
- setMessage('');
- setTitle('');
- setError(null);
- onClose();
- };
-
- const handleSelectAll = () => {
- if (selectedUsers.size === users.length) {
- setSelectedUsers(new Set());
- } else {
- setSelectedUsers(new Set(users.map(user => user.id)));
- }
- };
-
- if (!isOpen) return null;
-
- return (
-
-
-
-
- {t('share_modal.title')} "{promptName}"
-
-
-
-
-
-
-
- );
-}
-
-export default PromptShareModal;
\ No newline at end of file
diff --git a/src/components/Dashboard/DashboardPrompt/DashboardPromptSettings/DashboardPromptSettings.module.css b/src/components/Dashboard/DashboardPrompt/DashboardPromptSettings/DashboardPromptSettings.module.css
deleted file mode 100644
index 071b746..0000000
--- a/src/components/Dashboard/DashboardPrompt/DashboardPromptSettings/DashboardPromptSettings.module.css
+++ /dev/null
@@ -1,39 +0,0 @@
-.promptArea {
- display: flex;
- flex-direction: column;
- height: 100%;
- position: relative;
- font-family: var(--font-family);
-}
-
-.cancelContainer {
- position: absolute;
- top: 10px;
- right: 10px;
- z-index: 10;
-}
-
-.cancelButton {
- display: flex;
- align-items: center;
- gap: 4px;
- padding: 8px 16px;
- background: var(--color-bg);
- border: 1px solid var(--color-gray-disabled);
- border-radius: 20px;
- color: var(--color-gray);
- font-size: 14px;
- cursor: pointer;
- transition: all 0.2s;
- font-family: var(--font-family);
-}
-
-.cancelButton:hover {
- background-color: var(--color-surface);
- border-color: var(--color-gray);
- color: var(--color-text);
-}
-
-.cancelIcon {
- font-size: 16px;
-}
\ No newline at end of file
diff --git a/src/components/Dashboard/DashboardPrompt/DashboardPromptSettings/DashboardPromptSettings.tsx b/src/components/Dashboard/DashboardPrompt/DashboardPromptSettings/DashboardPromptSettings.tsx
deleted file mode 100644
index 45d8aff..0000000
--- a/src/components/Dashboard/DashboardPrompt/DashboardPromptSettings/DashboardPromptSettings.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react';
-import { useLanguage } from '../../../../contexts/LanguageContext';
-import styles from './DashboardPromptSettings.module.css';
-
-function DashboardPromptSettings() {
- const { t } = useLanguage();
-
- return (
-
-
-
{t('prompt_settings.title')}
-
{t('prompt_settings.content_placeholder')}
-
-
- );
-}
-
-export default DashboardPromptSettings;
diff --git a/src/components/FormGenerator/FormGenerator.module.css b/src/components/FormGenerator/FormGenerator.module.css
index fda1980..91c9a67 100644
--- a/src/components/FormGenerator/FormGenerator.module.css
+++ b/src/components/FormGenerator/FormGenerator.module.css
@@ -130,6 +130,13 @@
box-sizing: border-box;
}
+.filterInput:focus {
+ outline: none;
+ border-color: var(--color-secondary);
+ opacity: 1;
+ box-shadow: 0 0 0 2px rgba(var(--color-secondary), 0.1);
+}
+
.filterInput::placeholder {
color: transparent;
}
@@ -168,12 +175,7 @@
opacity: 1;
}
-.filterInput:focus {
- outline: none;
- border-color: var(--color-secondary);
- opacity: 1;
- box-shadow: 0 0 0 2px rgba(var(--color-secondary), 0.1);
-}
+
@@ -218,7 +220,7 @@
border: 1px solid var(--color-primary);
border-radius: 25px;
background: var(--color-bg);
- max-height: 600px;
+ max-height: 90%;
}
.loading {
@@ -401,7 +403,7 @@ tbody .actionsColumn {
align-items: center;
gap: 10px;
padding: 15px;
- border-top: 1px solid var(--color-gray-disabled);
+ border-top: 1px solid var(--color-primary);
background: var(--color-bg);
border-radius: 0 0 8px 8px;
}
diff --git a/src/components/PageManager/PageManager.module.css b/src/components/PageManager/PageManager.module.css
new file mode 100644
index 0000000..cd57d52
--- /dev/null
+++ b/src/components/PageManager/PageManager.module.css
@@ -0,0 +1,30 @@
+.pageManager {
+ height: 100%;
+ width: 100%;
+ position: relative;
+}
+
+.pageInstance {
+ height: 100%;
+ width: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 1;
+}
+
+.hiddenPreserved {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ visibility: hidden;
+ pointer-events: none;
+ z-index: -1;
+}
+
+.pageContent {
+ height: 100%;
+ width: 100%;
+}
diff --git a/src/components/PageManager/PageManager.tsx b/src/components/PageManager/PageManager.tsx
new file mode 100644
index 0000000..f2fc7c1
--- /dev/null
+++ b/src/components/PageManager/PageManager.tsx
@@ -0,0 +1,192 @@
+import React, { useEffect, useState, Suspense } from 'react';
+import { useLocation } from 'react-router-dom';
+import { motion, AnimatePresence } from 'framer-motion';
+import { pageConfigs } from './pageConfigs';
+import styles from './PageManager.module.css';
+
+interface PageInstance {
+ path: string;
+ component: React.ReactElement;
+ isActive: boolean;
+ shouldPreserve: boolean;
+}
+
+interface PageManagerProps {
+ loadingComponent: React.ComponentType;
+ errorComponent: React.ComponentType;
+}
+
+const PageManager: React.FC = ({ loadingComponent: LoadingComponent, errorComponent: ErrorComponent }) => {
+ const location = useLocation();
+ const [pageInstances, setPageInstances] = useState