Initial commit: breakpilot-compliance - Compliance SDK Platform
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>
This commit is contained in:
343
admin-compliance/lib/sdk/drafting-engine/use-drafting-engine.ts
Normal file
343
admin-compliance/lib/sdk/drafting-engine/use-drafting-engine.ts
Normal file
@@ -0,0 +1,343 @@
|
||||
'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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user