Files
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

328 lines
8.3 KiB
TypeScript

/**
* SDK Backend Client
*
* Client for communicating with the SDK Backend (Go service)
* for RAG search and document generation.
*/
// =============================================================================
// TYPES
// =============================================================================
export interface SearchResult {
id: string
content: string
source: string
score: number
metadata?: Record<string, string>
highlights?: string[]
}
export interface SearchResponse {
query: string
topK: number
results: SearchResult[]
source: 'qdrant' | 'mock'
}
export interface CorpusStatus {
status: 'ready' | 'unavailable' | 'indexing'
collections: string[]
documents: number
lastUpdated?: string
}
export interface GenerateRequest {
tenantId: string
context: Record<string, unknown>
template?: string
language?: 'de' | 'en'
useRag?: boolean
ragQuery?: string
maxTokens?: number
temperature?: number
}
export interface GenerateResponse {
content: string
generatedAt: string
model: string
tokensUsed: number
ragSources?: SearchResult[]
confidence?: number
}
export interface SDKBackendResponse<T> {
success: boolean
data?: T
error?: string
code?: string
}
// =============================================================================
// CONFIGURATION
// =============================================================================
const SDK_BACKEND_URL = process.env.NEXT_PUBLIC_SDK_BACKEND_URL || 'http://localhost:8085'
const DEFAULT_TIMEOUT = 60000 // 60 seconds for generation
// =============================================================================
// SDK BACKEND CLIENT
// =============================================================================
export class SDKBackendClient {
private baseUrl: string
private timeout: number
constructor(options: {
baseUrl?: string
timeout?: number
} = {}) {
this.baseUrl = options.baseUrl || SDK_BACKEND_URL
this.timeout = options.timeout || DEFAULT_TIMEOUT
}
// ---------------------------------------------------------------------------
// Private Methods
// ---------------------------------------------------------------------------
private async fetch<T>(
path: string,
options: RequestInit = {}
): Promise<T> {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), this.timeout)
try {
const response = await fetch(`${this.baseUrl}${path}`, {
...options,
signal: controller.signal,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
})
const data = await response.json() as SDKBackendResponse<T>
if (!response.ok || !data.success) {
throw new Error(data.error || `HTTP ${response.status}`)
}
return data.data as T
} finally {
clearTimeout(timeoutId)
}
}
// ---------------------------------------------------------------------------
// RAG Search
// ---------------------------------------------------------------------------
/**
* Search the legal corpus using semantic search
*/
async search(
query: string,
options: {
topK?: number
collection?: string
filter?: string
} = {}
): Promise<SearchResponse> {
const params = new URLSearchParams({
q: query,
top_k: String(options.topK || 5),
})
if (options.collection) {
params.set('collection', options.collection)
}
if (options.filter) {
params.set('filter', options.filter)
}
return this.fetch<SearchResponse>(`/sdk/v1/rag/search?${params}`)
}
/**
* Get the status of the legal corpus
*/
async getCorpusStatus(): Promise<CorpusStatus> {
return this.fetch<CorpusStatus>('/sdk/v1/rag/status')
}
/**
* Index a new document into the corpus
*/
async indexDocument(
collection: string,
id: string,
content: string,
metadata?: Record<string, string>
): Promise<{ indexed: boolean; id: string }> {
return this.fetch('/sdk/v1/rag/index', {
method: 'POST',
body: JSON.stringify({
collection,
id,
content,
metadata,
}),
})
}
// ---------------------------------------------------------------------------
// Document Generation
// ---------------------------------------------------------------------------
/**
* Generate a Data Protection Impact Assessment (DSFA)
*/
async generateDSFA(request: GenerateRequest): Promise<GenerateResponse> {
return this.fetch<GenerateResponse>('/sdk/v1/generate/dsfa', {
method: 'POST',
body: JSON.stringify(request),
})
}
/**
* Generate Technical and Organizational Measures (TOM)
*/
async generateTOM(request: GenerateRequest): Promise<GenerateResponse> {
return this.fetch<GenerateResponse>('/sdk/v1/generate/tom', {
method: 'POST',
body: JSON.stringify(request),
})
}
/**
* Generate a Processing Activity Register (VVT)
*/
async generateVVT(request: GenerateRequest): Promise<GenerateResponse> {
return this.fetch<GenerateResponse>('/sdk/v1/generate/vvt', {
method: 'POST',
body: JSON.stringify(request),
})
}
/**
* Generate an expert opinion/assessment (Gutachten)
*/
async generateGutachten(request: GenerateRequest): Promise<GenerateResponse> {
return this.fetch<GenerateResponse>('/sdk/v1/generate/gutachten', {
method: 'POST',
body: JSON.stringify(request),
})
}
// ---------------------------------------------------------------------------
// Health Check
// ---------------------------------------------------------------------------
/**
* Check if the SDK backend is healthy
*/
async healthCheck(): Promise<{
status: string
timestamp: string
services: {
database: boolean
rag: boolean
llm: boolean
}
}> {
try {
const response = await fetch(`${this.baseUrl}/health`, {
method: 'GET',
})
return response.json()
} catch {
return {
status: 'unhealthy',
timestamp: new Date().toISOString(),
services: {
database: false,
rag: false,
llm: false,
},
}
}
}
}
// =============================================================================
// SINGLETON INSTANCE
// =============================================================================
let clientInstance: SDKBackendClient | null = null
export function getSDKBackendClient(): SDKBackendClient {
if (!clientInstance) {
clientInstance = new SDKBackendClient()
}
return clientInstance
}
export function resetSDKBackendClient(): void {
clientInstance = null
}
// =============================================================================
// UTILITY FUNCTIONS
// =============================================================================
/**
* Check if a query is likely a legal/compliance search
*/
export function isLegalQuery(query: string): boolean {
const legalKeywords = [
'dsgvo', 'gdpr', 'datenschutz', 'privacy',
'ai act', 'ki-gesetz', 'ai-verordnung',
'nis2', 'cybersicherheit',
'artikel', 'article', 'art.',
'verordnung', 'regulation', 'richtlinie', 'directive',
'gesetz', 'law', 'compliance',
'dsfa', 'dpia', 'folgenabschätzung',
'tom', 'maßnahmen', 'measures',
'vvt', 'verarbeitungsverzeichnis',
]
const normalizedQuery = query.toLowerCase()
return legalKeywords.some(keyword => normalizedQuery.includes(keyword))
}
/**
* Extract regulation references from text
*/
export function extractRegulationReferences(text: string): Array<{
regulation: string
article: string
fullReference: string
}> {
const references: Array<{
regulation: string
article: string
fullReference: string
}> = []
// Match patterns like "Art. 5 DSGVO", "Article 6 GDPR", "Art. 35 Abs. 1 DSGVO"
const patterns = [
/Art(?:ikel|\.|\s)*(\d+)(?:\s*Abs\.\s*(\d+))?\s*(DSGVO|GDPR|AI\s*Act|NIS2)/gi,
/Article\s*(\d+)(?:\s*para(?:graph)?\s*(\d+))?\s*(DSGVO|GDPR|AI\s*Act|NIS2)/gi,
]
for (const pattern of patterns) {
let match
while ((match = pattern.exec(text)) !== null) {
references.push({
regulation: match[3].toUpperCase().replace(/\s+/g, ' '),
article: match[2] ? `${match[1]} Abs. ${match[2]}` : match[1],
fullReference: match[0],
})
}
}
return references
}