Files
breakpilot-compliance/admin-compliance/app/sdk/controls/_components/RAGPanel.tsx
Sharang Parnerkar 0125199c76 refactor(admin): split controls, training, control-provenance, iace/verification pages
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>
2026-04-16 22:50:15 +02:00

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 &quot;Vorschlaege generieren&quot;, um KI-Controls abzurufen.
</p>
)}
</div>
)
}