Files
breakpilot-lehrer/website/app/tools/communication/page.tsx
Benjamin Admin 451365a312 [split-required] Split remaining 500-680 LOC files (final batch)
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>
2026-04-25 08:56:45 +02:00

242 lines
7.6 KiB
TypeScript

'use client'
/**
* Communication Tool - Lehrer-Eltern-Kommunikation
*
* KI-gestuetzte Unterstuetzung fuer professionelle, rechtlich fundierte
* und empathische Elternkommunikation basierend auf den Prinzipien
* der gewaltfreien Kommunikation (GFK).
*/
import { useState, useEffect } from 'react'
import {
API_BASE,
Option,
LegalReference,
GFKPrinciple,
ValidationResult,
} from './_components/types'
import { InputForm } from './_components/InputForm'
import { OutputArea } from './_components/OutputArea'
export default function CommunicationToolPage() {
// Form state
const [communicationType, setCommunicationType] = useState('behavior')
const [tone, setTone] = useState('professional')
const [state, setState] = useState('NRW')
const [studentName, setStudentName] = useState('')
const [parentName, setParentName] = useState('')
const [situation, setSituation] = useState('')
const [additionalInfo, setAdditionalInfo] = useState('')
// Options
const [types, setTypes] = useState<Option[]>([])
const [tones, setTones] = useState<Option[]>([])
const [states, setStates] = useState<Option[]>([])
// Result state
const [generatedMessage, setGeneratedMessage] = useState('')
const [subject, setSubject] = useState('')
const [validation, setValidation] = useState<ValidationResult | null>(null)
const [legalRefs, setLegalRefs] = useState<LegalReference[]>([])
const [gfkPrinciples, setGfkPrinciples] = useState<GFKPrinciple[]>([])
// UI state
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
// Fetch options on mount
useEffect(() => {
const fetchOptions = async () => {
try {
const [typesRes, tonesRes, statesRes, gfkRes] = await Promise.all([
fetch(`${API_BASE}/v1/communication/types`),
fetch(`${API_BASE}/v1/communication/tones`),
fetch(`${API_BASE}/v1/communication/states`),
fetch(`${API_BASE}/v1/communication/gfk-principles`),
])
if (typesRes.ok) setTypes(await typesRes.json())
if (tonesRes.ok) setTones(await tonesRes.json())
if (statesRes.ok) setStates(await statesRes.json())
if (gfkRes.ok) setGfkPrinciples(await gfkRes.json())
} catch (e) {
console.error('Fehler beim Laden der Optionen:', e)
// Fallback-Werte
setTypes([
{ value: 'behavior', label: 'Verhalten/Disziplin' },
{ value: 'academic', label: 'Schulleistungen' },
{ value: 'attendance', label: 'Fehlzeiten' },
{ value: 'meeting_invite', label: 'Einladung zum Gespraech' },
{ value: 'positive_feedback', label: 'Positives Feedback' },
{ value: 'concern', label: 'Bedenken aeussern' },
{ value: 'conflict', label: 'Konfliktloesung' },
])
setTones([
{ value: 'professional', label: 'Professionell-freundlich' },
{ value: 'formal', label: 'Sehr foermlich' },
{ value: 'warm', label: 'Warmherzig' },
{ value: 'concerned', label: 'Besorgt' },
])
setStates([
{ value: 'NRW', label: 'Nordrhein-Westfalen' },
{ value: 'BY', label: 'Bayern' },
{ value: 'BW', label: 'Baden-Wuerttemberg' },
])
}
}
fetchOptions()
}, [])
// Generate message
const handleGenerate = async () => {
if (!studentName || !parentName || !situation) {
setError('Bitte fuellen Sie alle Pflichtfelder aus.')
return
}
setLoading(true)
setError(null)
try {
const res = await fetch(`${API_BASE}/v1/communication/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
communication_type: communicationType,
tone,
state,
student_name: studentName,
parent_name: parentName,
situation,
additional_info: additionalInfo || undefined,
}),
})
if (!res.ok) {
throw new Error(`Server-Fehler: ${res.status}`)
}
const data = await res.json()
setGeneratedMessage(data.message)
setSubject(data.subject)
setValidation(data.validation)
setLegalRefs(data.legal_references || [])
} catch (e) {
setError(`Fehler bei der Generierung: ${e instanceof Error ? e.message : 'Unbekannter Fehler'}`)
} finally {
setLoading(false)
}
}
// Improve existing text
const handleImprove = async () => {
if (!generatedMessage) return
setLoading(true)
setError(null)
try {
const res = await fetch(`${API_BASE}/v1/communication/improve`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: generatedMessage }),
})
if (!res.ok) {
throw new Error(`Server-Fehler: ${res.status}`)
}
const data = await res.json()
if (data.was_improved) {
setGeneratedMessage(data.improved_text)
// Re-validate
const valRes = await fetch(`${API_BASE}/v1/communication/validate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: data.improved_text }),
})
if (valRes.ok) {
setValidation(await valRes.json())
}
}
} catch (e) {
setError(`Fehler bei der Verbesserung: ${e instanceof Error ? e.message : 'Unbekannter Fehler'}`)
} finally {
setLoading(false)
}
}
// Copy to clipboard
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(generatedMessage)
alert('In die Zwischenablage kopiert!')
} catch (e) {
console.error('Kopieren fehlgeschlagen:', e)
}
}
return (
<div className="min-h-screen bg-gray-50 py-8">
<div className="max-w-6xl mx-auto px-4">
{/* Header */}
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-2">
Kommunikationsassistent
</h1>
<p className="text-gray-600">
KI-gestuetzte Unterstuetzung fuer professionelle Elternkommunikation
nach den Prinzipien der gewaltfreien Kommunikation (GFK)
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<InputForm
communicationType={communicationType}
setCommunicationType={setCommunicationType}
tone={tone}
setTone={setTone}
state={state}
setState={setState}
studentName={studentName}
setStudentName={setStudentName}
parentName={parentName}
setParentName={setParentName}
situation={situation}
setSituation={setSituation}
additionalInfo={additionalInfo}
setAdditionalInfo={setAdditionalInfo}
types={types}
tones={tones}
states={states}
gfkPrinciples={gfkPrinciples}
error={error}
loading={loading}
onGenerate={handleGenerate}
/>
<OutputArea
generatedMessage={generatedMessage}
setGeneratedMessage={setGeneratedMessage}
subject={subject}
validation={validation}
legalRefs={legalRefs}
loading={loading}
onImprove={handleImprove}
onCopy={handleCopy}
/>
</div>
{/* Footer */}
<div className="mt-8 text-center text-sm text-gray-500">
<p>
Hinweis: Die generierten Texte sind Vorschlaege und sollten vor dem
Versand ueberprueft und ggf. angepasst werden.
</p>
</div>
</div>
</div>
)
}