controls/page.tsx 840→211 LOC — extracted StatsCards, FilterBar, ControlCard, AddControlForm, RAGPanel, LoadingSkeleton to _components/; useControlsData, useRAGSuggestions to _hooks/; shared types to _types.ts. dsr/[requestId]/page.tsx 854→172 LOC — extracted detail panels and timeline components to _components/. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
142 lines
6.0 KiB
TypeScript
142 lines
6.0 KiB
TypeScript
'use client'
|
|
|
|
import React from 'react'
|
|
import { RAGControlSuggestion } from '../_types'
|
|
|
|
export function RAGPanel({
|
|
selectedRequirementId,
|
|
onSelectedRequirementIdChange,
|
|
requirements,
|
|
onSuggestControls,
|
|
ragLoading,
|
|
ragSuggestions,
|
|
onAddSuggestion,
|
|
onClose,
|
|
}: {
|
|
selectedRequirementId: string
|
|
onSelectedRequirementIdChange: (v: 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 schlägt 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 wählen...</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>
|
|
Vorschläge generieren
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Suggestions */}
|
|
{ragSuggestions.length > 0 && (
|
|
<div className="space-y-3">
|
|
<h4 className="text-sm font-semibold text-purple-800">{ragSuggestions.length} Vorschläge 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>
|
|
Hinzufügen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{!ragLoading && ragSuggestions.length === 0 && selectedRequirementId && (
|
|
<p className="text-sm text-purple-600 italic">
|
|
Klicken Sie auf "Vorschläge generieren", um KI-Controls abzurufen.
|
|
</p>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|