278 lines
No EOL
12 KiB
TypeScript
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;
|