'use client' /** * useDraftingEngine - React Hook fuer die Drafting Engine * * Managed: currentMode, activeDocumentType, draftSessions, validationState * Handled: State-Projection, API-Calls, Streaming * Provides: sendMessage(), requestDraft(), validateDraft(), acceptDraft() */ import { useState, useCallback, useRef } from 'react' import { useSDK } from '../context' import { stateProjector } from './state-projector' import { intentClassifier } from './intent-classifier' import { constraintEnforcer } from './constraint-enforcer' import type { AgentMode, DraftSession, DraftRevision, DraftingChatMessage, ValidationResult, ConstraintCheckResult, DraftContext, GapContext, ValidationContext, } from './types' import type { ScopeDocumentType } from '../compliance-scope-types' export interface DraftingEngineState { currentMode: AgentMode activeDocumentType: ScopeDocumentType | null messages: DraftingChatMessage[] isTyping: boolean currentDraft: DraftRevision | null validationResult: ValidationResult | null constraintCheck: ConstraintCheckResult | null error: string | null } export interface DraftingEngineActions { setMode: (mode: AgentMode) => void setDocumentType: (type: ScopeDocumentType) => void sendMessage: (content: string) => Promise requestDraft: (instructions?: string) => Promise validateDraft: () => Promise acceptDraft: () => void stopGeneration: () => void clearMessages: () => void } export function useDraftingEngine(): DraftingEngineState & DraftingEngineActions { const { state, dispatch } = useSDK() const abortControllerRef = useRef(null) const [currentMode, setCurrentMode] = useState('explain') const [activeDocumentType, setActiveDocumentType] = useState(null) const [messages, setMessages] = useState([]) const [isTyping, setIsTyping] = useState(false) const [currentDraft, setCurrentDraft] = useState(null) const [validationResult, setValidationResult] = useState(null) const [constraintCheck, setConstraintCheck] = useState(null) const [error, setError] = useState(null) // Get state projection based on mode const getProjection = useCallback(() => { switch (currentMode) { case 'draft': return activeDocumentType ? stateProjector.projectForDraft(state, activeDocumentType) : null case 'ask': return stateProjector.projectForAsk(state) case 'validate': return activeDocumentType ? stateProjector.projectForValidate(state, [activeDocumentType]) : stateProjector.projectForValidate(state, ['vvt', 'tom', 'lf']) default: return activeDocumentType ? stateProjector.projectForDraft(state, activeDocumentType) : null } }, [state, currentMode, activeDocumentType]) const setMode = useCallback((mode: AgentMode) => { setCurrentMode(mode) }, []) const setDocumentType = useCallback((type: ScopeDocumentType) => { setActiveDocumentType(type) }, []) const sendMessage = useCallback(async (content: string) => { if (!content.trim() || isTyping) return setError(null) // Auto-detect mode if needed const classification = intentClassifier.classify(content) if (classification.confidence > 0.7 && classification.mode !== currentMode) { setCurrentMode(classification.mode) } if (classification.detectedDocumentType && !activeDocumentType) { setActiveDocumentType(classification.detectedDocumentType) } const userMessage: DraftingChatMessage = { role: 'user', content: content.trim(), } setMessages(prev => [...prev, userMessage]) setIsTyping(true) abortControllerRef.current = new AbortController() try { const projection = getProjection() const response = await fetch('/api/sdk/drafting-engine/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: content.trim(), history: messages.map(m => ({ role: m.role, content: m.content })), sdkStateProjection: projection, mode: currentMode, documentType: activeDocumentType, }), signal: abortControllerRef.current.signal, }) if (!response.ok) { const errorData = await response.json().catch(() => ({ error: 'Unbekannter Fehler' })) throw new Error(errorData.error || `Server-Fehler (${response.status})`) } const agentMessageId = `msg-${Date.now()}-agent` setMessages(prev => [...prev, { role: 'assistant', content: '', metadata: { mode: currentMode, documentType: activeDocumentType ?? undefined }, }]) // Stream response const reader = response.body!.getReader() const decoder = new TextDecoder() let accumulated = '' while (true) { const { done, value } = await reader.read() if (done) break accumulated += decoder.decode(value, { stream: true }) const text = accumulated setMessages(prev => prev.map((m, i) => i === prev.length - 1 ? { ...m, content: text } : m) ) } setIsTyping(false) } catch (err) { if ((err as Error).name === 'AbortError') { setIsTyping(false) return } setError((err as Error).message) setMessages(prev => [...prev, { role: 'assistant', content: `Fehler: ${(err as Error).message}`, }]) setIsTyping(false) } }, [isTyping, messages, currentMode, activeDocumentType, getProjection]) const requestDraft = useCallback(async (instructions?: string) => { if (!activeDocumentType) { setError('Bitte waehlen Sie zuerst einen Dokumenttyp.') return } setError(null) setIsTyping(true) try { const draftContext = stateProjector.projectForDraft(state, activeDocumentType) const response = await fetch('/api/sdk/drafting-engine/draft', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ documentType: activeDocumentType, draftContext, instructions, existingDraft: currentDraft, }), }) const result = await response.json() if (!response.ok) { throw new Error(result.error || 'Draft-Generierung fehlgeschlagen') } setCurrentDraft(result.draft) setConstraintCheck(result.constraintCheck) setMessages(prev => [...prev, { role: 'assistant', content: `Draft fuer ${activeDocumentType} erstellt (${result.draft.sections.length} Sections). Oeffnen Sie den Editor zur Bearbeitung.`, metadata: { mode: 'draft', documentType: activeDocumentType, hasDraft: true }, }]) setIsTyping(false) } catch (err) { setError((err as Error).message) setIsTyping(false) } }, [activeDocumentType, state, currentDraft]) const validateDraft = useCallback(async () => { setError(null) setIsTyping(true) try { const docTypes: ScopeDocumentType[] = activeDocumentType ? [activeDocumentType] : ['vvt', 'tom', 'lf'] const validationContext = stateProjector.projectForValidate(state, docTypes) const response = await fetch('/api/sdk/drafting-engine/validate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ documentType: activeDocumentType || 'vvt', draftContent: currentDraft?.content || '', validationContext, }), }) const result = await response.json() if (!response.ok) { throw new Error(result.error || 'Validierung fehlgeschlagen') } setValidationResult(result) const summary = result.passed ? `Validierung bestanden. ${result.warnings.length} Warnungen, ${result.suggestions.length} Vorschlaege.` : `Validierung fehlgeschlagen. ${result.errors.length} Fehler, ${result.warnings.length} Warnungen.` setMessages(prev => [...prev, { role: 'assistant', content: summary, metadata: { mode: 'validate', hasValidation: true }, }]) setIsTyping(false) } catch (err) { setError((err as Error).message) setIsTyping(false) } }, [activeDocumentType, state, currentDraft]) const acceptDraft = useCallback(() => { if (!currentDraft || !activeDocumentType) return // Dispatch the draft data into SDK state switch (activeDocumentType) { case 'vvt': dispatch({ type: 'ADD_PROCESSING_ACTIVITY', payload: { id: `draft-vvt-${Date.now()}`, name: currentDraft.sections.find(s => s.schemaField === 'name')?.content || 'Neuer VVT-Eintrag', ...Object.fromEntries( currentDraft.sections .filter(s => s.schemaField) .map(s => [s.schemaField!, s.content]) ), }, }) break case 'tom': dispatch({ type: 'ADD_TOM', payload: { id: `draft-tom-${Date.now()}`, name: 'TOM-Entwurf', ...Object.fromEntries( currentDraft.sections .filter(s => s.schemaField) .map(s => [s.schemaField!, s.content]) ), }, }) break default: dispatch({ type: 'ADD_DOCUMENT', payload: { id: `draft-${activeDocumentType}-${Date.now()}`, type: activeDocumentType, content: currentDraft.content, sections: currentDraft.sections, }, }) } setMessages(prev => [...prev, { role: 'assistant', content: `Draft wurde in den SDK-State uebernommen.`, }]) setCurrentDraft(null) }, [currentDraft, activeDocumentType, dispatch]) const stopGeneration = useCallback(() => { abortControllerRef.current?.abort() setIsTyping(false) }, []) const clearMessages = useCallback(() => { setMessages([]) setCurrentDraft(null) setValidationResult(null) setConstraintCheck(null) setError(null) }, []) return { currentMode, activeDocumentType, messages, isTyping, currentDraft, validationResult, constraintCheck, error, setMode, setDocumentType, sendMessage, requestDraft, validateDraft, acceptDraft, stopGeneration, clearMessages, } }