ui-nyla/src/pages/views/chatbot/ChatbotConversationsView.tsx

253 lines
8.2 KiB
TypeScript

/**
* ChatbotConversationsView
*
* Chatbot interface with chat history sidebar and messages view.
* Similar to trustee views but hardcoded for chatbot feature.
*/
import React, { useState } from 'react';
import { useChatbot } from '../../../hooks/useChatbot';
import { Messages } from '../../../components/UiComponents/Messages';
import { TextField } from '../../../components/UiComponents/TextField';
import { Button } from '../../../components/UiComponents/Button';
import { AutoScroll } from '../../../components/UiComponents/AutoScroll';
import { ChatMessage } from '../../../components/UiComponents/Messages/ChatMessages/ChatMessage';
import { Message } from '../../../components/UiComponents/Messages/MessagesTypes';
import { IoMdSend } from 'react-icons/io';
import { MdStop } from 'react-icons/md';
import { LuMessageSquare, LuTrash2 } from 'react-icons/lu';
import messagesStyles from '../../../components/UiComponents/Messages/Messages.module.css';
import styles from './ChatbotViews.module.css';
export const ChatbotConversationsView: React.FC = () => {
const {
threads,
selectedThreadId,
loadingThreads,
error,
messages,
loadingMessages,
isStreaming,
currentWorkflowId,
selectThread,
createNewThread,
sendMessage,
stopStreaming,
deleteThread,
refreshThreads,
inputValue,
setInputValue
} = useChatbot();
const [deletingId, setDeletingId] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!inputValue.trim() || isStreaming) return;
await sendMessage(inputValue);
};
const handleStop = async () => {
console.log('Stop button clicked', {
isStreaming,
currentWorkflowId,
selectedThreadId,
hasMessages: messages.length > 0
});
if (isStreaming) {
console.log('Calling stopStreaming...');
try {
await stopStreaming();
console.log('stopStreaming completed');
} catch (error) {
console.error('Error in stopStreaming:', error);
}
} else {
console.warn('Stop button clicked but not streaming');
}
};
const handleDeleteThread = async (e: React.MouseEvent, workflowId: string) => {
e.stopPropagation();
if (window.confirm('Möchten Sie diese Konversation wirklich löschen?')) {
setDeletingId(workflowId);
try {
await deleteThread(workflowId);
} finally {
setDeletingId(null);
}
}
};
const formatDate = (timestamp?: number) => {
if (!timestamp) return '';
const date = new Date(timestamp);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMs / 3600000);
const diffDays = Math.floor(diffMs / 86400000);
if (diffMins < 1) return 'Gerade eben';
if (diffMins < 60) return `Vor ${diffMins} Min`;
if (diffHours < 24) return `Vor ${diffHours} Std`;
if (diffDays < 7) return `Vor ${diffDays} Tagen`;
return date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
};
const getThreadTitle = (thread: any) => {
if (thread.name) return thread.name;
// Try to get first message content as title
return 'Neue Konversation';
};
return (
<div className={styles.chatbotView}>
{/* Chat History Sidebar */}
<aside className={styles.chatHistory}>
<div className={styles.chatHistoryHeader}>
<h2 className={styles.chatHistoryTitle}>Konversationen</h2>
<button
className={styles.newChatButton}
onClick={createNewThread}
title="Neue Konversation"
>
<LuMessageSquare /> Neu
</button>
</div>
{loadingThreads ? (
<div className={styles.loading}>
<div className={styles.spinner} />
<span>Lade Konversationen...</span>
</div>
) : error ? (
<div className={styles.error}>
<p>{error}</p>
<button className={styles.retryButton} onClick={refreshThreads}>
Erneut versuchen
</button>
</div>
) : threads.length === 0 ? (
<div className={styles.emptyState}>
<LuMessageSquare className={styles.emptyIcon} />
<p>Noch keine Konversationen vorhanden.</p>
<p className={styles.emptyHint}>Starte eine neue Konversation, um zu beginnen.</p>
</div>
) : (
<div className={styles.threadList}>
{threads.map((thread) => (
<div
key={thread.id}
className={`${styles.threadItem} ${selectedThreadId === thread.id ? styles.selected : ''}`}
onClick={() => selectThread(thread.id)}
>
<div className={styles.threadContent}>
<div className={styles.threadTitle}>{getThreadTitle(thread)}</div>
<div className={styles.threadMeta}>
{formatDate(thread.lastActivity || thread.startedAt)}
</div>
</div>
<button
className={styles.deleteButton}
onClick={(e) => handleDeleteThread(e, thread.id)}
disabled={deletingId === thread.id}
title="Löschen"
>
{deletingId === thread.id ? (
<div className={styles.spinner} />
) : (
<LuTrash2 />
)}
</button>
</div>
))}
</div>
)}
</aside>
{/* Main Chat Area */}
<main className={styles.chatArea}>
{/* Messages Area */}
<div className={styles.messagesArea}>
{loadingMessages && messages.length === 0 ? (
<div className={styles.loading}>
<div className={styles.spinner} />
<span>Lade Nachrichten...</span>
</div>
) : messages.length === 0 ? (
<div className={`${messagesStyles.messagesContainer} ${messagesStyles.emptyContainer}`}>
<div className={messagesStyles.emptyState}>
Noch keine Nachrichten. Starte eine Konversation!
</div>
</div>
) : (
<AutoScroll
scrollDependency={messages.length + (isStreaming ? 1 : 0)}
>
<div className={messagesStyles.messagesContainer}>
{messages.map((message) => (
<ChatMessage
key={message.id}
message={message}
showDocuments={true}
showMetadata={false}
showProgress={false}
/>
))}
{isStreaming && (
<div className={styles.typingIndicator}>
<div className={styles.typingBubble}>
<div className={styles.typingDots}>
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
)}
</div>
</AutoScroll>
)}
</div>
{/* Input Form */}
<form onSubmit={handleSubmit} className={styles.inputForm}>
<TextField
value={inputValue}
onChange={setInputValue}
placeholder="Nachricht eingeben..."
disabled={isStreaming}
className={styles.inputField}
size="md"
/>
{isStreaming ? (
<Button
type="button"
onClick={handleStop}
variant="danger"
size="md"
icon={MdStop}
disabled={!isStreaming}
>
Stoppen
</Button>
) : (
<Button
type="submit"
disabled={!inputValue.trim() || isStreaming}
variant="primary"
size="md"
icon={IoMdSend}
>
Senden
</Button>
)}
</form>
</main>
</div>
);
};
export default ChatbotConversationsView;