Services: Admin-Compliance, Backend-Compliance, AI-Compliance-SDK, Consent-SDK, Developer-Portal, PCA-Platform, DSMS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
344 lines
10 KiB
TypeScript
344 lines
10 KiB
TypeScript
'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<void>
|
|
requestDraft: (instructions?: string) => Promise<void>
|
|
validateDraft: () => Promise<void>
|
|
acceptDraft: () => void
|
|
stopGeneration: () => void
|
|
clearMessages: () => void
|
|
}
|
|
|
|
export function useDraftingEngine(): DraftingEngineState & DraftingEngineActions {
|
|
const { state, dispatch } = useSDK()
|
|
const abortControllerRef = useRef<AbortController | null>(null)
|
|
|
|
const [currentMode, setCurrentMode] = useState<AgentMode>('explain')
|
|
const [activeDocumentType, setActiveDocumentType] = useState<ScopeDocumentType | null>(null)
|
|
const [messages, setMessages] = useState<DraftingChatMessage[]>([])
|
|
const [isTyping, setIsTyping] = useState(false)
|
|
const [currentDraft, setCurrentDraft] = useState<DraftRevision | null>(null)
|
|
const [validationResult, setValidationResult] = useState<ValidationResult | null>(null)
|
|
const [constraintCheck, setConstraintCheck] = useState<ConstraintCheckResult | null>(null)
|
|
const [error, setError] = useState<string | null>(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,
|
|
}
|
|
}
|