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>
328 lines
8.3 KiB
TypeScript
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
|
|
}
|