'use client' import { useCallback, useState } from 'react' /** * Chat message type */ export interface ChatMessage { id: string role: 'user' | 'assistant' content: string } /** * Hook state */ interface UseDocsChatState { messages: ChatMessage[] isLoading: boolean isStreaming: boolean error: string | null } /** * Hook return type */ interface UseDocsChatReturn extends UseDocsChatState { sendMessage: (content: string) => Promise clearMessages: () => void clearError: () => void } /** * Generate unique message ID */ function generateId(): string { return `msg_${Date.now()}_${Math.random().toString(36).slice(2, 9)}` } /** * Welcome message shown on first load */ const WELCOME_MESSAGE: ChatMessage = { id: 'welcome', role: 'assistant', content: 'Hallo! Ik ben de documentatie assistent voor het Mini-EPD. Stel me een vraag over het systeem, bijvoorbeeld:\n\n• Hoe maak ik een intake aan?\n• Hoe werkt de spraakherkenning?\n• Waar vind ik het behandelplan?', } /** * Custom hook for docs chat functionality * * Features: * - Message state management * - Streaming responses from Claude API * - Loading and error states * - Welcome message on init * * @example * const { messages, isLoading, sendMessage } = useDocsChat() * await sendMessage("Hoe maak ik een intake aan?") */ export function useDocsChat(): UseDocsChatReturn { const [state, setState] = useState({ messages: [WELCOME_MESSAGE], isLoading: false, isStreaming: false, error: null, }) const sendMessage = useCallback(async (content: string) => { const trimmedContent = content.trim() if (!trimmedContent) return // Add user message const userMessage: ChatMessage = { id: generateId(), role: 'user', content: trimmedContent, } // Prepare assistant message placeholder const assistantMessage: ChatMessage = { id: generateId(), role: 'assistant', content: '', } setState((prev) => ({ ...prev, messages: [...prev.messages, userMessage, assistantMessage], isLoading: true, isStreaming: false, error: null, })) try { // Get message history (excluding welcome and current messages) const history = state.messages .filter((m) => m.id !== 'welcome') .map((m) => ({ role: m.role, content: m.content })) const response = await fetch('/api/docs/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: history, userMessage: trimmedContent, }), }) if (!response.ok) { const errorData = await response.json().catch(() => ({})) throw new Error(errorData.error || `Fout: ${response.status}`) } if (!response.body) { throw new Error('Geen response body ontvangen') } // Start streaming setState((prev) => ({ ...prev, isLoading: false, isStreaming: true, })) const reader = response.body.getReader() const decoder = new TextDecoder() let accumulatedContent = '' while (true) { const { done, value } = await reader.read() if (done) break const chunk = decoder.decode(value, { stream: true }) const lines = chunk.split('\n') for (const line of lines) { if (!line.startsWith('data: ')) continue const data = line.slice(6) if (data === '[DONE]') continue try { const parsed = JSON.parse(data) // Handle content_block_delta events if (parsed.type === 'content_block_delta' && parsed.delta?.text) { accumulatedContent += parsed.delta.text setState((prev) => ({ ...prev, messages: prev.messages.map((m) => m.id === assistantMessage.id ? { ...m, content: accumulatedContent } : m ), })) } } catch { // Ignore parse errors for non-JSON lines } } } // Streaming complete setState((prev) => ({ ...prev, isStreaming: false, })) } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Er ging iets mis' setState((prev) => ({ ...prev, isLoading: false, isStreaming: false, error: errorMessage, // Remove empty assistant message on error messages: prev.messages.filter( (m) => m.id !== assistantMessage.id || m.content.length > 0 ), })) } }, [state.messages]) const clearMessages = useCallback(() => { setState({ messages: [WELCOME_MESSAGE], isLoading: false, isStreaming: false, error: null, }) }, []) const clearError = useCallback(() => { setState((prev) => ({ ...prev, error: null })) }, []) return { ...state, sendMessage, clearMessages, clearError, } }