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>
This commit is contained in:
70
website/lib/LanguageContext.tsx
Normal file
70
website/lib/LanguageContext.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext, useState, useEffect, ReactNode } from 'react'
|
||||
import { Language, DEFAULT_LANGUAGE, isRTL, t as translate } from './i18n'
|
||||
|
||||
interface LanguageContextType {
|
||||
language: Language
|
||||
setLanguage: (lang: Language) => void
|
||||
t: (key: string) => string
|
||||
isRTL: boolean
|
||||
}
|
||||
|
||||
const LanguageContext = createContext<LanguageContextType | undefined>(undefined)
|
||||
|
||||
const STORAGE_KEY = 'breakpilot-language'
|
||||
|
||||
export function LanguageProvider({ children }: { children: ReactNode }) {
|
||||
const [language, setLanguageState] = useState<Language>(DEFAULT_LANGUAGE)
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
// Load language from localStorage on mount
|
||||
useEffect(() => {
|
||||
const stored = localStorage.getItem(STORAGE_KEY)
|
||||
if (stored && ['de', 'en', 'tr', 'ar', 'ru', 'uk', 'pl'].includes(stored)) {
|
||||
setLanguageState(stored as Language)
|
||||
}
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
// Update document direction for RTL languages
|
||||
useEffect(() => {
|
||||
if (mounted) {
|
||||
document.documentElement.dir = isRTL(language) ? 'rtl' : 'ltr'
|
||||
document.documentElement.lang = language
|
||||
}
|
||||
}, [language, mounted])
|
||||
|
||||
const setLanguage = (lang: Language) => {
|
||||
setLanguageState(lang)
|
||||
localStorage.setItem(STORAGE_KEY, lang)
|
||||
}
|
||||
|
||||
const t = (key: string) => translate(key, language)
|
||||
|
||||
const value: LanguageContextType = {
|
||||
language,
|
||||
setLanguage,
|
||||
t,
|
||||
isRTL: isRTL(language),
|
||||
}
|
||||
|
||||
// Prevent hydration mismatch
|
||||
if (!mounted) {
|
||||
return (
|
||||
<LanguageContext.Provider value={{ ...value, language: DEFAULT_LANGUAGE, isRTL: false }}>
|
||||
{children}
|
||||
</LanguageContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
return <LanguageContext.Provider value={value}>{children}</LanguageContext.Provider>
|
||||
}
|
||||
|
||||
export function useLanguage() {
|
||||
const context = useContext(LanguageContext)
|
||||
if (context === undefined) {
|
||||
throw new Error('useLanguage must be used within a LanguageProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
267
website/lib/architecture-data.ts
Normal file
267
website/lib/architecture-data.ts
Normal file
@@ -0,0 +1,267 @@
|
||||
// ==============================================
|
||||
// Architecture Data for Wizard Framework
|
||||
// ==============================================
|
||||
// Zentrale Konfiguration der Systemarchitektur fuer
|
||||
// Visualisierung in den Admin-Wizards
|
||||
|
||||
export interface ServiceNode {
|
||||
name: string
|
||||
port: number
|
||||
type: 'frontend' | 'api' | 'service' | 'database' | 'cache' | 'external'
|
||||
description?: string
|
||||
dependencies?: string[]
|
||||
}
|
||||
|
||||
export interface ModuleArchitecture {
|
||||
displayName: string
|
||||
description: string
|
||||
primaryServices: string[]
|
||||
databases: string[]
|
||||
tables?: string[]
|
||||
externalTools?: string[]
|
||||
dataFlow: string[]
|
||||
dependencies: string[]
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Service-Definitionen
|
||||
// ==============================================
|
||||
|
||||
export const ARCHITECTURE_SERVICES: Record<string, ServiceNode> = {
|
||||
frontend: {
|
||||
name: 'Next.js Admin',
|
||||
port: 3000,
|
||||
type: 'frontend',
|
||||
description: 'React-basiertes Admin Panel',
|
||||
},
|
||||
backend: {
|
||||
name: 'Python Backend',
|
||||
port: 8000,
|
||||
type: 'api',
|
||||
description: 'FastAPI REST API',
|
||||
dependencies: ['postgres', 'valkey'],
|
||||
},
|
||||
'consent-service': {
|
||||
name: 'Go Consent Service',
|
||||
port: 8081,
|
||||
type: 'service',
|
||||
description: 'DSGVO-konforme Einwilligungsverwaltung',
|
||||
dependencies: ['postgres'],
|
||||
},
|
||||
postgres: {
|
||||
name: 'PostgreSQL',
|
||||
port: 5432,
|
||||
type: 'database',
|
||||
description: 'Relationale Datenbank',
|
||||
},
|
||||
valkey: {
|
||||
name: 'Valkey',
|
||||
port: 6379,
|
||||
type: 'cache',
|
||||
description: 'In-Memory Cache & Session Store (Redis-kompatibler Open Source Fork)',
|
||||
},
|
||||
matrix: {
|
||||
name: 'Matrix Synapse',
|
||||
port: 8008,
|
||||
type: 'service',
|
||||
description: 'Dezentraler Messenger Server',
|
||||
},
|
||||
jitsi: {
|
||||
name: 'Jitsi Meet',
|
||||
port: 8443,
|
||||
type: 'service',
|
||||
description: 'Video-Konferenz Server',
|
||||
},
|
||||
mailserver: {
|
||||
name: 'Mailserver',
|
||||
port: 993,
|
||||
type: 'external',
|
||||
description: 'IMAP/SMTP Mailserver',
|
||||
},
|
||||
'unity-bridge': {
|
||||
name: 'Unity AI Bridge',
|
||||
port: 8090,
|
||||
type: 'external',
|
||||
description: 'REST API im Unity Editor fuer externe Steuerung',
|
||||
},
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Modul-spezifische Architektur
|
||||
// ==============================================
|
||||
|
||||
export const MODULE_ARCHITECTURE: Record<string, ModuleArchitecture> = {
|
||||
middleware: {
|
||||
displayName: 'Middleware Stack',
|
||||
description: 'Security & Request Processing Layer',
|
||||
primaryServices: ['backend'],
|
||||
databases: ['postgres', 'valkey'],
|
||||
tables: ['middleware_config', 'middleware_events', 'rate_limit_ips'],
|
||||
dataFlow: ['Browser', 'Next.js', 'FastAPI Middleware', 'PostgreSQL'],
|
||||
dependencies: ['JWT Auth', 'Valkey Session'],
|
||||
},
|
||||
consent: {
|
||||
displayName: 'Consent Verwaltung',
|
||||
description: 'DSGVO-konforme Einwilligungsverwaltung',
|
||||
primaryServices: ['consent-service'],
|
||||
databases: ['postgres'],
|
||||
tables: ['consent_records', 'document_versions', 'consent_templates'],
|
||||
dataFlow: ['Browser', 'Next.js', 'FastAPI', 'Go Consent Service', 'PostgreSQL'],
|
||||
dependencies: ['JWT Auth', 'RBAC (data_protection_officer)'],
|
||||
},
|
||||
dsr: {
|
||||
displayName: 'Datenschutzanfragen (DSR)',
|
||||
description: 'DSGVO Art. 15-21 Anfragenbearbeitung',
|
||||
primaryServices: ['backend', 'consent-service'],
|
||||
databases: ['postgres'],
|
||||
tables: ['dsr_requests', 'dsr_exports', 'audit_log'],
|
||||
dataFlow: ['Browser', 'Next.js', 'FastAPI', 'Go Consent Service', 'PostgreSQL'],
|
||||
dependencies: ['JWT Auth', 'RBAC', 'Export Service'],
|
||||
},
|
||||
security: {
|
||||
displayName: 'Security & DevSecOps',
|
||||
description: 'SAST, SCA, Secret Detection, SBOM',
|
||||
primaryServices: ['backend'],
|
||||
databases: ['postgres'],
|
||||
tables: ['security_scans', 'security_findings', 'sbom_components'],
|
||||
externalTools: ['gitleaks', 'semgrep', 'trivy', 'grype'],
|
||||
dataFlow: ['Git Repository', 'Scanner Tools', 'FastAPI', 'PostgreSQL'],
|
||||
dependencies: ['Git Integration', 'CI/CD Pipeline'],
|
||||
},
|
||||
rbac: {
|
||||
displayName: 'RBAC & Authentifizierung',
|
||||
description: 'Rollen- und Berechtigungsverwaltung',
|
||||
primaryServices: ['backend', 'consent-service'],
|
||||
databases: ['postgres', 'valkey'],
|
||||
tables: ['users', 'roles', 'permissions', 'role_permissions'],
|
||||
dataFlow: ['Browser', 'Next.js', 'FastAPI', 'JWT/Session', 'PostgreSQL'],
|
||||
dependencies: ['Valkey Session', 'bcrypt'],
|
||||
},
|
||||
communication: {
|
||||
displayName: 'Kommunikation',
|
||||
description: 'Matrix Messenger & Jitsi Video',
|
||||
primaryServices: ['matrix', 'jitsi'],
|
||||
databases: ['postgres'],
|
||||
tables: ['matrix_rooms', 'jitsi_meetings'],
|
||||
dataFlow: ['Browser', 'Matrix Synapse', 'Jitsi Meet', 'PostgreSQL'],
|
||||
dependencies: ['Matrix Federation', 'STUN/TURN'],
|
||||
},
|
||||
mail: {
|
||||
displayName: 'E-Mail Management',
|
||||
description: 'IMAP/SMTP mit KI-Analyse',
|
||||
primaryServices: ['backend', 'mailserver'],
|
||||
databases: ['postgres'],
|
||||
tables: ['mail_accounts', 'mail_messages', 'mail_analysis'],
|
||||
dataFlow: ['Mailserver (IMAP)', 'FastAPI', 'LLM Analysis', 'PostgreSQL'],
|
||||
dependencies: ['IMAP Auth', 'SMTP Auth', 'LLM Service'],
|
||||
},
|
||||
gpu: {
|
||||
displayName: 'GPU Infrastruktur',
|
||||
description: 'CUDA/ROCm GPU Management',
|
||||
primaryServices: ['backend'],
|
||||
databases: ['postgres'],
|
||||
tables: ['gpu_nodes', 'gpu_jobs', 'gpu_metrics'],
|
||||
dataFlow: ['Browser', 'FastAPI', 'NVIDIA/AMD Driver', 'GPU Hardware'],
|
||||
dependencies: ['CUDA', 'ROCm', 'nvidia-smi'],
|
||||
},
|
||||
llm: {
|
||||
displayName: 'LLM Vergleich',
|
||||
description: 'Vergleich verschiedener LLM Provider',
|
||||
primaryServices: ['backend'],
|
||||
databases: ['postgres'],
|
||||
tables: ['llm_providers', 'llm_benchmarks', 'llm_costs'],
|
||||
externalTools: ['OpenAI API', 'Anthropic API', 'Local LLMs'],
|
||||
dataFlow: ['Browser', 'FastAPI', 'LLM Provider API', 'PostgreSQL'],
|
||||
dependencies: ['API Keys', 'Token Counting'],
|
||||
},
|
||||
rag: {
|
||||
displayName: 'RAG & BYOEH',
|
||||
description: 'Retrieval Augmented Generation mit Client-Side Encryption',
|
||||
primaryServices: ['backend', 'klausur-frontend'],
|
||||
databases: ['postgres', 'qdrant'],
|
||||
tables: ['erwartungshorizonte', 'eh_chunks', 'documents', 'embeddings'],
|
||||
externalTools: ['Qdrant Vector DB', 'BGE-M3 Embeddings', 'BGE Reranker'],
|
||||
dataFlow: ['EH Upload', 'Client-Side Encryption', 'Chunking', 'Embedding', 'Vector Store', 'RAG Search', 'Client-Side Decryption'],
|
||||
dependencies: ['AES-256-GCM', 'PBKDF2', 'Web Crypto API', 'Qdrant'],
|
||||
},
|
||||
sbom: {
|
||||
displayName: 'SBOM Management',
|
||||
description: 'Software Bill of Materials',
|
||||
primaryServices: ['backend'],
|
||||
databases: ['postgres'],
|
||||
tables: ['sbom_components', 'vulnerabilities', 'licenses'],
|
||||
externalTools: ['syft', 'cyclonedx'],
|
||||
dataFlow: ['Container Image', 'SBOM Generator', 'FastAPI', 'PostgreSQL'],
|
||||
dependencies: ['Container Runtime', 'CVE Database'],
|
||||
},
|
||||
docs: {
|
||||
displayName: 'Entwickler-Dokumentation',
|
||||
description: 'Projektdokumentation & Guides',
|
||||
primaryServices: ['frontend'],
|
||||
databases: [],
|
||||
dataFlow: ['Markdown Files', 'Next.js', 'Browser'],
|
||||
dependencies: ['File System'],
|
||||
},
|
||||
'unity-bridge': {
|
||||
displayName: 'Unity AI Bridge',
|
||||
description: 'REST API zur externen Steuerung des Unity Editors',
|
||||
primaryServices: ['unity-bridge'],
|
||||
databases: [],
|
||||
dataFlow: ['Admin Panel', 'API Proxy', 'Unity Bridge', 'Unity Editor'],
|
||||
dependencies: ['Unity Editor muss laufen', 'Bridge Server gestartet'],
|
||||
},
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Hilfsfunktionen
|
||||
// ==============================================
|
||||
|
||||
export function getServiceInfo(serviceId: string): ServiceNode | undefined {
|
||||
return ARCHITECTURE_SERVICES[serviceId]
|
||||
}
|
||||
|
||||
export function getModuleArchitecture(moduleId: string): ModuleArchitecture | undefined {
|
||||
return MODULE_ARCHITECTURE[moduleId]
|
||||
}
|
||||
|
||||
export function getAllServices(moduleId: string): ServiceNode[] {
|
||||
const module = MODULE_ARCHITECTURE[moduleId]
|
||||
if (!module) return []
|
||||
|
||||
const serviceIds = [...module.primaryServices, ...module.databases]
|
||||
return serviceIds
|
||||
.map((id) => ARCHITECTURE_SERVICES[id])
|
||||
.filter((s): s is ServiceNode => s !== undefined)
|
||||
}
|
||||
|
||||
export function getDependencyChain(moduleId: string): string[] {
|
||||
const module = MODULE_ARCHITECTURE[moduleId]
|
||||
if (!module) return []
|
||||
|
||||
return module.dataFlow
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Wizard-spezifische Konfiguration
|
||||
// ==============================================
|
||||
|
||||
export interface WizardModuleConfig {
|
||||
module: string
|
||||
available: boolean
|
||||
steps: number
|
||||
priority: 'high' | 'medium' | 'low'
|
||||
}
|
||||
|
||||
export const AVAILABLE_WIZARDS: WizardModuleConfig[] = [
|
||||
{ module: 'middleware', available: true, steps: 8, priority: 'high' },
|
||||
{ module: 'consent', available: true, steps: 6, priority: 'high' },
|
||||
{ module: 'dsr', available: true, steps: 7, priority: 'high' },
|
||||
{ module: 'security', available: true, steps: 8, priority: 'high' },
|
||||
{ module: 'rbac', available: true, steps: 6, priority: 'medium' },
|
||||
{ module: 'communication', available: true, steps: 5, priority: 'medium' },
|
||||
{ module: 'mail', available: true, steps: 6, priority: 'medium' },
|
||||
{ module: 'unity-bridge', available: true, steps: 7, priority: 'medium' },
|
||||
{ module: 'gpu', available: true, steps: 4, priority: 'low' },
|
||||
{ module: 'llm', available: true, steps: 4, priority: 'low' },
|
||||
{ module: 'rag', available: true, steps: 6, priority: 'high' },
|
||||
]
|
||||
361
website/lib/compliance-i18n.ts
Normal file
361
website/lib/compliance-i18n.ts
Normal file
@@ -0,0 +1,361 @@
|
||||
/**
|
||||
* Compliance Framework - Internationalization (i18n)
|
||||
*
|
||||
* Provides bilingual terminology (DE/EN) for the compliance module.
|
||||
* This helps non-technical stakeholders understand GRC concepts.
|
||||
*/
|
||||
|
||||
export type Language = 'de' | 'en'
|
||||
|
||||
// Core compliance terms with translations and explanations
|
||||
export const COMPLIANCE_TERMS = {
|
||||
de: {
|
||||
// Core concepts
|
||||
control: 'Massnahme',
|
||||
control_description: 'Eine technische oder organisatorische Massnahme zur Erfuellung einer Compliance-Anforderung.',
|
||||
evidence: 'Nachweis',
|
||||
evidence_description: 'Dokumentierter Beleg, dass eine Massnahme umgesetzt und wirksam ist.',
|
||||
requirement: 'Anforderung',
|
||||
requirement_description: 'Eine gesetzliche oder normative Vorgabe, die erfuellt werden muss.',
|
||||
regulation: 'Verordnung',
|
||||
regulation_description: 'Ein Gesetz oder Standard, der verbindliche Anforderungen definiert.',
|
||||
risk: 'Risiko',
|
||||
risk_description: 'Eine potenzielle Bedrohung fuer die Organisation mit Eintrittswahrscheinlichkeit und Schadenshoehe.',
|
||||
|
||||
// Status values
|
||||
pass: 'Erfuellt',
|
||||
pass_description: 'Die Massnahme ist vollstaendig umgesetzt und nachgewiesen.',
|
||||
partial: 'Teilweise erfuellt',
|
||||
partial_description: 'Die Massnahme ist teilweise umgesetzt, es bestehen noch Luecken.',
|
||||
fail: 'Nicht erfuellt',
|
||||
fail_description: 'Die Massnahme ist nicht oder unzureichend umgesetzt.',
|
||||
planned: 'Geplant',
|
||||
planned_description: 'Die Umsetzung der Massnahme ist geplant aber noch nicht begonnen.',
|
||||
not_applicable: 'Nicht anwendbar',
|
||||
not_applicable_description: 'Die Anforderung trifft auf unsere Organisation nicht zu.',
|
||||
|
||||
// Metrics
|
||||
compliance_score: 'Erfuellungsgrad',
|
||||
compliance_score_description: 'Prozentsatz der erfuellten Compliance-Anforderungen.',
|
||||
coverage_level: 'Abdeckungsgrad',
|
||||
coverage_level_description: 'Wie gut eine Massnahme die Anforderung erfuellt (vollstaendig/teilweise).',
|
||||
risk_level: 'Risikostufe',
|
||||
risk_level_description: 'Kombinierte Bewertung aus Eintrittswahrscheinlichkeit und Schadenshoehe.',
|
||||
|
||||
// Risk levels
|
||||
low: 'Niedrig',
|
||||
low_description: 'Geringes Risiko, keine sofortigen Massnahmen erforderlich.',
|
||||
medium: 'Mittel',
|
||||
medium_description: 'Moderates Risiko, Massnahmen sollten geplant werden.',
|
||||
high: 'Hoch',
|
||||
high_description: 'Hohes Risiko, zeitnahe Massnahmen erforderlich.',
|
||||
critical: 'Kritisch',
|
||||
critical_description: 'Kritisches Risiko, sofortige Massnahmen erforderlich.',
|
||||
|
||||
// Domains
|
||||
governance: 'Governance',
|
||||
governance_description: 'Organisatorische Steuerung und Fuehrung.',
|
||||
privacy: 'Datenschutz',
|
||||
privacy_description: 'Schutz personenbezogener Daten.',
|
||||
iam: 'Identitaets- & Zugriffsmanagement',
|
||||
iam_description: 'Verwaltung von Benutzerkonten und Zugriffsrechten.',
|
||||
crypto: 'Kryptografie',
|
||||
crypto_description: 'Verschluesselung und kryptografische Verfahren.',
|
||||
sdlc: 'Sichere Entwicklung',
|
||||
sdlc_description: 'Sicherheit im Softwareentwicklungsprozess.',
|
||||
ops: 'Betrieb',
|
||||
ops_description: 'IT-Betrieb, Monitoring und Incident Response.',
|
||||
ai: 'KI-spezifisch',
|
||||
ai_description: 'Anforderungen fuer Kuenstliche Intelligenz.',
|
||||
cra: 'Supply Chain',
|
||||
cra_description: 'Lieferketten-Sicherheit und Produkthaftung.',
|
||||
audit: 'Audit',
|
||||
audit_description: 'Pruefung und Nachvollziehbarkeit.',
|
||||
|
||||
// UI Elements
|
||||
dashboard: 'Uebersicht',
|
||||
export: 'Export',
|
||||
filter: 'Filter',
|
||||
search: 'Suche',
|
||||
details: 'Details',
|
||||
actions: 'Aktionen',
|
||||
status: 'Status',
|
||||
owner: 'Verantwortlich',
|
||||
deadline: 'Frist',
|
||||
last_review: 'Letzte Pruefung',
|
||||
next_review: 'Naechste Pruefung',
|
||||
|
||||
// Executive Dashboard
|
||||
traffic_light_status: 'Ampelstatus',
|
||||
traffic_light_description: 'Schnelle Einschaetzung: Gruen = gut, Gelb = Aufmerksamkeit, Rot = kritisch.',
|
||||
top_risks: 'Top Risiken',
|
||||
upcoming_deadlines: 'Naechste Fristen',
|
||||
trend: 'Trend',
|
||||
trend_description: 'Entwicklung des Erfuellungsgrades ueber Zeit.',
|
||||
workload: 'Arbeitsbelastung',
|
||||
workload_description: 'Offene Aufgaben pro Team oder Person.',
|
||||
|
||||
// Audit & Sign-off (Sprint 3)
|
||||
audit_session: 'Pruefung',
|
||||
audit_session_description: 'Eine strukturierte Compliance-Pruefung mit definierten Anforderungen.',
|
||||
sign_off: 'Freigabe',
|
||||
sign_off_description: 'Bestaetigung eines Pruefergebnisses durch den Auditor.',
|
||||
digital_signature: 'Digitale Signatur',
|
||||
digital_signature_description: 'SHA-256 Hash zur unveraenderlichen Dokumentation des Pruefergebnisses.',
|
||||
audit_checklist: 'Pruefungscheckliste',
|
||||
audit_checklist_description: 'Liste aller zu pruefenden Anforderungen einer Audit-Session.',
|
||||
completion_rate: 'Abschlussrate',
|
||||
completion_rate_description: 'Prozentsatz der bereits abgeschlossenen Pruefpunkte.',
|
||||
|
||||
// Audit Results
|
||||
compliant: 'Konform',
|
||||
compliant_description: 'Die Anforderung ist vollstaendig erfuellt.',
|
||||
compliant_with_notes: 'Konform mit Anmerkungen',
|
||||
compliant_with_notes_description: 'Die Anforderung ist erfuellt, aber es gibt Hinweise zur Verbesserung.',
|
||||
non_compliant: 'Nicht konform',
|
||||
non_compliant_description: 'Die Anforderung ist nicht oder unzureichend erfuellt.',
|
||||
pending_audit: 'Ausstehend',
|
||||
pending_audit_description: 'Die Pruefung dieses Punktes steht noch aus.',
|
||||
|
||||
// Session Status
|
||||
session_draft: 'Entwurf',
|
||||
session_in_progress: 'In Bearbeitung',
|
||||
session_completed: 'Abgeschlossen',
|
||||
session_archived: 'Archiviert',
|
||||
|
||||
// Actions
|
||||
create_session: 'Neue Session erstellen',
|
||||
start_audit: 'Pruefung starten',
|
||||
complete_audit: 'Pruefung abschliessen',
|
||||
export_report: 'Report exportieren',
|
||||
sign_item: 'Punkt signieren',
|
||||
},
|
||||
|
||||
en: {
|
||||
// Core concepts
|
||||
control: 'Control',
|
||||
control_description: 'A technical or organizational measure to fulfill a compliance requirement.',
|
||||
evidence: 'Evidence',
|
||||
evidence_description: 'Documented proof that a control is implemented and effective.',
|
||||
requirement: 'Requirement',
|
||||
requirement_description: 'A legal or normative obligation that must be fulfilled.',
|
||||
regulation: 'Regulation',
|
||||
regulation_description: 'A law or standard that defines binding requirements.',
|
||||
risk: 'Risk',
|
||||
risk_description: 'A potential threat to the organization with likelihood and impact.',
|
||||
|
||||
// Status values
|
||||
pass: 'Pass',
|
||||
pass_description: 'The control is fully implemented and evidenced.',
|
||||
partial: 'Partial',
|
||||
partial_description: 'The control is partially implemented, gaps remain.',
|
||||
fail: 'Fail',
|
||||
fail_description: 'The control is not or insufficiently implemented.',
|
||||
planned: 'Planned',
|
||||
planned_description: 'Implementation of the control is planned but not started.',
|
||||
not_applicable: 'N/A',
|
||||
not_applicable_description: 'The requirement does not apply to our organization.',
|
||||
|
||||
// Metrics
|
||||
compliance_score: 'Compliance Score',
|
||||
compliance_score_description: 'Percentage of fulfilled compliance requirements.',
|
||||
coverage_level: 'Coverage Level',
|
||||
coverage_level_description: 'How well a control fulfills the requirement (full/partial).',
|
||||
risk_level: 'Risk Level',
|
||||
risk_level_description: 'Combined assessment of likelihood and impact.',
|
||||
|
||||
// Risk levels
|
||||
low: 'Low',
|
||||
low_description: 'Low risk, no immediate action required.',
|
||||
medium: 'Medium',
|
||||
medium_description: 'Moderate risk, actions should be planned.',
|
||||
high: 'High',
|
||||
high_description: 'High risk, timely action required.',
|
||||
critical: 'Critical',
|
||||
critical_description: 'Critical risk, immediate action required.',
|
||||
|
||||
// Domains
|
||||
governance: 'Governance',
|
||||
governance_description: 'Organizational steering and leadership.',
|
||||
privacy: 'Privacy',
|
||||
privacy_description: 'Protection of personal data.',
|
||||
iam: 'Identity & Access Management',
|
||||
iam_description: 'Management of user accounts and access rights.',
|
||||
crypto: 'Cryptography',
|
||||
crypto_description: 'Encryption and cryptographic procedures.',
|
||||
sdlc: 'Secure Development',
|
||||
sdlc_description: 'Security in the software development process.',
|
||||
ops: 'Operations',
|
||||
ops_description: 'IT operations, monitoring, and incident response.',
|
||||
ai: 'AI-specific',
|
||||
ai_description: 'Requirements for Artificial Intelligence.',
|
||||
cra: 'Supply Chain',
|
||||
cra_description: 'Supply chain security and product liability.',
|
||||
audit: 'Audit',
|
||||
audit_description: 'Auditing and traceability.',
|
||||
|
||||
// UI Elements
|
||||
dashboard: 'Dashboard',
|
||||
export: 'Export',
|
||||
filter: 'Filter',
|
||||
search: 'Search',
|
||||
details: 'Details',
|
||||
actions: 'Actions',
|
||||
status: 'Status',
|
||||
owner: 'Owner',
|
||||
deadline: 'Deadline',
|
||||
last_review: 'Last Review',
|
||||
next_review: 'Next Review',
|
||||
|
||||
// Executive Dashboard
|
||||
traffic_light_status: 'Traffic Light Status',
|
||||
traffic_light_description: 'Quick assessment: Green = good, Yellow = attention, Red = critical.',
|
||||
top_risks: 'Top Risks',
|
||||
upcoming_deadlines: 'Upcoming Deadlines',
|
||||
trend: 'Trend',
|
||||
trend_description: 'Development of compliance score over time.',
|
||||
workload: 'Workload',
|
||||
workload_description: 'Open tasks per team or person.',
|
||||
|
||||
// Audit & Sign-off (Sprint 3)
|
||||
audit_session: 'Audit Session',
|
||||
audit_session_description: 'A structured compliance audit with defined requirements.',
|
||||
sign_off: 'Sign-off',
|
||||
sign_off_description: 'Confirmation of an audit result by the auditor.',
|
||||
digital_signature: 'Digital Signature',
|
||||
digital_signature_description: 'SHA-256 hash for immutable documentation of audit results.',
|
||||
audit_checklist: 'Audit Checklist',
|
||||
audit_checklist_description: 'List of all requirements to be audited in a session.',
|
||||
completion_rate: 'Completion Rate',
|
||||
completion_rate_description: 'Percentage of completed audit items.',
|
||||
|
||||
// Audit Results
|
||||
compliant: 'Compliant',
|
||||
compliant_description: 'The requirement is fully met.',
|
||||
compliant_with_notes: 'Compliant with Notes',
|
||||
compliant_with_notes_description: 'The requirement is met, but there are notes for improvement.',
|
||||
non_compliant: 'Non-Compliant',
|
||||
non_compliant_description: 'The requirement is not or insufficiently met.',
|
||||
pending_audit: 'Pending',
|
||||
pending_audit_description: 'The audit of this item is still pending.',
|
||||
|
||||
// Session Status
|
||||
session_draft: 'Draft',
|
||||
session_in_progress: 'In Progress',
|
||||
session_completed: 'Completed',
|
||||
session_archived: 'Archived',
|
||||
|
||||
// Actions
|
||||
create_session: 'Create Session',
|
||||
start_audit: 'Start Audit',
|
||||
complete_audit: 'Complete Audit',
|
||||
export_report: 'Export Report',
|
||||
sign_item: 'Sign Item',
|
||||
},
|
||||
}
|
||||
|
||||
// Domain mapping for display
|
||||
export const DOMAIN_LABELS: Record<string, { de: string; en: string }> = {
|
||||
gov: { de: 'Governance', en: 'Governance' },
|
||||
priv: { de: 'Datenschutz', en: 'Privacy' },
|
||||
iam: { de: 'Identitaet & Zugriff', en: 'Identity & Access' },
|
||||
crypto: { de: 'Kryptografie', en: 'Cryptography' },
|
||||
sdlc: { de: 'Sichere Entwicklung', en: 'Secure Dev' },
|
||||
ops: { de: 'Betrieb', en: 'Operations' },
|
||||
ai: { de: 'KI-spezifisch', en: 'AI-specific' },
|
||||
cra: { de: 'Supply Chain', en: 'Supply Chain' },
|
||||
aud: { de: 'Audit', en: 'Audit' },
|
||||
}
|
||||
|
||||
// Status colors and labels
|
||||
export const STATUS_CONFIG: Record<string, { color: string; bgColor: string; de: string; en: string }> = {
|
||||
pass: { color: 'text-green-700', bgColor: 'bg-green-100', de: 'Erfuellt', en: 'Pass' },
|
||||
partial: { color: 'text-yellow-700', bgColor: 'bg-yellow-100', de: 'Teilweise', en: 'Partial' },
|
||||
fail: { color: 'text-red-700', bgColor: 'bg-red-100', de: 'Nicht erfuellt', en: 'Fail' },
|
||||
planned: { color: 'text-slate-700', bgColor: 'bg-slate-100', de: 'Geplant', en: 'Planned' },
|
||||
'n/a': { color: 'text-slate-500', bgColor: 'bg-slate-50', de: 'N/A', en: 'N/A' },
|
||||
}
|
||||
|
||||
// Risk level colors
|
||||
export const RISK_CONFIG: Record<string, { color: string; bgColor: string; de: string; en: string }> = {
|
||||
low: { color: 'text-green-700', bgColor: 'bg-green-100', de: 'Niedrig', en: 'Low' },
|
||||
medium: { color: 'text-yellow-700', bgColor: 'bg-yellow-100', de: 'Mittel', en: 'Medium' },
|
||||
high: { color: 'text-orange-700', bgColor: 'bg-orange-100', de: 'Hoch', en: 'High' },
|
||||
critical: { color: 'text-red-700', bgColor: 'bg-red-100', de: 'Kritisch', en: 'Critical' },
|
||||
}
|
||||
|
||||
// Traffic light colors for executive dashboard
|
||||
export const TRAFFIC_LIGHT_CONFIG: Record<string, { color: string; bgColor: string; borderColor: string; de: string; en: string }> = {
|
||||
green: {
|
||||
color: 'text-green-700',
|
||||
bgColor: 'bg-green-500',
|
||||
borderColor: 'border-green-500',
|
||||
de: 'Gut',
|
||||
en: 'Good'
|
||||
},
|
||||
yellow: {
|
||||
color: 'text-yellow-700',
|
||||
bgColor: 'bg-yellow-500',
|
||||
borderColor: 'border-yellow-500',
|
||||
de: 'Aufmerksamkeit',
|
||||
en: 'Attention'
|
||||
},
|
||||
red: {
|
||||
color: 'text-red-700',
|
||||
bgColor: 'bg-red-500',
|
||||
borderColor: 'border-red-500',
|
||||
de: 'Kritisch',
|
||||
en: 'Critical'
|
||||
},
|
||||
}
|
||||
|
||||
// Helper function to get term with fallback
|
||||
export function getTerm(lang: Language, key: string): string {
|
||||
const terms = COMPLIANCE_TERMS[lang]
|
||||
return (terms as Record<string, string>)[key] || key
|
||||
}
|
||||
|
||||
// Helper function to get description
|
||||
export function getDescription(lang: Language, key: string): string {
|
||||
const terms = COMPLIANCE_TERMS[lang]
|
||||
return (terms as Record<string, string>)[`${key}_description`] || ''
|
||||
}
|
||||
|
||||
// Helper to get domain label
|
||||
export function getDomainLabel(domain: string, lang: Language): string {
|
||||
return DOMAIN_LABELS[domain]?.[lang] || domain.toUpperCase()
|
||||
}
|
||||
|
||||
// Helper to get status display
|
||||
export function getStatusDisplay(status: string, lang: Language): { label: string; color: string; bgColor: string } {
|
||||
const config = STATUS_CONFIG[status] || STATUS_CONFIG['planned']
|
||||
return {
|
||||
label: config[lang],
|
||||
color: config.color,
|
||||
bgColor: config.bgColor,
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to get risk display
|
||||
export function getRiskDisplay(level: string, lang: Language): { label: string; color: string; bgColor: string } {
|
||||
const config = RISK_CONFIG[level] || RISK_CONFIG['medium']
|
||||
return {
|
||||
label: config[lang],
|
||||
color: config.color,
|
||||
bgColor: config.bgColor,
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to get traffic light display
|
||||
export function getTrafficLightDisplay(status: string, lang: Language): { label: string; color: string; bgColor: string; borderColor: string } {
|
||||
const config = TRAFFIC_LIGHT_CONFIG[status] || TRAFFIC_LIGHT_CONFIG['yellow']
|
||||
return {
|
||||
label: config[lang],
|
||||
color: config.color,
|
||||
bgColor: config.bgColor,
|
||||
borderColor: config.borderColor,
|
||||
}
|
||||
}
|
||||
|
||||
// Language context default
|
||||
export const DEFAULT_LANGUAGE: Language = 'de'
|
||||
60
website/lib/content-types.ts
Normal file
60
website/lib/content-types.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Website Content Type Definitions
|
||||
*
|
||||
* Typen fuer Website-Inhalte (ohne Server-side Imports)
|
||||
*/
|
||||
|
||||
export interface HeroContent {
|
||||
badge: string
|
||||
title: string
|
||||
titleHighlight1: string
|
||||
titleHighlight2: string
|
||||
subtitle: string
|
||||
ctaPrimary: string
|
||||
ctaSecondary: string
|
||||
ctaHint: string
|
||||
}
|
||||
|
||||
export interface FeatureContent {
|
||||
id: string
|
||||
icon: string
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export interface FAQItem {
|
||||
question: string
|
||||
answer: string[]
|
||||
}
|
||||
|
||||
export interface PricingPlan {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
price: number
|
||||
currency: string
|
||||
interval: string
|
||||
popular?: boolean
|
||||
features: {
|
||||
tasks: string
|
||||
taskDescription: string
|
||||
included: string[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface WebsiteContent {
|
||||
hero: HeroContent
|
||||
features: FeatureContent[]
|
||||
faq: FAQItem[]
|
||||
pricing: PricingPlan[]
|
||||
trust: {
|
||||
item1: { value: string; label: string }
|
||||
item2: { value: string; label: string }
|
||||
item3: { value: string; label: string }
|
||||
}
|
||||
testimonial: {
|
||||
quote: string
|
||||
author: string
|
||||
role: string
|
||||
}
|
||||
}
|
||||
284
website/lib/content.ts
Normal file
284
website/lib/content.ts
Normal file
@@ -0,0 +1,284 @@
|
||||
/**
|
||||
* Website Content Management (Server-only)
|
||||
*
|
||||
* Laedt Website-Texte aus JSON-Dateien.
|
||||
* Admin kann Texte ueber /admin bearbeiten.
|
||||
*
|
||||
* WICHTIG: Diese Datei darf nur in Server Components verwendet werden!
|
||||
* Fuer Client Components verwende @/lib/content-types
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync, existsSync, mkdirSync, accessSync, constants } from 'fs'
|
||||
import { join, dirname } from 'path'
|
||||
|
||||
// Re-export types from content-types for backward compatibility
|
||||
export type {
|
||||
HeroContent,
|
||||
FeatureContent,
|
||||
FAQItem,
|
||||
PricingPlan,
|
||||
WebsiteContent,
|
||||
} from './content-types'
|
||||
|
||||
import type { WebsiteContent } from './content-types'
|
||||
|
||||
// Content-Verzeichnis - nutze Umgebungsvariable oder relativen Pfad
|
||||
function getContentDir(): string {
|
||||
// Prüfe Umgebungsvariable zuerst
|
||||
if (process.env.CONTENT_DIR) {
|
||||
return process.env.CONTENT_DIR
|
||||
}
|
||||
|
||||
// Versuche verschiedene mögliche Pfade
|
||||
const possiblePaths = [
|
||||
join(process.cwd(), 'content'), // Standard: CWD/content
|
||||
join(process.cwd(), 'website', 'content'), // Falls CWD das Projekt-Root ist
|
||||
'/app/content', // Docker-Container
|
||||
join(dirname(__filename), '..', 'content'), // Relativ zu dieser Datei
|
||||
]
|
||||
|
||||
// Prüfe ob einer der Pfade existiert und beschreibbar ist
|
||||
for (const path of possiblePaths) {
|
||||
try {
|
||||
if (existsSync(path)) {
|
||||
accessSync(path, constants.W_OK)
|
||||
return path
|
||||
}
|
||||
} catch {
|
||||
// Pfad nicht beschreibbar, versuche nächsten
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: Erstelle im CWD
|
||||
const fallbackPath = join(process.cwd(), 'content')
|
||||
try {
|
||||
mkdirSync(fallbackPath, { recursive: true, mode: 0o755 })
|
||||
console.log(`[Content] Created content directory at: ${fallbackPath}`)
|
||||
} catch (err) {
|
||||
console.error(`[Content] Failed to create content directory: ${err}`)
|
||||
}
|
||||
return fallbackPath
|
||||
}
|
||||
|
||||
const CONTENT_DIR = getContentDir()
|
||||
|
||||
// Default Content
|
||||
const defaultContent: WebsiteContent = {
|
||||
hero: {
|
||||
badge: 'Entwickelt fuer deutsche Lehrkraefte',
|
||||
title: 'Korrigieren Sie',
|
||||
titleHighlight1: 'schneller',
|
||||
titleHighlight2: 'besser',
|
||||
subtitle: 'BreakPilot unterstuetzt Lehrkraefte mit intelligenter KI bei der Bewertung von Aufgaben. Sparen Sie bis zu 50% Ihrer Korrekturzeit und geben Sie besseres Feedback.',
|
||||
ctaPrimary: '7 Tage kostenlos testen',
|
||||
ctaSecondary: 'Mehr erfahren',
|
||||
ctaHint: 'Keine Kreditkarte fuer den Start erforderlich',
|
||||
},
|
||||
features: [
|
||||
{
|
||||
id: 'ai-correction',
|
||||
icon: '✍️',
|
||||
title: 'KI-gestuetzte Korrektur',
|
||||
description: 'Intelligente Analyse von Schuelerantworten mit Verbesserungsvorschlaegen und automatischer Bewertung nach Ihren Kriterien.',
|
||||
},
|
||||
{
|
||||
id: 'templates',
|
||||
icon: '📋',
|
||||
title: 'Dokumentvorlagen',
|
||||
description: 'Erstellen und verwalten Sie Ihre eigenen Arbeitsblatt-Vorlagen. Wiederverwendbar fuer verschiedene Klassen und Jahrgaenge.',
|
||||
},
|
||||
{
|
||||
id: 'analytics',
|
||||
icon: '📊',
|
||||
title: 'Fortschrittsanalyse',
|
||||
description: 'Verfolgen Sie die Entwicklung Ihrer Schueler ueber Zeit. Erkennen Sie Staerken und Schwaechen fruehzeitig.',
|
||||
},
|
||||
{
|
||||
id: 'gdpr',
|
||||
icon: '🔒',
|
||||
title: 'DSGVO-konform',
|
||||
description: 'Hosting in Deutschland, volle Datenschutzkonformitaet. Ihre Daten und die Ihrer Schueler sind sicher.',
|
||||
},
|
||||
{
|
||||
id: 'team',
|
||||
icon: '👥',
|
||||
title: 'Team-Funktionen',
|
||||
description: 'Arbeiten Sie im Fachbereich zusammen. Teilen Sie Vorlagen, Bewertungskriterien und Best Practices.',
|
||||
},
|
||||
{
|
||||
id: 'mobile',
|
||||
icon: '📱',
|
||||
title: 'Ueberall verfuegbar',
|
||||
description: 'Browserbasiert und responsive. Funktioniert auf Desktop, Tablet und Smartphone - ohne Installation.',
|
||||
},
|
||||
],
|
||||
faq: [
|
||||
{
|
||||
question: 'Was ist bei Breakpilot eine „Aufgabe"?',
|
||||
answer: [
|
||||
'Eine Aufgabe ist ein abgeschlossener Arbeitsauftrag, den du mit Breakpilot erledigst.',
|
||||
'Typische Beispiele:',
|
||||
'• eine Klassenarbeit korrigieren (egal wie viele Seiten)',
|
||||
'• mehrere Klassenarbeiten in einer Serie korrigieren',
|
||||
'• einen Elternbrief erstellen',
|
||||
'Wichtig: Die Anzahl der Seiten, Dateien oder Uploads spielt dabei keine Rolle.',
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'Kann ich Breakpilot kostenlos testen?',
|
||||
answer: [
|
||||
'Ja.',
|
||||
'• Du kannst Breakpilot 7 Tage kostenlos testen',
|
||||
'• Dafuer ist eine Kreditkarte erforderlich',
|
||||
'• Wenn du innerhalb der Testphase kuendigst, entstehen keine Kosten',
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'Werden meine Daten fuer KI-Training verwendet?',
|
||||
answer: [
|
||||
'Nein.',
|
||||
'• Deine Inhalte werden nicht fuer das Training oeffentlicher KI-Modelle genutzt',
|
||||
'• Die Verarbeitung erfolgt DSGVO-konform',
|
||||
'• Deine Daten bleiben unter deiner Kontrolle',
|
||||
],
|
||||
},
|
||||
{
|
||||
question: 'Kann ich meinen Tarif jederzeit aendern oder kuendigen?',
|
||||
answer: [
|
||||
'Ja.',
|
||||
'• Upgrades sind jederzeit moeglich',
|
||||
'• Downgrades greifen zum naechsten Abrechnungszeitraum',
|
||||
'• Kuendigungen sind jederzeit moeglich',
|
||||
],
|
||||
},
|
||||
],
|
||||
pricing: [
|
||||
{
|
||||
id: 'basic',
|
||||
name: 'Basic',
|
||||
description: 'Perfekt fuer den Einstieg',
|
||||
price: 9.90,
|
||||
currency: 'EUR',
|
||||
interval: 'Monat',
|
||||
features: {
|
||||
tasks: '30 Aufgaben',
|
||||
taskDescription: 'pro Monat',
|
||||
included: [
|
||||
'KI-gestuetzte Korrektur',
|
||||
'Basis-Dokumentvorlagen',
|
||||
'E-Mail Support',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'standard',
|
||||
name: 'Standard',
|
||||
description: 'Fuer regelmaessige Nutzer',
|
||||
price: 19.90,
|
||||
currency: 'EUR',
|
||||
interval: 'Monat',
|
||||
popular: true,
|
||||
features: {
|
||||
tasks: '100 Aufgaben',
|
||||
taskDescription: 'pro Monat',
|
||||
included: [
|
||||
'Alles aus Basic',
|
||||
'Eigene Vorlagen erstellen',
|
||||
'Batch-Verarbeitung',
|
||||
'Bis zu 3 Teammitglieder',
|
||||
'Prioritaets-Support',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'premium',
|
||||
name: 'Premium',
|
||||
description: 'Sorglos-Tarif fuer Vielnutzer',
|
||||
price: 39.90,
|
||||
currency: 'EUR',
|
||||
interval: 'Monat',
|
||||
features: {
|
||||
tasks: 'Unbegrenzt',
|
||||
taskDescription: 'Fair Use',
|
||||
included: [
|
||||
'Alles aus Standard',
|
||||
'Unbegrenzte Aufgaben (Fair Use)',
|
||||
'Bis zu 10 Teammitglieder',
|
||||
'Admin-Panel & Audit-Log',
|
||||
'API-Zugang',
|
||||
'Eigenes Branding',
|
||||
'Dedizierter Support',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
trust: {
|
||||
item1: { value: 'DSGVO', label: 'Konform & sicher' },
|
||||
item2: { value: '7 Tage', label: 'Kostenlos testen' },
|
||||
item3: { value: '100%', label: 'Made in Germany' },
|
||||
},
|
||||
testimonial: {
|
||||
quote: 'BreakPilot hat meine Korrekturzeit halbiert. Ich habe endlich wieder Zeit fuer das Wesentliche: meine Schueler.',
|
||||
author: 'Maria S.',
|
||||
role: 'Deutschlehrerin, Gymnasium',
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Laedt Content aus JSON-Datei oder gibt Default zurueck
|
||||
*/
|
||||
export function getContent(): WebsiteContent {
|
||||
const contentPath = join(CONTENT_DIR, 'website.json')
|
||||
|
||||
try {
|
||||
if (existsSync(contentPath)) {
|
||||
const fileContent = readFileSync(contentPath, 'utf-8')
|
||||
return JSON.parse(fileContent) as WebsiteContent
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading content:', error)
|
||||
}
|
||||
|
||||
return defaultContent
|
||||
}
|
||||
|
||||
/**
|
||||
* Speichert Content in JSON-Datei
|
||||
* @returns Objekt mit success-Status und optionaler Fehlermeldung
|
||||
*/
|
||||
export function saveContent(content: WebsiteContent): { success: boolean; error?: string } {
|
||||
const contentPath = join(CONTENT_DIR, 'website.json')
|
||||
|
||||
try {
|
||||
// Stelle sicher, dass Verzeichnis existiert
|
||||
if (!existsSync(CONTENT_DIR)) {
|
||||
console.log(`[Content] Creating directory: ${CONTENT_DIR}`)
|
||||
mkdirSync(CONTENT_DIR, { recursive: true, mode: 0o755 })
|
||||
}
|
||||
|
||||
// Prüfe Schreibrechte
|
||||
try {
|
||||
accessSync(CONTENT_DIR, constants.W_OK)
|
||||
} catch {
|
||||
const error = `Verzeichnis nicht beschreibbar: ${CONTENT_DIR}`
|
||||
console.error(`[Content] ${error}`)
|
||||
return { success: false, error }
|
||||
}
|
||||
|
||||
// Schreibe Datei
|
||||
writeFileSync(contentPath, JSON.stringify(content, null, 2), 'utf-8')
|
||||
console.log(`[Content] Saved successfully to: ${contentPath}`)
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler'
|
||||
console.error(`[Content] Error saving: ${errorMessage}`)
|
||||
return { success: false, error: errorMessage }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt Default Content zurueck (fuer Reset)
|
||||
*/
|
||||
export function getDefaultContent(): WebsiteContent {
|
||||
return defaultContent
|
||||
}
|
||||
1173
website/lib/i18n.ts
Normal file
1173
website/lib/i18n.ts
Normal file
File diff suppressed because it is too large
Load Diff
211
website/lib/llm-mode-context.tsx
Normal file
211
website/lib/llm-mode-context.tsx
Normal file
@@ -0,0 +1,211 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* LLM Mode Context
|
||||
*
|
||||
* Globaler Kontext fuer den LLM-Modus (Hybrid, Local-Only, Cloud-Only, Auto).
|
||||
* Steuert welche Provider fuer KI-Anfragen verwendet werden.
|
||||
*
|
||||
* Modi:
|
||||
* - hybrid: Lokal zuerst (Ollama), Cloud als Fallback (Claude/OpenAI)
|
||||
* - local-only: Nur lokale Modelle (Ollama) - maximaler Datenschutz
|
||||
* - cloud-only: Nur Cloud-Provider (Claude, OpenAI) - beste Qualitaet
|
||||
* - auto: Automatische Auswahl basierend auf Komplexitaet
|
||||
*/
|
||||
|
||||
import { createContext, useContext, useState, useEffect, ReactNode, useCallback } from 'react'
|
||||
|
||||
// LLM Mode Types
|
||||
export type LLMMode = 'hybrid' | 'local-only' | 'cloud-only' | 'auto'
|
||||
|
||||
export interface LLMModeConfig {
|
||||
mode: LLMMode
|
||||
label: string
|
||||
description: string
|
||||
providers: {
|
||||
enableOllama: boolean
|
||||
enableClaude: boolean
|
||||
enableOpenAI: boolean
|
||||
}
|
||||
icon: string
|
||||
}
|
||||
|
||||
// Mode configurations
|
||||
export const LLM_MODE_CONFIGS: Record<LLMMode, LLMModeConfig> = {
|
||||
hybrid: {
|
||||
mode: 'hybrid',
|
||||
label: 'Hybrid',
|
||||
description: 'Lokal zuerst, Cloud als Fallback',
|
||||
providers: {
|
||||
enableOllama: true,
|
||||
enableClaude: true,
|
||||
enableOpenAI: false,
|
||||
},
|
||||
icon: '🔄',
|
||||
},
|
||||
'local-only': {
|
||||
mode: 'local-only',
|
||||
label: 'Nur Lokal',
|
||||
description: 'Maximaler Datenschutz (nur Ollama)',
|
||||
providers: {
|
||||
enableOllama: true,
|
||||
enableClaude: false,
|
||||
enableOpenAI: false,
|
||||
},
|
||||
icon: '🔒',
|
||||
},
|
||||
'cloud-only': {
|
||||
mode: 'cloud-only',
|
||||
label: 'Nur Cloud',
|
||||
description: 'Beste Qualitaet (Claude/OpenAI)',
|
||||
providers: {
|
||||
enableOllama: false,
|
||||
enableClaude: true,
|
||||
enableOpenAI: true,
|
||||
},
|
||||
icon: '☁️',
|
||||
},
|
||||
auto: {
|
||||
mode: 'auto',
|
||||
label: 'Auto',
|
||||
description: 'Automatisch nach Komplexitaet',
|
||||
providers: {
|
||||
enableOllama: true,
|
||||
enableClaude: true,
|
||||
enableOpenAI: true,
|
||||
},
|
||||
icon: '⚡',
|
||||
},
|
||||
}
|
||||
|
||||
// Context type
|
||||
interface LLMModeContextType {
|
||||
mode: LLMMode
|
||||
config: LLMModeConfig
|
||||
setMode: (mode: LLMMode) => void
|
||||
// Convenience getters for provider states
|
||||
enableOllama: boolean
|
||||
enableClaude: boolean
|
||||
enableOpenAI: boolean
|
||||
// Override individual providers (for fine-tuning within a mode)
|
||||
setProviderOverrides: (overrides: Partial<LLMModeConfig['providers']>) => void
|
||||
providerOverrides: Partial<LLMModeConfig['providers']>
|
||||
clearOverrides: () => void
|
||||
}
|
||||
|
||||
const LLMModeContext = createContext<LLMModeContextType | undefined>(undefined)
|
||||
|
||||
const STORAGE_KEY = 'breakpilot-llm-mode'
|
||||
const OVERRIDES_KEY = 'breakpilot-llm-overrides'
|
||||
const DEFAULT_MODE: LLMMode = 'hybrid'
|
||||
|
||||
export function LLMModeProvider({ children }: { children: ReactNode }) {
|
||||
const [mode, setModeState] = useState<LLMMode>(DEFAULT_MODE)
|
||||
const [providerOverrides, setProviderOverridesState] = useState<Partial<LLMModeConfig['providers']>>({})
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
// Load from localStorage on mount
|
||||
useEffect(() => {
|
||||
try {
|
||||
const storedMode = localStorage.getItem(STORAGE_KEY)
|
||||
if (storedMode && storedMode in LLM_MODE_CONFIGS) {
|
||||
setModeState(storedMode as LLMMode)
|
||||
}
|
||||
|
||||
const storedOverrides = localStorage.getItem(OVERRIDES_KEY)
|
||||
if (storedOverrides) {
|
||||
setProviderOverridesState(JSON.parse(storedOverrides))
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to load LLM mode from localStorage:', e)
|
||||
}
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
const setMode = useCallback((newMode: LLMMode) => {
|
||||
setModeState(newMode)
|
||||
// Clear overrides when switching modes
|
||||
setProviderOverridesState({})
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, newMode)
|
||||
localStorage.removeItem(OVERRIDES_KEY)
|
||||
} catch (e) {
|
||||
console.warn('Failed to save LLM mode to localStorage:', e)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const setProviderOverrides = useCallback((overrides: Partial<LLMModeConfig['providers']>) => {
|
||||
setProviderOverridesState(prev => {
|
||||
const newOverrides = { ...prev, ...overrides }
|
||||
try {
|
||||
localStorage.setItem(OVERRIDES_KEY, JSON.stringify(newOverrides))
|
||||
} catch (e) {
|
||||
console.warn('Failed to save provider overrides:', e)
|
||||
}
|
||||
return newOverrides
|
||||
})
|
||||
}, [])
|
||||
|
||||
const clearOverrides = useCallback(() => {
|
||||
setProviderOverridesState({})
|
||||
try {
|
||||
localStorage.removeItem(OVERRIDES_KEY)
|
||||
} catch (e) {
|
||||
console.warn('Failed to clear provider overrides:', e)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const config = LLM_MODE_CONFIGS[mode]
|
||||
|
||||
// Compute effective provider states (mode defaults + overrides)
|
||||
const enableOllama = providerOverrides.enableOllama ?? config.providers.enableOllama
|
||||
const enableClaude = providerOverrides.enableClaude ?? config.providers.enableClaude
|
||||
const enableOpenAI = providerOverrides.enableOpenAI ?? config.providers.enableOpenAI
|
||||
|
||||
const value: LLMModeContextType = {
|
||||
mode,
|
||||
config,
|
||||
setMode,
|
||||
enableOllama,
|
||||
enableClaude,
|
||||
enableOpenAI,
|
||||
setProviderOverrides,
|
||||
providerOverrides,
|
||||
clearOverrides,
|
||||
}
|
||||
|
||||
// Prevent hydration mismatch
|
||||
if (!mounted) {
|
||||
return (
|
||||
<LLMModeContext.Provider
|
||||
value={{
|
||||
...value,
|
||||
mode: DEFAULT_MODE,
|
||||
config: LLM_MODE_CONFIGS[DEFAULT_MODE],
|
||||
enableOllama: LLM_MODE_CONFIGS[DEFAULT_MODE].providers.enableOllama,
|
||||
enableClaude: LLM_MODE_CONFIGS[DEFAULT_MODE].providers.enableClaude,
|
||||
enableOpenAI: LLM_MODE_CONFIGS[DEFAULT_MODE].providers.enableOpenAI,
|
||||
providerOverrides: {},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</LLMModeContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
return <LLMModeContext.Provider value={value}>{children}</LLMModeContext.Provider>
|
||||
}
|
||||
|
||||
export function useLLMMode() {
|
||||
const context = useContext(LLMModeContext)
|
||||
if (context === undefined) {
|
||||
throw new Error('useLLMMode must be used within a LLMModeProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
// Utility hook for getting provider settings without full context
|
||||
export function useLLMProviders() {
|
||||
const { enableOllama, enableClaude, enableOpenAI, mode } = useLLMMode()
|
||||
return { enableOllama, enableClaude, enableOpenAI, mode }
|
||||
}
|
||||
Reference in New Issue
Block a user