Split 4 oversized component files (all >500 LOC) into sibling modules: - SDKPipelineSidebar → Icons + Parts siblings (193/264/35 LOC) - SourcesTab → SourceModals sibling (311/243 LOC) - ScopeDecisionTab → ScopeDecisionSections sibling (127/444 LOC) - ComplianceAdvisorWidget → ComplianceAdvisorParts sibling (265/131 LOC) Zero behavior changes; all logic relocated verbatim. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
132 lines
5.0 KiB
TypeScript
132 lines
5.0 KiB
TypeScript
'use client'
|
|
|
|
// =============================================================================
|
|
// ComplianceAdvisorWidget — shared constants and sub-components
|
|
// =============================================================================
|
|
|
|
// =============================================================================
|
|
// EXAMPLE QUESTIONS
|
|
// =============================================================================
|
|
|
|
export const EXAMPLE_QUESTIONS: Record<string, string[]> = {
|
|
vvt: [
|
|
'Was ist ein Verarbeitungsverzeichnis?',
|
|
'Welche Informationen muss ich erfassen?',
|
|
'Wie dokumentiere ich die Rechtsgrundlage?',
|
|
],
|
|
'compliance-scope': [
|
|
'Was bedeutet L3?',
|
|
'Wann brauche ich eine DSFA?',
|
|
'Was ist der Unterschied zwischen L2 und L3?',
|
|
],
|
|
tom: [
|
|
'Was sind TOM?',
|
|
'Welche Massnahmen sind erforderlich?',
|
|
'Wie dokumentiere ich Verschluesselung?',
|
|
],
|
|
dsfa: [
|
|
'Was ist eine DSFA?',
|
|
'Wann ist eine DSFA verpflichtend?',
|
|
'Wie bewerte ich Risiken?',
|
|
],
|
|
loeschfristen: [
|
|
'Wie definiere ich Loeschfristen?',
|
|
'Was ist der Unterschied zwischen Loeschpflicht und Aufbewahrungspflicht?',
|
|
'Wann muss ich Daten loeschen?',
|
|
],
|
|
default: [
|
|
'Wie starte ich mit dem SDK?',
|
|
'Was ist der erste Schritt?',
|
|
'Welche Compliance-Anforderungen gelten fuer KI-Systeme?',
|
|
],
|
|
}
|
|
|
|
// =============================================================================
|
|
// TYPES
|
|
// =============================================================================
|
|
|
|
export interface Message {
|
|
id: string
|
|
role: 'user' | 'agent'
|
|
content: string
|
|
timestamp: Date
|
|
}
|
|
|
|
// =============================================================================
|
|
// EmptyState — shown when no messages yet
|
|
// =============================================================================
|
|
|
|
interface EmptyStateProps {
|
|
exampleQuestions: string[]
|
|
onExampleClick: (question: string) => void
|
|
}
|
|
|
|
export function AdvisorEmptyState({ exampleQuestions, onExampleClick }: EmptyStateProps) {
|
|
return (
|
|
<div className="text-center py-8">
|
|
<div className="w-16 h-16 bg-purple-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
<svg className="w-8 h-8 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" />
|
|
</svg>
|
|
</div>
|
|
<h3 className="text-sm font-medium text-gray-900 mb-2">Willkommen beim Compliance Advisor</h3>
|
|
<p className="text-xs text-gray-500 mb-4">Stellen Sie Fragen zu DSGVO, KI-Verordnung und mehr.</p>
|
|
<div className="text-left space-y-2">
|
|
<p className="text-xs font-medium text-gray-700 mb-2">Beispielfragen:</p>
|
|
{exampleQuestions.map((question, idx) => (
|
|
<button
|
|
key={idx}
|
|
onClick={() => onExampleClick(question)}
|
|
className="w-full text-left px-3 py-2 text-xs bg-white hover:bg-purple-50 border border-gray-200 rounded-lg transition-colors text-gray-700"
|
|
>
|
|
{question}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// MessageList — renders messages + typing indicator
|
|
// =============================================================================
|
|
|
|
interface MessageListProps {
|
|
messages: Message[]
|
|
isTyping: boolean
|
|
messagesEndRef: React.RefObject<HTMLDivElement | null>
|
|
}
|
|
|
|
export function AdvisorMessageList({ messages, isTyping, messagesEndRef }: MessageListProps) {
|
|
return (
|
|
<>
|
|
{messages.map((message) => (
|
|
<div key={message.id} className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}>
|
|
<div className={`max-w-[80%] rounded-lg px-3 py-2 ${message.role === 'user' ? 'bg-indigo-600 text-white' : 'bg-white border border-gray-200 text-gray-800'}`}>
|
|
<p className={`text-sm ${message.role === 'agent' ? 'whitespace-pre-wrap' : ''}`}>
|
|
{message.content || (message.role === 'agent' && isTyping ? '' : message.content)}
|
|
</p>
|
|
<p className={`text-xs mt-1 ${message.role === 'user' ? 'text-indigo-200' : 'text-gray-400'}`}>
|
|
{message.timestamp.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
{isTyping && (
|
|
<div className="flex justify-start">
|
|
<div className="bg-white border border-gray-200 rounded-lg px-3 py-2">
|
|
<div className="flex space-x-1">
|
|
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" />
|
|
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }} />
|
|
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div ref={messagesEndRef} />
|
|
</>
|
|
)
|
|
}
|