website (17 pages + 3 components): - multiplayer/wizard, middleware/wizard+test-wizard, communication - builds/wizard, staff-search, voice, sbom/wizard - foerderantrag, mail/tasks, tools/communication, sbom - compliance/evidence, uni-crawler, brandbook (already done) - CollectionsTab, IngestionTab, RiskHeatmap backend-lehrer (5 files): - letters_api (641 → 2), certificates_api (636 → 2) - alerts_agent/db/models (636 → 3) - llm_gateway/communication_service (614 → 2) - game/database already done in prior batch klausur-service (2 files): - hybrid_vocab_extractor (664 → 2) - klausur-service/frontend: api.ts (620 → 3), EHUploadWizard (591 → 2) voice-service (3 files): - bqas/rag_judge (618 → 3), runner (529 → 2) - enhanced_task_orchestrator (519 → 2) studio-v2 (6 files): - korrektur/[klausurId] (578 → 4), fairness (569 → 2) - AlertsWizard (552 → 2), OnboardingWizard (513 → 2) - korrektur/api.ts (506 → 3), geo-lernwelt (501 → 2) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
166 lines
5.5 KiB
TypeScript
166 lines
5.5 KiB
TypeScript
import { useState } from 'react'
|
|
import type { ValidationResult, LegalReference } from './types'
|
|
|
|
interface OutputAreaProps {
|
|
generatedMessage: string
|
|
setGeneratedMessage: (msg: string) => void
|
|
subject: string
|
|
validation: ValidationResult | null
|
|
legalRefs: LegalReference[]
|
|
loading: boolean
|
|
onImprove: () => void
|
|
onCopy: () => void
|
|
}
|
|
|
|
export function OutputArea({
|
|
generatedMessage,
|
|
setGeneratedMessage,
|
|
subject,
|
|
validation,
|
|
legalRefs,
|
|
loading,
|
|
onImprove,
|
|
onCopy,
|
|
}: OutputAreaProps) {
|
|
const [showLegalInfo, setShowLegalInfo] = useState(false)
|
|
|
|
return (
|
|
<div className="lg:col-span-2 space-y-6">
|
|
{/* Generated Message */}
|
|
<div className="bg-white rounded-lg shadow p-6">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h2 className="text-lg font-semibold">Generierte Nachricht</h2>
|
|
{generatedMessage && (
|
|
<div className="flex gap-2">
|
|
<button
|
|
onClick={onImprove}
|
|
disabled={loading}
|
|
className="text-sm bg-green-100 text-green-700 px-3 py-1 rounded hover:bg-green-200"
|
|
>
|
|
Verbessern
|
|
</button>
|
|
<button
|
|
onClick={onCopy}
|
|
className="text-sm bg-gray-100 text-gray-700 px-3 py-1 rounded hover:bg-gray-200"
|
|
>
|
|
Kopieren
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{subject && (
|
|
<div className="mb-3 p-2 bg-gray-50 rounded">
|
|
<span className="text-sm font-medium text-gray-500">Betreff: </span>
|
|
<span className="text-sm">{subject}</span>
|
|
</div>
|
|
)}
|
|
|
|
{generatedMessage ? (
|
|
<textarea
|
|
value={generatedMessage}
|
|
onChange={(e) => setGeneratedMessage(e.target.value)}
|
|
className="w-full border rounded-md p-4 min-h-[400px] font-mono text-sm"
|
|
/>
|
|
) : (
|
|
<div className="text-gray-400 text-center py-20 border-2 border-dashed rounded-md">
|
|
Hier erscheint die generierte Nachricht
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Validation */}
|
|
{validation && (
|
|
<div className="bg-white rounded-lg shadow p-6">
|
|
<h3 className="text-lg font-semibold mb-4">GFK-Analyse</h3>
|
|
|
|
{/* Score */}
|
|
<div className="mb-4">
|
|
<div className="flex items-center justify-between mb-1">
|
|
<span className="text-sm font-medium">GFK-Score</span>
|
|
<span className={`text-sm font-bold ${
|
|
validation.gfk_score >= 0.8 ? 'text-green-600' :
|
|
validation.gfk_score >= 0.6 ? 'text-yellow-600' : 'text-red-600'
|
|
}`}>
|
|
{Math.round(validation.gfk_score * 100)}%
|
|
</span>
|
|
</div>
|
|
<div className="w-full bg-gray-200 rounded-full h-2">
|
|
<div
|
|
className={`h-2 rounded-full ${
|
|
validation.gfk_score >= 0.8 ? 'bg-green-500' :
|
|
validation.gfk_score >= 0.6 ? 'bg-yellow-500' : 'bg-red-500'
|
|
}`}
|
|
style={{ width: `${validation.gfk_score * 100}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Issues */}
|
|
{validation.issues.length > 0 && (
|
|
<div className="mb-4">
|
|
<h4 className="text-sm font-medium text-red-600 mb-2">
|
|
Verbesserungsvorschlaege:
|
|
</h4>
|
|
<ul className="text-sm space-y-1">
|
|
{validation.issues.map((issue, i) => (
|
|
<li key={i} className="flex items-start">
|
|
<span className="text-red-500 mr-2">!</span>
|
|
<span>{issue}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
|
|
{/* Positive Elements */}
|
|
{validation.positive_elements.length > 0 && (
|
|
<div>
|
|
<h4 className="text-sm font-medium text-green-600 mb-2">
|
|
Positive Elemente:
|
|
</h4>
|
|
<ul className="text-sm space-y-1">
|
|
{validation.positive_elements.map((elem, i) => (
|
|
<li key={i} className="flex items-start">
|
|
<span className="text-green-500 mr-2">+</span>
|
|
<span>{elem}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Legal References */}
|
|
{legalRefs.length > 0 && (
|
|
<div className="bg-white rounded-lg shadow p-6">
|
|
<button
|
|
onClick={() => setShowLegalInfo(!showLegalInfo)}
|
|
className="flex items-center justify-between w-full text-left"
|
|
>
|
|
<h3 className="text-lg font-semibold">Rechtliche Grundlagen</h3>
|
|
<span>{showLegalInfo ? '-' : '+'}</span>
|
|
</button>
|
|
{showLegalInfo && (
|
|
<div className="mt-4 space-y-4">
|
|
{legalRefs.map((ref, i) => (
|
|
<div key={i} className="border rounded-md p-3 bg-gray-50">
|
|
<div className="font-medium">
|
|
{ref.law} {ref.paragraph}
|
|
</div>
|
|
<div className="text-sm text-gray-600">{ref.title}</div>
|
|
<div className="text-sm mt-1">{ref.summary}</div>
|
|
<div className="text-xs text-blue-600 mt-1">
|
|
Relevanz: {ref.relevance}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|