Each page.tsx exceeded the 500-LOC hard cap. Extracted components and hooks into colocated _components/ and _hooks/ directories; page.tsx is now a thin orchestrator. - controls/page.tsx: 944 → 180 LOC; extracted ControlCard, AddControlForm, LoadingSkeleton, TransitionErrorBanner, StatsCards, FilterBar, RAGPanel into _components/ and useControlsData, useRAGSuggestions into _hooks/; types into _types.ts - training/page.tsx: 780 → 288 LOC; extracted ContentTab (inline content generator tab) into _components/ContentTab.tsx - control-provenance/page.tsx: 739 → 122 LOC; extracted MarkdownRenderer, UsageBadge, PermBadge, LicenseMatrix, SourceRegistry into _components/; PROVENANCE_SECTIONS static data into _data/provenance-sections.ts - iace/[projectId]/verification/page.tsx: 673 → 196 LOC; extracted StatusBadge, VerificationForm, CompleteModal, SuggestEvidenceModal, VerificationTable into _components/ Zero behavior changes; logic relocated verbatim. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
136 lines
5.9 KiB
TypeScript
136 lines
5.9 KiB
TypeScript
'use client'
|
|
|
|
import type { RAGControlSuggestion } from '../_types'
|
|
|
|
export function RAGPanel({
|
|
selectedRequirementId,
|
|
onSelectedRequirementIdChange,
|
|
requirements,
|
|
onSuggestControls,
|
|
ragLoading,
|
|
ragSuggestions,
|
|
onAddSuggestion,
|
|
onClose,
|
|
}: {
|
|
selectedRequirementId: string
|
|
onSelectedRequirementIdChange: (id: string) => void
|
|
requirements: { id: string; title?: string }[]
|
|
onSuggestControls: () => void
|
|
ragLoading: boolean
|
|
ragSuggestions: RAGControlSuggestion[]
|
|
onAddSuggestion: (s: RAGControlSuggestion) => void
|
|
onClose: () => void
|
|
}) {
|
|
return (
|
|
<div className="bg-purple-50 border border-purple-200 rounded-xl p-6">
|
|
<div className="flex items-start justify-between mb-4">
|
|
<div>
|
|
<h3 className="text-lg font-semibold text-purple-900">KI-Controls aus RAG vorschlagen</h3>
|
|
<p className="text-sm text-purple-700 mt-1">
|
|
Geben Sie eine Anforderungs-ID ein. Das KI-System analysiert die Anforderung mit Hilfe des RAG-Corpus
|
|
und schlaegt passende Controls vor.
|
|
</p>
|
|
</div>
|
|
<button onClick={onClose} className="text-purple-400 hover:text-purple-600 ml-4">
|
|
<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>
|
|
|
|
<div className="flex items-center gap-3 mb-4">
|
|
<input
|
|
type="text"
|
|
value={selectedRequirementId}
|
|
onChange={e => onSelectedRequirementIdChange(e.target.value)}
|
|
placeholder="Anforderungs-UUID eingeben..."
|
|
className="flex-1 px-4 py-2 border border-purple-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent bg-white"
|
|
/>
|
|
{requirements.length > 0 && (
|
|
<select
|
|
value={selectedRequirementId}
|
|
onChange={e => onSelectedRequirementIdChange(e.target.value)}
|
|
className="px-3 py-2 border border-purple-300 rounded-lg bg-white text-sm focus:ring-2 focus:ring-purple-500"
|
|
>
|
|
<option value="">Aus Liste waehlen...</option>
|
|
{requirements.slice(0, 20).map(r => (
|
|
<option key={r.id} value={r.id}>{r.id.substring(0, 8)}... — {r.title?.substring(0, 40)}</option>
|
|
))}
|
|
</select>
|
|
)}
|
|
<button
|
|
onClick={onSuggestControls}
|
|
disabled={ragLoading || !selectedRequirementId}
|
|
className={`flex items-center gap-2 px-5 py-2 rounded-lg font-medium transition-colors ${
|
|
ragLoading || !selectedRequirementId
|
|
? 'bg-gray-200 text-gray-400 cursor-not-allowed'
|
|
: 'bg-purple-600 text-white hover:bg-purple-700'
|
|
}`}
|
|
>
|
|
{ragLoading ? (
|
|
<>
|
|
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
|
Analysiere...
|
|
</>
|
|
) : (
|
|
<>
|
|
<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>
|
|
Vorschlaege generieren
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
|
|
{ragSuggestions.length > 0 && (
|
|
<div className="space-y-3">
|
|
<h4 className="text-sm font-semibold text-purple-800">{ragSuggestions.length} Vorschlaege gefunden:</h4>
|
|
{ragSuggestions.map((suggestion) => (
|
|
<div key={suggestion.control_id} className="bg-white border border-purple-200 rounded-lg p-4">
|
|
<div className="flex items-start justify-between gap-3">
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<span className="px-2 py-0.5 text-xs bg-purple-100 text-purple-700 rounded font-mono">
|
|
{suggestion.control_id}
|
|
</span>
|
|
<span className="px-2 py-0.5 text-xs bg-gray-100 text-gray-600 rounded">{suggestion.domain}</span>
|
|
<span className="text-xs text-gray-500">Konfidenz: {Math.round(suggestion.confidence_score * 100)}%</span>
|
|
</div>
|
|
<h5 className="font-semibold text-gray-900">{suggestion.title}</h5>
|
|
<p className="text-sm text-gray-600 mt-1">{suggestion.description}</p>
|
|
{suggestion.pass_criteria && (
|
|
<p className="text-xs text-gray-500 mt-1">
|
|
<span className="font-medium">Erfolgskriterium:</span> {suggestion.pass_criteria}
|
|
</p>
|
|
)}
|
|
{suggestion.is_automated && (
|
|
<span className="mt-1 inline-block px-2 py-0.5 text-xs bg-green-100 text-green-700 rounded">
|
|
Automatisierbar {suggestion.automation_tool ? `(${suggestion.automation_tool})` : ''}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<button
|
|
onClick={() => onAddSuggestion(suggestion)}
|
|
className="flex-shrink-0 flex items-center gap-1 px-3 py-1.5 bg-purple-600 text-white text-sm rounded-lg hover:bg-purple-700 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="M12 4v16m8-8H4" />
|
|
</svg>
|
|
Hinzufuegen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{!ragLoading && ragSuggestions.length === 0 && selectedRequirementId && (
|
|
<p className="text-sm text-purple-600 italic">
|
|
Klicken Sie auf "Vorschlaege generieren", um KI-Controls abzurufen.
|
|
</p>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|