feat(T07): KI-Copilot with OpenRouter chat, voice input, action system, chat history
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { getChatHistory, type ChatMessage } from '@/lib/copilot';
|
||||
|
||||
interface ChatHistoryProps {
|
||||
onSelectSession: (sessionId: string) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface SessionGroup {
|
||||
sessionId: string;
|
||||
firstMessage: string;
|
||||
messageCount: number;
|
||||
lastActivity: string;
|
||||
}
|
||||
|
||||
export function ChatHistory({ onSelectSession, onClose }: ChatHistoryProps) {
|
||||
const [sessions, setSessions] = useState<SessionGroup[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loadHistory = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const result = await getChatHistory(1, 100);
|
||||
const sessionMap = new Map<string, ChatMessage[]>();
|
||||
for (const msg of result.items) {
|
||||
const existing = sessionMap.get(msg.session_id) || [];
|
||||
existing.push(msg);
|
||||
sessionMap.set(msg.session_id, existing);
|
||||
}
|
||||
const groups: SessionGroup[] = [];
|
||||
for (const [sessionId, msgs] of sessionMap) {
|
||||
const userMsg = msgs.find((m) => m.role === 'user');
|
||||
groups.push({
|
||||
sessionId,
|
||||
firstMessage: userMsg?.content || 'Unbekannt',
|
||||
messageCount: msgs.length,
|
||||
lastActivity: msgs[msgs.length - 1]?.created_at || '',
|
||||
});
|
||||
}
|
||||
groups.sort((a, b) => b.lastActivity.localeCompare(a.lastActivity));
|
||||
setSessions(groups);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Verlauf konnte nicht geladen werden');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
loadHistory();
|
||||
}, []);
|
||||
|
||||
const handleSelect = useCallback((sessionId: string) => {
|
||||
onSelectSession(sessionId);
|
||||
}, [onSelectSession]);
|
||||
|
||||
return (
|
||||
<div className="w-64 border-r border-gray-200 pr-4" data-testid="chat-history">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h2 className="font-semibold text-sm">Konversationen</h2>
|
||||
<button onClick={onClose} className="text-gray-400 hover:text-gray-600 text-sm" data-testid="close-history">
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
{loading && <p className="text-sm text-gray-400" data-testid="history-loading">Wird geladen...</p>}
|
||||
{error && <p className="text-sm text-red-600" data-testid="history-error">{error}</p>}
|
||||
{!loading && !error && sessions.length === 0 && (
|
||||
<p className="text-sm text-gray-400" data-testid="history-empty">Keine Konversationen vorhanden.</p>
|
||||
)}
|
||||
{!loading && !error && sessions.length > 0 && (
|
||||
<ul className="space-y-1">
|
||||
{sessions.map((session) => (
|
||||
<li key={session.sessionId}>
|
||||
<button
|
||||
onClick={() => handleSelect(session.sessionId)}
|
||||
className="w-full text-left p-2 rounded hover:bg-gray-100 text-sm"
|
||||
data-testid={`history-session-${session.sessionId}`}
|
||||
>
|
||||
<p className="font-medium truncate">{session.firstMessage}</p>
|
||||
<p className="text-xs text-gray-400">{session.messageCount} Nachrichten</p>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user