Adds the missing navigation entries and SVG icons for all AI tool modules so they appear in the sidebar navigation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
463 lines
18 KiB
TypeScript
463 lines
18 KiB
TypeScript
'use client'
|
||
|
||
/**
|
||
* AI Tools Sidebar
|
||
*
|
||
* Kompakte Sidebar-Komponente für Cross-Navigation zwischen KI-Werkzeugen.
|
||
* Zeigt Shared Resources 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'
|
||
|
||
export type AIToolId = 'llm-compare' | 'test-quality' | 'gpu' | 'ocr-compare' | 'ocr-labeling' | 'rag-pipeline' | 'magic-help'
|
||
|
||
export interface AIToolModule {
|
||
id: AIToolId
|
||
name: string
|
||
href: string
|
||
description: string
|
||
icon: string
|
||
}
|
||
|
||
export const AI_TOOLS_MODULES: AIToolModule[] = [
|
||
{
|
||
id: 'llm-compare',
|
||
name: 'LLM Vergleich',
|
||
href: '/ai/llm-compare',
|
||
description: 'KI-Provider vergleichen',
|
||
icon: '⚖️',
|
||
},
|
||
{
|
||
id: 'test-quality',
|
||
name: 'Test Quality (BQAS)',
|
||
href: '/ai/test-quality',
|
||
description: 'Golden Suite & Tests',
|
||
icon: '🧪',
|
||
},
|
||
{
|
||
id: 'gpu',
|
||
name: 'GPU Infrastruktur',
|
||
href: '/ai/gpu',
|
||
description: 'vast.ai Management',
|
||
icon: '🖥️',
|
||
},
|
||
{
|
||
id: 'ocr-compare',
|
||
name: 'OCR Vergleich',
|
||
href: '/ai/ocr-compare',
|
||
description: 'OCR-Methoden & Vokabel-Extraktion',
|
||
icon: '🔍',
|
||
},
|
||
{
|
||
id: 'ocr-labeling',
|
||
name: 'OCR Labeling',
|
||
href: '/ai/ocr-labeling',
|
||
description: 'Trainingsdaten erstellen',
|
||
icon: '🏷️',
|
||
},
|
||
{
|
||
id: 'rag-pipeline',
|
||
name: 'RAG Pipeline',
|
||
href: '/ai/rag-pipeline',
|
||
description: 'Retrieval Augmented Generation',
|
||
icon: '🔗',
|
||
},
|
||
{
|
||
id: 'magic-help',
|
||
name: 'Magic Help',
|
||
href: '/ai/magic-help',
|
||
description: 'KI-Assistent',
|
||
icon: '✨',
|
||
},
|
||
]
|
||
|
||
export interface AIToolsSidebarProps {
|
||
/** ID des aktuell aktiven Tools */
|
||
currentTool: AIToolId
|
||
/** Optional: Kompakter Modus (nur Icons) */
|
||
compact?: boolean
|
||
/** Optional: Zusätzliche CSS-Klassen */
|
||
className?: string
|
||
}
|
||
|
||
export interface AIToolsSidebarResponsiveProps extends AIToolsSidebarProps {
|
||
/** Position des FAB auf Mobile */
|
||
fabPosition?: 'bottom-right' | 'bottom-left'
|
||
}
|
||
|
||
// Icons für die Tools
|
||
const ToolIcon = ({ id }: { id: string }) => {
|
||
switch (id) {
|
||
case 'llm-compare':
|
||
return (
|
||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
||
d="M3 6l3 1m0 0l-3 9a5.002 5.002 0 006.001 0M6 7l3 9M6 7l6-2m6 2l3-1m-3 1l-3 9a5.002 5.002 0 006.001 0M18 7l3 9m-3-9l-6-2m0-2v2m0 16V5m0 16H9m3 0h3" />
|
||
</svg>
|
||
)
|
||
case 'test-quality':
|
||
return (
|
||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||
</svg>
|
||
)
|
||
case 'gpu':
|
||
return (
|
||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
||
d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
||
</svg>
|
||
)
|
||
case 'ocr-compare':
|
||
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>
|
||
)
|
||
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="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
|
||
</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="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
|
||
</svg>
|
||
)
|
||
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>
|
||
)
|
||
default:
|
||
return null
|
||
}
|
||
}
|
||
|
||
// Werkzeug-Icon für Header
|
||
const WrenchIcon = () => (
|
||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||
</svg>
|
||
)
|
||
|
||
export function AIToolsSidebar({
|
||
currentTool,
|
||
compact = false,
|
||
className = '',
|
||
}: AIToolsSidebarProps) {
|
||
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-violet-50 to-purple-50 dark:from-violet-900/20 dark:to-purple-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-violet-600 dark:text-violet-400">
|
||
<WrenchIcon />
|
||
</span>
|
||
<span className="font-semibold text-slate-700 dark:text-slate-200 text-sm">
|
||
KI-Werkzeuge
|
||
</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">
|
||
{/* Tool Links */}
|
||
<div className="space-y-1">
|
||
{AI_TOOLS_MODULES.map((tool) => (
|
||
<Link
|
||
key={tool.id}
|
||
href={tool.href}
|
||
className={`flex items-center gap-3 px-3 py-2 rounded-lg transition-colors ${
|
||
currentTool === tool.id
|
||
? 'bg-violet-100 dark:bg-violet-900/30 text-violet-700 dark:text-violet-300 font-medium'
|
||
: 'text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-gray-700'
|
||
}`}
|
||
>
|
||
<ToolIcon id={tool.id} />
|
||
<div className="flex-1 min-w-0">
|
||
<div className="text-sm font-medium truncate">{tool.name}</div>
|
||
<div className="text-xs text-slate-500 dark:text-slate-500 truncate">
|
||
{tool.description}
|
||
</div>
|
||
</div>
|
||
{currentTool === tool.id && (
|
||
<span className="flex-shrink-0 w-2 h-2 bg-violet-500 rounded-full" />
|
||
)}
|
||
</Link>
|
||
))}
|
||
</div>
|
||
|
||
{/* Shared Resources 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">
|
||
Shared Resources
|
||
</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-2 text-xs">
|
||
<span title="GPU Infrastruktur">🖥️</span>
|
||
<span className="text-slate-400">→</span>
|
||
<span title="LLM Vergleich">⚖️</span>
|
||
<span className="text-slate-400">→</span>
|
||
<span title="Test Quality">🧪</span>
|
||
</div>
|
||
</div>
|
||
<div className="text-xs text-slate-400 mt-1 px-1">
|
||
GPU-Ressourcen fuer Modelle & Tests
|
||
</div>
|
||
</div>
|
||
|
||
{/* Quick Info zum aktuellen Tool */}
|
||
<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">
|
||
{currentTool === 'llm-compare' && (
|
||
<span>Vergleichen Sie LLM-Antworten verschiedener Provider</span>
|
||
)}
|
||
{currentTool === 'test-quality' && (
|
||
<span>Ueberwachen Sie die Qualitaet der KI-Ausgaben</span>
|
||
)}
|
||
{currentTool === 'gpu' && (
|
||
<span>Verwalten Sie GPU-Instanzen fuer ML-Training</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
/**
|
||
* Responsive Tools Sidebar mit Mobile FAB + Drawer
|
||
*
|
||
* Desktop (xl+): Fixierte Sidebar rechts
|
||
* Mobile/Tablet: Floating Action Button unten rechts, öffnet Drawer
|
||
*/
|
||
export function AIToolsSidebarResponsive({
|
||
currentTool,
|
||
compact = false,
|
||
className = '',
|
||
fabPosition = 'bottom-right',
|
||
}: AIToolsSidebarResponsiveProps) {
|
||
const [isMobileOpen, setIsMobileOpen] = useState(false)
|
||
|
||
// Close drawer on 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}`}>
|
||
<AIToolsSidebar currentTool={currentTool} 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-violet-500 to-purple-500 text-white rounded-full shadow-lg hover:shadow-xl transition-all flex items-center justify-center group`}
|
||
aria-label="KI-Werkzeuge Navigation oeffnen"
|
||
>
|
||
<WrenchIcon />
|
||
{/* Pulse indicator */}
|
||
<span className="absolute -top-1 -right-1 w-3 h-3 bg-amber-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-violet-50 to-purple-50 dark:from-violet-900/20 dark:to-purple-900/20">
|
||
<div className="flex items-center gap-2">
|
||
<span className="text-violet-600 dark:text-violet-400">
|
||
<WrenchIcon />
|
||
</span>
|
||
<span className="font-semibold text-slate-700 dark:text-slate-200">
|
||
KI-Werkzeuge
|
||
</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="Schliessen"
|
||
>
|
||
<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)]">
|
||
{/* Tool Links */}
|
||
<div className="space-y-2">
|
||
{AI_TOOLS_MODULES.map((tool) => (
|
||
<Link
|
||
key={tool.id}
|
||
href={tool.href}
|
||
onClick={() => setIsMobileOpen(false)}
|
||
className={`flex items-center gap-3 px-4 py-3 rounded-xl transition-all ${
|
||
currentTool === tool.id
|
||
? 'bg-violet-100 dark:bg-violet-900/30 text-violet-700 dark:text-violet-300 font-medium shadow-sm'
|
||
: 'text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-gray-800'
|
||
}`}
|
||
>
|
||
<ToolIcon id={tool.id} />
|
||
<div className="flex-1">
|
||
<div className="text-base font-medium">{tool.name}</div>
|
||
<div className="text-sm text-slate-500 dark:text-slate-500">
|
||
{tool.description}
|
||
</div>
|
||
</div>
|
||
{currentTool === tool.id && (
|
||
<span className="flex-shrink-0 w-2.5 h-2.5 bg-violet-500 rounded-full" />
|
||
)}
|
||
</Link>
|
||
))}
|
||
</div>
|
||
|
||
{/* Shared Resources 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">
|
||
Shared Resources
|
||
</div>
|
||
<div className="flex items-center justify-center gap-3 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">GPU</span>
|
||
</div>
|
||
<span className="text-slate-400">→</span>
|
||
<div className="flex flex-col items-center">
|
||
<span className="text-2xl">⚖️</span>
|
||
<span className="text-xs text-slate-500 mt-1">LLM</span>
|
||
</div>
|
||
<span className="text-slate-400">→</span>
|
||
<div className="flex flex-col items-center">
|
||
<span className="text-2xl">🧪</span>
|
||
<span className="text-xs text-slate-500 mt-1">BQAS</span>
|
||
</div>
|
||
</div>
|
||
<div className="text-xs text-slate-400 mt-2 text-center">
|
||
GPU-Ressourcen fuer Modelle & Tests
|
||
</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">
|
||
{currentTool === 'llm-compare' && (
|
||
<>
|
||
<strong className="text-slate-700 dark:text-slate-300">Aktuell:</strong> LLM-Antworten verschiedener Provider vergleichen
|
||
</>
|
||
)}
|
||
{currentTool === 'test-quality' && (
|
||
<>
|
||
<strong className="text-slate-700 dark:text-slate-300">Aktuell:</strong> Qualitaet der KI-Ausgaben ueberwachen
|
||
</>
|
||
)}
|
||
{currentTool === 'gpu' && (
|
||
<>
|
||
<strong className="text-slate-700 dark:text-slate-300">Aktuell:</strong> GPU-Instanzen fuer ML-Training verwalten
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Link zur Pipeline */}
|
||
<div className="pt-4 border-t border-slate-200 dark:border-gray-700">
|
||
<Link
|
||
href="/ai/magic-help"
|
||
onClick={() => setIsMobileOpen(false)}
|
||
className="flex items-center gap-2 px-3 py-2 text-sm text-teal-600 dark:text-teal-400 hover:bg-teal-50 dark:hover:bg-teal-900/20 rounded-lg transition-colors"
|
||
>
|
||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||
</svg>
|
||
<span>Zur KI-Daten-Pipeline</span>
|
||
</Link>
|
||
</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 AIToolsSidebar
|