fix(admin-v2): Restore complete admin-v2 application
The admin-v2 application was incomplete in the repository. This commit restores all missing components: - Admin pages (76 pages): dashboard, ai, compliance, dsgvo, education, infrastructure, communication, development, onboarding, rbac - SDK pages (45 pages): tom, dsfa, vvt, loeschfristen, einwilligungen, vendor-compliance, tom-generator, dsr, and more - Developer portal (25 pages): API docs, SDK guides, frameworks - All components, lib files, hooks, and types - Updated package.json with all dependencies The issue was caused by incomplete initial repository state - the full admin-v2 codebase existed in backend/admin-v2 and docs-src/admin-v2 but was never fully synced to the main admin-v2 directory. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
327
admin-v2/lib/sdk/sdk-client.ts
Normal file
327
admin-v2/lib/sdk/sdk-client.ts
Normal file
@@ -0,0 +1,327 @@
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
Reference in New Issue
Block a user