fix: Restore all files lost during destructive rebase

A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-02-09 09:51:32 +01:00
parent f7487ee240
commit bfdaf63ba9
2009 changed files with 749983 additions and 1731 deletions

View File

@@ -0,0 +1,405 @@
'use client'
/**
* AI Module Sidebar
*
* Kompakte Sidebar-Komponente für Cross-Navigation zwischen AI-Modulen.
* Zeigt den Datenfluss und ermöglicht schnelle Navigation.
*
* Features:
* - Desktop: Fixierte Sidebar rechts
* - Mobile: Floating Action Button mit Slide-In Drawer
*/
import Link from 'next/link'
import { useState, useEffect } from 'react'
import { AI_MODULES, DATA_FLOW_CONNECTIONS, type AIModuleLink } from '@/types/ai-modules'
export interface AIModuleSidebarProps {
/** ID des aktuell aktiven Moduls */
currentModule: 'magic-help' | 'ocr-labeling' | 'rag-pipeline' | 'rag'
/** Optional: Kompakter Modus (nur Icons) */
compact?: boolean
/** Optional: Zusätzliche CSS-Klassen */
className?: string
}
export interface AIModuleSidebarResponsiveProps extends AIModuleSidebarProps {
/** Position des FAB auf Mobile */
fabPosition?: 'bottom-right' | 'bottom-left'
}
// Icons für die Module
const ModuleIcon = ({ id }: { id: string }) => {
switch (id) {
case 'magic-help':
return (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" />
</svg>
)
case 'ocr-labeling':
return (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
</svg>
)
case 'rag-pipeline':
return (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
)
case 'rag':
return (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
)
default:
return null
}
}
// Pfeil-Icon für Datenfluss
const ArrowIcon = () => (
<svg className="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 8l4 4m0 0l-4 4m4-4H3" />
</svg>
)
export function AIModuleSidebar({
currentModule,
compact = false,
className = '',
}: AIModuleSidebarProps) {
const [isExpanded, setIsExpanded] = useState(!compact)
return (
<div className={`bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-slate-200 dark:border-gray-700 overflow-hidden ${className}`}>
{/* Header */}
<div
className="px-4 py-3 bg-gradient-to-r from-teal-50 to-cyan-50 dark:from-teal-900/20 dark:to-cyan-900/20 border-b border-slate-200 dark:border-gray-700 cursor-pointer"
onClick={() => setIsExpanded(!isExpanded)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-teal-600 dark:text-teal-400">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</span>
<span className="font-semibold text-slate-700 dark:text-slate-200 text-sm">
KI-Daten-Pipeline
</span>
</div>
<svg
className={`w-4 h-4 text-slate-400 transition-transform ${isExpanded ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
{/* Content */}
{isExpanded && (
<div className="p-3 space-y-3">
{/* Module Links */}
<div className="space-y-1">
{AI_MODULES.map((module) => (
<Link
key={module.id}
href={module.href}
className={`flex items-center gap-3 px-3 py-2 rounded-lg transition-colors ${
currentModule === module.id
? 'bg-teal-100 dark:bg-teal-900/30 text-teal-700 dark:text-teal-300 font-medium'
: 'text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-gray-700'
}`}
>
<ModuleIcon id={module.id} />
<div className="flex-1 min-w-0">
<div className="text-sm font-medium truncate">{module.name}</div>
<div className="text-xs text-slate-500 dark:text-slate-500 truncate">
{module.description}
</div>
</div>
{currentModule === module.id && (
<span className="flex-shrink-0 w-2 h-2 bg-teal-500 rounded-full" />
)}
</Link>
))}
</div>
{/* Datenfluss-Visualisierung */}
<div className="pt-2 border-t border-slate-200 dark:border-gray-700">
<div className="text-xs font-medium text-slate-500 dark:text-slate-400 mb-2 px-1">
Datenfluss
</div>
<div className="flex items-center justify-between px-2 py-2 bg-slate-50 dark:bg-gray-900 rounded-lg">
<div className="flex items-center gap-1.5 text-xs">
<span title="Magic Help"></span>
<span className="text-slate-400" title="Bidirektional"></span>
<span title="OCR-Labeling">🏷</span>
<ArrowIcon />
<span title="RAG Pipeline">🔄</span>
<ArrowIcon />
<span title="Daten & RAG">🔍</span>
</div>
</div>
</div>
{/* Quick Info zum aktuellen Modul */}
<div className="pt-2 border-t border-slate-200 dark:border-gray-700">
<div className="text-xs text-slate-500 dark:text-slate-400 px-1">
{currentModule === 'magic-help' && (
<span>Testen und verbessern Sie die TrOCR-Handschrifterkennung</span>
)}
{currentModule === 'ocr-labeling' && (
<span>Erstellen Sie Ground Truth Daten für das OCR-Training</span>
)}
{currentModule === 'rag-pipeline' && (
<span>Indexieren Sie Dokumente für die semantische Suche</span>
)}
{currentModule === 'rag' && (
<span>Verwalten und durchsuchen Sie indexierte Dokumente</span>
)}
</div>
</div>
</div>
)}
</div>
)
}
/**
* Kompakte Inline-Version für mobile Ansichten
*/
export function AIModuleNav({ currentModule }: { currentModule: string }) {
return (
<div className="flex items-center gap-1 p-1 bg-slate-100 dark:bg-gray-800 rounded-lg">
{AI_MODULES.map((module) => (
<Link
key={module.id}
href={module.href}
className={`px-3 py-1.5 text-sm rounded-md transition-colors ${
currentModule === module.id
? 'bg-white dark:bg-gray-700 text-teal-600 dark:text-teal-400 font-medium shadow-sm'
: 'text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-200'
}`}
title={module.description}
>
{module.name}
</Link>
))}
</div>
)
}
/**
* Responsive Sidebar mit Mobile FAB + Drawer
*
* Desktop (xl+): Fixierte Sidebar rechts
* Mobile/Tablet: Floating Action Button unten rechts, öffnet Drawer
*/
export function AIModuleSidebarResponsive({
currentModule,
compact = false,
className = '',
fabPosition = 'bottom-right',
}: AIModuleSidebarResponsiveProps) {
const [isMobileOpen, setIsMobileOpen] = useState(false)
// Close drawer on route change or escape key
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') setIsMobileOpen(false)
}
window.addEventListener('keydown', handleEscape)
return () => window.removeEventListener('keydown', handleEscape)
}, [])
// Prevent body scroll when drawer is open
useEffect(() => {
if (isMobileOpen) {
document.body.style.overflow = 'hidden'
} else {
document.body.style.overflow = ''
}
return () => {
document.body.style.overflow = ''
}
}, [isMobileOpen])
const fabPositionClasses = fabPosition === 'bottom-right'
? 'right-4 bottom-20'
: 'left-4 bottom-20'
return (
<>
{/* Desktop: Fixed Sidebar */}
<div className={`hidden xl:block fixed right-6 top-24 w-64 z-10 ${className}`}>
<AIModuleSidebar currentModule={currentModule} compact={compact} />
</div>
{/* Mobile/Tablet: FAB */}
<button
onClick={() => setIsMobileOpen(true)}
className={`xl:hidden fixed ${fabPositionClasses} z-40 w-14 h-14 bg-gradient-to-r from-teal-500 to-cyan-500 text-white rounded-full shadow-lg hover:shadow-xl transition-all flex items-center justify-center group`}
aria-label="KI-Daten-Pipeline Navigation öffnen"
>
<svg
className="w-6 h-6 group-hover:scale-110 transition-transform"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
{/* Pulse indicator */}
<span className="absolute -top-1 -right-1 w-3 h-3 bg-orange-400 rounded-full animate-pulse" />
</button>
{/* Mobile/Tablet: Drawer Overlay */}
{isMobileOpen && (
<div className="xl:hidden fixed inset-0 z-50">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/50 backdrop-blur-sm transition-opacity"
onClick={() => setIsMobileOpen(false)}
/>
{/* Drawer */}
<div className="absolute right-0 top-0 bottom-0 w-80 max-w-[85vw] bg-white dark:bg-gray-900 shadow-2xl transform transition-transform animate-slide-in-right">
{/* Drawer Header */}
<div className="flex items-center justify-between px-4 py-4 border-b border-slate-200 dark:border-gray-700 bg-gradient-to-r from-teal-50 to-cyan-50 dark:from-teal-900/20 dark:to-cyan-900/20">
<div className="flex items-center gap-2">
<span className="text-teal-600 dark:text-teal-400">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</span>
<span className="font-semibold text-slate-700 dark:text-slate-200">
KI-Daten-Pipeline
</span>
</div>
<button
onClick={() => setIsMobileOpen(false)}
className="p-2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-300 rounded-lg hover:bg-slate-100 dark:hover:bg-gray-800 transition-colors"
aria-label="Schließen"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* Drawer Content */}
<div className="p-4 space-y-4 overflow-y-auto max-h-[calc(100vh-80px)]">
{/* Module Links */}
<div className="space-y-2">
{AI_MODULES.map((module) => (
<Link
key={module.id}
href={module.href}
onClick={() => setIsMobileOpen(false)}
className={`flex items-center gap-3 px-4 py-3 rounded-xl transition-all ${
currentModule === module.id
? 'bg-teal-100 dark:bg-teal-900/30 text-teal-700 dark:text-teal-300 font-medium shadow-sm'
: 'text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-gray-800'
}`}
>
<ModuleIcon id={module.id} />
<div className="flex-1">
<div className="text-base font-medium">{module.name}</div>
<div className="text-sm text-slate-500 dark:text-slate-500">
{module.description}
</div>
</div>
{currentModule === module.id && (
<span className="flex-shrink-0 w-2.5 h-2.5 bg-teal-500 rounded-full" />
)}
</Link>
))}
</div>
{/* Datenfluss-Visualisierung */}
<div className="pt-4 border-t border-slate-200 dark:border-gray-700">
<div className="text-sm font-medium text-slate-500 dark:text-slate-400 mb-3">
Datenfluss
</div>
<div className="flex items-center justify-center gap-2 p-4 bg-slate-50 dark:bg-gray-800 rounded-xl">
<div className="flex flex-col items-center">
<span className="text-2xl"></span>
<span className="text-xs text-slate-500 mt-1">Magic</span>
</div>
<span className="text-slate-400 text-lg" title="Bidirektional"></span>
<div className="flex flex-col items-center">
<span className="text-2xl">🏷</span>
<span className="text-xs text-slate-500 mt-1">OCR</span>
</div>
<ArrowIcon />
<div className="flex flex-col items-center">
<span className="text-2xl">🔄</span>
<span className="text-xs text-slate-500 mt-1">Pipeline</span>
</div>
<ArrowIcon />
<div className="flex flex-col items-center">
<span className="text-2xl">🔍</span>
<span className="text-xs text-slate-500 mt-1">RAG</span>
</div>
</div>
</div>
{/* Quick Info */}
<div className="pt-4 border-t border-slate-200 dark:border-gray-700">
<div className="text-sm text-slate-600 dark:text-slate-400 p-3 bg-slate-50 dark:bg-gray-800 rounded-xl">
{currentModule === 'magic-help' && (
<>
<strong className="text-slate-700 dark:text-slate-300">Aktuell:</strong> TrOCR-Handschrifterkennung testen und verbessern
</>
)}
{currentModule === 'ocr-labeling' && (
<>
<strong className="text-slate-700 dark:text-slate-300">Aktuell:</strong> Ground Truth Daten für OCR-Training erstellen
</>
)}
{currentModule === 'rag-pipeline' && (
<>
<strong className="text-slate-700 dark:text-slate-300">Aktuell:</strong> Dokumente für semantische Suche indexieren
</>
)}
{currentModule === 'rag' && (
<>
<strong className="text-slate-700 dark:text-slate-300">Aktuell:</strong> Indexierte Dokumente verwalten und durchsuchen
</>
)}
</div>
</div>
</div>
</div>
</div>
)}
{/* CSS for slide-in animation */}
<style jsx>{`
@keyframes slide-in-right {
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
.animate-slide-in-right {
animation: slide-in-right 0.2s ease-out;
}
`}</style>
</>
)
}
export default AIModuleSidebar