Files

163 lines
5.4 KiB
TypeScript
Raw Permalink Normal View History

'use client';
import { useState, useCallback } from 'react';
import { MessageList } from './MessageList';
import { ChatInput } from './ChatInput';
import { VoiceInput } from './VoiceInput';
import { ActionPreview } from './ActionPreview';
import { ChatHistory } from './ChatHistory';
import {
sendChatMessage,
executeAction,
sendVoiceMessage,
type ChatMessage,
type ActionItem,
} from '@/lib/copilot';
export function ChatInterface() {
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [sessionId, setSessionId] = useState<string | undefined>(undefined);
const [pendingActions, setPendingActions] = useState<ActionItem[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [showHistory, setShowHistory] = useState(false);
const handleSend = useCallback(async (text: string) => {
if (!text.trim() || loading) return;
setLoading(true);
setError(null);
const userMsg: ChatMessage = {
id: `temp-${Date.now()}`,
session_id: sessionId || '',
role: 'user',
content: text,
created_at: new Date().toISOString(),
};
setMessages((prev) => [...prev, userMsg]);
try {
const result = await sendChatMessage(text, sessionId);
setSessionId(result.session_id);
const assistantMsg: ChatMessage = {
id: result.message_id,
session_id: result.session_id,
role: 'assistant',
content: result.response,
actions: result.actions,
created_at: new Date().toISOString(),
};
setMessages((prev) => [...prev, assistantMsg]);
if (result.actions && result.actions.length > 0) {
setPendingActions(result.actions);
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten');
} finally {
setLoading(false);
}
}, [sessionId, loading]);
const handleVoice = useCallback(async (audioBase64: string, mimeType: string) => {
if (loading) return;
setLoading(true);
setError(null);
try {
const result = await sendVoiceMessage(audioBase64, mimeType, sessionId);
setSessionId(result.session_id);
const userMsg: ChatMessage = {
id: `voice-${Date.now()}`,
session_id: result.session_id,
role: 'user',
content: `🎤 ${result.transcription}`,
created_at: new Date().toISOString(),
};
const assistantMsg: ChatMessage = {
id: result.message_id,
session_id: result.session_id,
role: 'assistant',
content: result.response,
actions: result.actions,
created_at: new Date().toISOString(),
};
setMessages((prev) => [...prev, userMsg, assistantMsg]);
if (result.actions && result.actions.length > 0) {
setPendingActions(result.actions);
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Spracherkennung fehlgeschlagen');
} finally {
setLoading(false);
}
}, [sessionId, loading]);
const handleConfirmAction = useCallback(async (action: ActionItem) => {
setLoading(true);
setError(null);
try {
const result = await executeAction(action.type, action.params, sessionId);
const resultMsg: ChatMessage = {
id: `action-${Date.now()}`,
session_id: sessionId || '',
role: 'assistant',
content: `Aktion "${result.action}" ausgefuehrt. ${result.success ? 'Erfolgreich.' : 'Fehlgeschlagen.'}`,
created_at: new Date().toISOString(),
};
setMessages((prev) => [...prev, resultMsg]);
setPendingActions((prev) => prev.filter((a) => a !== action));
} catch (err) {
setError(err instanceof Error ? err.message : 'Aktion fehlgeschlagen');
} finally {
setLoading(false);
}
}, [sessionId]);
const handleDismissAction = useCallback((action: ActionItem) => {
setPendingActions((prev) => prev.filter((a) => a !== action));
}, []);
const handleSelectSession = useCallback((selectedSessionId: string) => {
setSessionId(selectedSessionId);
setMessages([]);
setPendingActions([]);
setShowHistory(false);
}, []);
return (
<div className="flex gap-4 h-[calc(100vh-12rem)]">
{showHistory && (
<ChatHistory
onSelectSession={handleSelectSession}
onClose={() => setShowHistory(false)}
/>
)}
<div className="flex-1 flex flex-col">
<div className="flex items-center justify-between mb-2">
<button
onClick={() => setShowHistory(!showHistory)}
className="text-sm text-blue-600 hover:underline"
data-testid="toggle-history"
>
{showHistory ? 'Verlauf ausblenden' : 'Verlauf anzeigen'}
</button>
</div>
<MessageList messages={messages} loading={loading} />
{error && (
<div className="text-red-600 text-sm p-2" data-testid="chat-error">
{error}
</div>
)}
{pendingActions.length > 0 && (
<ActionPreview
actions={pendingActions}
onConfirm={handleConfirmAction}
onDismiss={handleDismissAction}
/>
)}
<div className="flex gap-2 items-end pt-2">
<ChatInput onSend={handleSend} disabled={loading} />
<VoiceInput onVoice={handleVoice} disabled={loading} />
</div>
</div>
</div>
);
}