frontend_nyla/src/components/Dashboard/DashboardChat/DashboardChatArea/DashboardChatArea.tsx
2025-05-28 09:48:06 +02:00

278 lines
No EOL
12 KiB
TypeScript

import React, { useState, useEffect, useRef } from "react";
import { motion } from "framer-motion";
import { LuSendHorizontal } from "react-icons/lu";
import { Prompt } from "../../../../hooks/usePrompts";
import { useWorkflowOperations, useWorkflowMessages, useWorkflowStatus } from "../../../../hooks/useWorkflows";
import styles from './DashboardChatArea.module.css';
interface DashboardChatAreaProps {
selectedPrompt?: Prompt | null;
onPromptUsed?: () => void;
onWorkflowIdChange?: (workflowId: string | null) => void;
resumeWorkflowId?: string | null;
}
const DashboardChatArea: React.FC<DashboardChatAreaProps> = ({
selectedPrompt,
onPromptUsed,
onWorkflowIdChange,
resumeWorkflowId
}) => {
const [inputValue, setInputValue] = useState("");
const [currentWorkflowId, setCurrentWorkflowId] = useState<string | null>(null);
const [workflowCompleted, setWorkflowCompleted] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
const { startWorkflow, startingWorkflow, startError } = useWorkflowOperations();
const { messages, loading: messagesLoading, error: messagesError, refetch: refetchMessages } = useWorkflowMessages(currentWorkflowId);
const { status: workflowStatus, refetch: refetchStatus } = useWorkflowStatus(currentWorkflowId);
// Update input value when a prompt is selected
useEffect(() => {
if (selectedPrompt) {
setInputValue(selectedPrompt.content);
// Focus the input field
if (inputRef.current) {
inputRef.current.focus();
}
}
}, [selectedPrompt]);
// Auto-scroll to bottom when new messages arrive
useEffect(() => {
if (messagesEndRef.current) {
messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
}
}, [messages]);
// Polling logic for fetching messages and status
useEffect(() => {
if (!currentWorkflowId || workflowCompleted) return;
const interval = setInterval(() => {
refetchMessages();
refetchStatus();
}, 1000); // Poll every second
return () => clearInterval(interval);
}, [currentWorkflowId, workflowCompleted, refetchMessages, refetchStatus]);
// Check if workflow is completed based on status or messages
useEffect(() => {
if (workflowStatus && (
workflowStatus.status === 'completed' ||
workflowStatus.status === 'finished' ||
workflowStatus.status === 'done' ||
workflowStatus.status === 'stopped'
)) {
setWorkflowCompleted(true);
return;
}
if (messages.length > 0) {
const lastMessage = messages[messages.length - 1];
// Check if the last message indicates completion
if (lastMessage.role === 'assistant' &&
(lastMessage.content.toLowerCase().includes('completed') ||
lastMessage.content.toLowerCase().includes('finished') ||
lastMessage.content.toLowerCase().includes('done') ||
lastMessage.content.toLowerCase().includes('workflow completed'))) {
setWorkflowCompleted(true);
}
}
}, [messages, workflowStatus]);
const handleSend = async () => {
if (inputValue.trim()) {
console.log('Sending message:', inputValue);
try {
let result;
// If we have a completed workflow, send as follow-up using the existing workflow ID
if (workflowCompleted && currentWorkflowId) {
console.log('Sending follow-up message to workflow:', currentWorkflowId);
result = await startWorkflow({
prompt: inputValue,
listFileId: []
}, currentWorkflowId);
if (result.success) {
console.log('Follow-up message sent successfully');
// Reset workflow completion state to resume polling
setWorkflowCompleted(false);
}
} else {
// Start a new workflow
console.log('Starting new workflow');
// Reset previous workflow state when starting a new one
setCurrentWorkflowId(null);
setWorkflowCompleted(false);
result = await startWorkflow({
prompt: inputValue,
listFileId: []
});
if (result.success && result.data) {
console.log('Workflow started successfully:', result.data);
// Set the workflow ID to start polling for messages
setCurrentWorkflowId(result.data.id);
setWorkflowCompleted(false);
}
}
if (result.success) {
// Clear the input after successful send
setInputValue("");
// Call onPromptUsed if a prompt was used
if (selectedPrompt && onPromptUsed) {
onPromptUsed();
}
} else {
console.error('Failed to send message:', result.error);
}
} catch (error) {
console.error('Error sending message:', error);
}
}
};
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSend();
}
};
const startNewWorkflow = () => {
setCurrentWorkflowId(null);
setWorkflowCompleted(false);
setInputValue("");
if (onWorkflowIdChange) {
onWorkflowIdChange(null);
}
};
useEffect(() => {
if (currentWorkflowId && onWorkflowIdChange) {
onWorkflowIdChange(currentWorkflowId);
}
}, [currentWorkflowId, onWorkflowIdChange]);
// Handle workflow resumption
useEffect(() => {
if (resumeWorkflowId && resumeWorkflowId !== currentWorkflowId) {
console.log('Resuming workflow:', resumeWorkflowId);
setCurrentWorkflowId(resumeWorkflowId);
setWorkflowCompleted(false);
setInputValue("");
}
}, [resumeWorkflowId, currentWorkflowId]);
return (
<div className={styles.chat_area}>
<motion.div
className={styles.chat_messages}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2, duration: 0.3, ease: "easeOut" }}
>
<div className={styles.messages_container}>
{startingWorkflow && (
<div className={styles.loading_message}>
<p>{workflowCompleted && currentWorkflowId ? 'Sending follow-up message...' : 'Sending message...'}</p>
</div>
)}
{startError && (
<div className={styles.error_message}>
<p>Error: {startError}</p>
</div>
)}
{messagesError && (
<div className={styles.error_message}>
<p>Error loading messages: {messagesError}</p>
</div>
)}
{currentWorkflowId && messagesLoading && messages.length === 0 && (
<div className={styles.loading_message}>
<p>Loading workflow messages...</p>
</div>
)}
{messages.length > 0 ? (
messages.map((message, index) => (
<div
key={message.id || index}
className={`${styles.message} ${styles[`message_${message.role}`]}`}
>
<div className={styles.message_role}>
{message.role === 'user' ? 'You' :
message.role === 'assistant' ? 'Assistant' : 'System'}
</div>
<div className={styles.message_content}>
{message.content}
</div>
{message.timestamp && (
<div className={styles.message_timestamp}>
{new Date(message.timestamp).toLocaleTimeString()}
</div>
)}
</div>
))
) : !currentWorkflowId ? (
<p className={styles.placeholder_text}>Start a conversation by typing a message...</p>
) : null}
{currentWorkflowId && !workflowCompleted && (
<div className={styles.workflow_status}>
<p>
Workflow {currentWorkflowId.substring(0, 8)}... is {workflowStatus?.status || 'running'}
{workflowStatus?.currentRound && ` (Round ${workflowStatus.currentRound})`}
</p>
</div>
)}
{workflowCompleted && (
<div className={styles.completion_message}>
<p>Workflow completed! You can continue the conversation or start a new workflow.</p>
<button
className={styles.new_workflow_button}
onClick={startNewWorkflow}
>
Start New Workflow
</button>
</div>
)}
<div ref={messagesEndRef} />
</div>
</motion.div>
<motion.div
className={styles.chat_input}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3, duration: 0.3, ease: "easeOut" }}
>
<input
ref={inputRef}
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={handleKeyPress}
placeholder={workflowCompleted ? "Continue the conversation..." : "Type your message..."}
className={styles.message_input}
disabled={startingWorkflow}
/>
<motion.button
className={styles.send_button}
onClick={handleSend}
disabled={startingWorkflow || !inputValue.trim()}
whileTap={{ scale: 0.95 }}
transition={{ duration: 0.2, ease: "easeOut" }}
>
<LuSendHorizontal className={styles.send_button_icon}/>
</motion.button>
</motion.div>
</div>
);
};
export default DashboardChatArea;