Some checks failed
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Successful in 49s
CI/CD / test-python-backend-compliance (push) Successful in 46s
CI/CD / test-python-document-crawler (push) Successful in 30s
CI/CD / test-python-dsms-gateway (push) Successful in 31s
CI/CD / validate-canonical-controls (push) Successful in 23s
CI/CD / Deploy (push) Failing after 7s
- SDKSidebar (918→236 LOC): extracted icons to SidebarIcons, sub-components (ProgressBar, PackageIndicator, StepItem, CorpusStalenessInfo, AdditionalModuleItem) to SidebarSubComponents, and the full module nav list to SidebarModuleNav - ScopeWizardTab (794→339 LOC): extracted DatenkategorienBlock9 and its dept mapping constants to DatenkategorienBlock, and question rendering (all switch-case types + help text) to ScopeQuestionRenderer - All files now under 500 LOC hard cap; zero behavior changes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
283 lines
9.3 KiB
TypeScript
283 lines
9.3 KiB
TypeScript
import React from 'react'
|
|
import Link from 'next/link'
|
|
import type { SDKStep, RAGCorpusStatus, SDKPackageId } from '@/lib/sdk'
|
|
import { CheckIcon, LockIcon, WarningIcon, ChevronDownIcon } from './SidebarIcons'
|
|
|
|
function withProject(url: string, projectId?: string): string {
|
|
if (!projectId) return url
|
|
const separator = url.includes('?') ? '&' : '?'
|
|
return `${url}${separator}project=${projectId}`
|
|
}
|
|
|
|
// =============================================================================
|
|
// PROGRESS BAR
|
|
// =============================================================================
|
|
|
|
interface ProgressBarProps {
|
|
value: number
|
|
className?: string
|
|
}
|
|
|
|
export function ProgressBar({ value, className = '' }: ProgressBarProps) {
|
|
return (
|
|
<div className={`h-1 bg-gray-200 rounded-full overflow-hidden ${className}`}>
|
|
<div
|
|
className="h-full bg-purple-600 rounded-full transition-all duration-500"
|
|
style={{ width: `${Math.min(100, Math.max(0, value))}%` }}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// PACKAGE INDICATOR
|
|
// =============================================================================
|
|
|
|
interface PackageIndicatorProps {
|
|
packageId: SDKPackageId
|
|
order: number
|
|
name: string
|
|
icon: string
|
|
completion: number
|
|
isActive: boolean
|
|
isExpanded: boolean
|
|
isLocked: boolean
|
|
onToggle: () => void
|
|
collapsed: boolean
|
|
}
|
|
|
|
export function PackageIndicator({
|
|
order,
|
|
name,
|
|
icon,
|
|
completion,
|
|
isActive,
|
|
isExpanded,
|
|
isLocked,
|
|
onToggle,
|
|
collapsed,
|
|
}: PackageIndicatorProps) {
|
|
if (collapsed) {
|
|
return (
|
|
<button
|
|
onClick={onToggle}
|
|
className={`w-full flex items-center justify-center py-3 transition-colors ${
|
|
isActive
|
|
? 'bg-purple-50 border-l-4 border-purple-600'
|
|
: isLocked
|
|
? 'border-l-4 border-transparent opacity-50'
|
|
: 'hover:bg-gray-50 border-l-4 border-transparent'
|
|
}`}
|
|
title={`${order}. ${name} (${completion}%)`}
|
|
>
|
|
<div
|
|
className={`w-8 h-8 rounded-full flex items-center justify-center text-lg ${
|
|
isLocked
|
|
? 'bg-gray-200 text-gray-400'
|
|
: isActive
|
|
? 'bg-purple-600 text-white'
|
|
: completion === 100
|
|
? 'bg-green-500 text-white'
|
|
: 'bg-gray-200 text-gray-600'
|
|
}`}
|
|
>
|
|
{isLocked ? <LockIcon /> : completion === 100 ? <CheckIcon /> : icon}
|
|
</div>
|
|
</button>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<button
|
|
onClick={onToggle}
|
|
disabled={isLocked}
|
|
className={`w-full flex items-center justify-between px-4 py-3 text-left transition-colors ${
|
|
isLocked
|
|
? 'opacity-50 cursor-not-allowed'
|
|
: isActive
|
|
? 'bg-purple-50 border-l-4 border-purple-600'
|
|
: 'hover:bg-gray-50 border-l-4 border-transparent'
|
|
}`}
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<div
|
|
className={`w-8 h-8 rounded-full flex items-center justify-center text-lg ${
|
|
isLocked
|
|
? 'bg-gray-200 text-gray-400'
|
|
: isActive
|
|
? 'bg-purple-600 text-white'
|
|
: completion === 100
|
|
? 'bg-green-500 text-white'
|
|
: 'bg-gray-200 text-gray-600'
|
|
}`}
|
|
>
|
|
{isLocked ? <LockIcon /> : completion === 100 ? <CheckIcon /> : icon}
|
|
</div>
|
|
<div>
|
|
<div className={`font-medium text-sm ${isActive ? 'text-purple-900' : isLocked ? 'text-gray-400' : 'text-gray-700'}`}>
|
|
{order}. {name}
|
|
</div>
|
|
<div className="text-xs text-gray-500">{completion}%</div>
|
|
</div>
|
|
</div>
|
|
{!isLocked && <ChevronDownIcon className={`transition-transform ${isExpanded ? 'rotate-180' : ''}`} />}
|
|
</button>
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// STEP ITEM
|
|
// =============================================================================
|
|
|
|
interface StepItemProps {
|
|
step: SDKStep
|
|
isActive: boolean
|
|
isCompleted: boolean
|
|
isLocked: boolean
|
|
checkpointStatus?: 'passed' | 'failed' | 'warning' | 'pending'
|
|
collapsed: boolean
|
|
projectId?: string
|
|
}
|
|
|
|
export function StepItem({ step, isActive, isCompleted, isLocked, checkpointStatus, collapsed, projectId }: StepItemProps) {
|
|
const content = (
|
|
<div
|
|
className={`flex items-center gap-3 px-4 py-2.5 text-sm transition-colors ${
|
|
collapsed ? 'justify-center' : ''
|
|
} ${
|
|
isActive
|
|
? 'bg-purple-100 text-purple-900 font-medium'
|
|
: isLocked
|
|
? 'text-gray-400 cursor-not-allowed'
|
|
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
|
|
}`}
|
|
title={collapsed ? step.name : undefined}
|
|
>
|
|
<div className="flex-shrink-0">
|
|
{isCompleted ? (
|
|
<div className="w-5 h-5 rounded-full bg-green-500 text-white flex items-center justify-center">
|
|
<CheckIcon />
|
|
</div>
|
|
) : isLocked ? (
|
|
<div className="w-5 h-5 rounded-full bg-gray-200 text-gray-400 flex items-center justify-center">
|
|
<LockIcon />
|
|
</div>
|
|
) : isActive ? (
|
|
<div className="w-5 h-5 rounded-full bg-purple-600 flex items-center justify-center">
|
|
<div className="w-2 h-2 rounded-full bg-white" />
|
|
</div>
|
|
) : (
|
|
<div className="w-5 h-5 rounded-full border-2 border-gray-300" />
|
|
)}
|
|
</div>
|
|
|
|
{!collapsed && <span className="flex-1 truncate">{step.nameShort}</span>}
|
|
|
|
{!collapsed && checkpointStatus && checkpointStatus !== 'pending' && (
|
|
<div className="flex-shrink-0">
|
|
{checkpointStatus === 'passed' ? (
|
|
<div className="w-4 h-4 rounded-full bg-green-100 text-green-600 flex items-center justify-center">
|
|
<CheckIcon />
|
|
</div>
|
|
) : checkpointStatus === 'failed' ? (
|
|
<div className="w-4 h-4 rounded-full bg-red-100 text-red-600 flex items-center justify-center">
|
|
<span className="text-xs font-bold">!</span>
|
|
</div>
|
|
) : (
|
|
<div className="w-4 h-4 rounded-full bg-yellow-100 text-yellow-600 flex items-center justify-center">
|
|
<WarningIcon />
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
|
|
if (isLocked) return content
|
|
|
|
return (
|
|
<Link href={withProject(step.url, projectId)} className="block">
|
|
{content}
|
|
</Link>
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// ADDITIONAL MODULE ITEM
|
|
// =============================================================================
|
|
|
|
interface AdditionalModuleItemProps {
|
|
href: string
|
|
icon: React.ReactNode
|
|
label: string
|
|
isActive: boolean | undefined
|
|
collapsed: boolean
|
|
projectId?: string
|
|
}
|
|
|
|
export function AdditionalModuleItem({ href, icon, label, isActive, collapsed, projectId }: AdditionalModuleItemProps) {
|
|
const isExternal = href.startsWith('http')
|
|
const className = `flex items-center gap-3 px-4 py-2.5 text-sm transition-colors ${
|
|
collapsed ? 'justify-center' : ''
|
|
} ${
|
|
isActive
|
|
? 'bg-purple-100 text-purple-900 font-medium'
|
|
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'
|
|
}`
|
|
|
|
if (isExternal) {
|
|
return (
|
|
<a href={href} target="_blank" rel="noopener noreferrer" className={className} title={collapsed ? label : undefined}>
|
|
{icon}
|
|
{!collapsed && (
|
|
<span className="flex items-center gap-1">
|
|
{label}
|
|
<svg className="w-3 h-3 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
|
</svg>
|
|
</span>
|
|
)}
|
|
</a>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<Link href={withProject(href, projectId)} className={className} title={collapsed ? label : undefined}>
|
|
{icon}
|
|
{!collapsed && <span>{label}</span>}
|
|
</Link>
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// CORPUS STALENESS INFO
|
|
// =============================================================================
|
|
|
|
export function CorpusStalenessInfo({ ragCorpusStatus }: { ragCorpusStatus: RAGCorpusStatus }) {
|
|
const collections = ragCorpusStatus.collections
|
|
const collectionNames = Object.keys(collections)
|
|
if (collectionNames.length === 0) return null
|
|
|
|
const lastUpdated = collectionNames.reduce((latest, name) => {
|
|
const updated = new Date(collections[name].last_updated)
|
|
return updated > latest ? updated : latest
|
|
}, new Date(0))
|
|
|
|
const daysSinceUpdate = Math.floor((Date.now() - lastUpdated.getTime()) / (1000 * 60 * 60 * 24))
|
|
const totalChunks = collectionNames.reduce((sum, name) => sum + collections[name].chunks_count, 0)
|
|
|
|
return (
|
|
<div className="px-4 py-2 border-b border-gray-100">
|
|
<div className="flex items-center gap-2 text-xs">
|
|
<div className={`w-2 h-2 rounded-full flex-shrink-0 ${daysSinceUpdate > 30 ? 'bg-amber-400' : 'bg-green-400'}`} />
|
|
<span className="text-gray-500 truncate">RAG Corpus: {totalChunks} Chunks</span>
|
|
</div>
|
|
{daysSinceUpdate > 30 && (
|
|
<div className="mt-1 text-xs text-amber-600 bg-amber-50 rounded px-2 py-1">
|
|
Corpus {daysSinceUpdate}d alt — Re-Evaluation empfohlen
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|