Files
breakpilot-lehrer/studio-v2/app/voice-test/page.tsx
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

236 lines
8.7 KiB
TypeScript

'use client'
import { useState } from 'react'
import { VoiceCapture, VoiceCommandBar } from '@/components/voice'
import { VoiceTask } from '@/lib/voice/voice-api'
/**
* Voice Test Page
* For testing and demonstrating voice interface
*/
export default function VoiceTestPage() {
const [activeTab, setActiveTab] = useState<'simple' | 'full'>('full')
const [transcripts, setTranscripts] = useState<string[]>([])
const [intents, setIntents] = useState<{ intent: string; params: Record<string, unknown> }[]>([])
const [tasks, setTasks] = useState<VoiceTask[]>([])
const handleTranscript = (text: string, isFinal: boolean) => {
if (isFinal) {
setTranscripts((prev) => [...prev.slice(-9), text])
}
}
const handleIntent = (intent: string, params: Record<string, unknown>) => {
setIntents((prev) => [...prev.slice(-9), { intent, params }])
}
const handleTaskCreated = (task: VoiceTask) => {
setTasks((prev) => [...prev.slice(-9), task])
}
return (
<div className="min-h-screen bg-gradient-to-br from-gray-100 to-gray-200 p-8">
<div className="max-w-6xl mx-auto">
{/* Header */}
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-800 mb-2">
Breakpilot Voice Test
</h1>
<p className="text-gray-600">
Testen Sie die Sprachsteuerung fuer Breakpilot. Sprechen Sie Befehle wie:
</p>
<ul className="mt-2 text-sm text-gray-500 list-disc list-inside">
<li>&quot;Notiz zu Max: heute wiederholt gestoert&quot;</li>
<li>&quot;Erinner mich morgen an Hausaufgabenkontrolle&quot;</li>
<li>&quot;Erstelle Arbeitsblatt mit 3 Lueckentexten&quot;</li>
<li>&quot;Elternbrief wegen wiederholter Stoerungen&quot;</li>
</ul>
</div>
{/* Tab switcher */}
<div className="flex gap-2 mb-6">
<button
onClick={() => setActiveTab('full')}
className={`px-4 py-2 rounded-lg transition-colors ${
activeTab === 'full'
? 'bg-blue-500 text-white'
: 'bg-white text-gray-700 hover:bg-gray-50'
}`}
>
Volle Ansicht
</button>
<button
onClick={() => setActiveTab('simple')}
className={`px-4 py-2 rounded-lg transition-colors ${
activeTab === 'simple'
? 'bg-blue-500 text-white'
: 'bg-white text-gray-700 hover:bg-gray-50'
}`}
>
Einfacher Modus
</button>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Voice Component */}
<div>
{activeTab === 'full' ? (
<VoiceCommandBar
onTaskCreated={handleTaskCreated}
className="h-[500px]"
/>
) : (
<div className="bg-white rounded-xl shadow-lg p-6">
<h2 className="text-lg font-semibold mb-4">Sprachaufnahme</h2>
<VoiceCapture
onTranscript={handleTranscript}
onIntent={handleIntent}
onTaskCreated={handleTaskCreated}
/>
</div>
)}
</div>
{/* Debug panel */}
<div className="space-y-6">
{/* Transcripts */}
<div className="bg-white rounded-xl shadow-lg p-6">
<h2 className="text-lg font-semibold mb-4">Erkannte Texte</h2>
<div className="space-y-2 max-h-[150px] overflow-y-auto">
{transcripts.length === 0 ? (
<p className="text-gray-400 text-sm">
Noch keine Transkripte...
</p>
) : (
transcripts.map((t, i) => (
<div
key={i}
className="bg-gray-50 rounded px-3 py-2 text-sm"
>
{t}
</div>
))
)}
</div>
</div>
{/* Intents */}
<div className="bg-white rounded-xl shadow-lg p-6">
<h2 className="text-lg font-semibold mb-4">Erkannte Absichten</h2>
<div className="space-y-2 max-h-[150px] overflow-y-auto">
{intents.length === 0 ? (
<p className="text-gray-400 text-sm">Noch keine Intents...</p>
) : (
intents.map((intent, i) => (
<div
key={i}
className="bg-blue-50 rounded px-3 py-2 text-sm"
>
<span className="font-medium text-blue-700">
{intent.intent}
</span>
{Object.keys(intent.params).length > 0 && (
<pre className="mt-1 text-xs text-gray-500">
{JSON.stringify(intent.params, null, 2)}
</pre>
)}
</div>
))
)}
</div>
</div>
{/* Tasks */}
<div className="bg-white rounded-xl shadow-lg p-6">
<h2 className="text-lg font-semibold mb-4">Erstellte Aufgaben</h2>
<div className="space-y-2 max-h-[150px] overflow-y-auto">
{tasks.length === 0 ? (
<p className="text-gray-400 text-sm">
Noch keine Aufgaben...
</p>
) : (
tasks.map((task, i) => (
<div
key={i}
className="bg-green-50 rounded px-3 py-2 text-sm"
>
<div className="flex justify-between items-center">
<span className="font-medium text-green-700">
{task.type}
</span>
<span
className={`px-2 py-0.5 rounded text-xs ${
task.state === 'completed'
? 'bg-green-200 text-green-800'
: task.state === 'ready'
? 'bg-yellow-200 text-yellow-800'
: 'bg-gray-200 text-gray-800'
}`}
>
{task.state}
</span>
</div>
<p className="text-xs text-gray-500 mt-1">
ID: {task.id.slice(0, 8)}...
</p>
</div>
))
)}
</div>
</div>
</div>
</div>
{/* Instructions */}
<div className="mt-8 bg-white rounded-xl shadow-lg p-6">
<h2 className="text-lg font-semibold mb-4">Anleitung</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 text-sm">
<div>
<h3 className="font-medium text-gray-700 mb-2">
1. Notizen & Beobachtungen
</h3>
<ul className="text-gray-500 space-y-1">
<li> &quot;Notiz zu [Name]: [Beobachtung]&quot;</li>
<li> &quot;[Name] braucht extra Uebung&quot;</li>
<li> &quot;Hausaufgabe kontrollieren&quot;</li>
</ul>
</div>
<div>
<h3 className="font-medium text-gray-700 mb-2">
2. Materialerstellung
</h3>
<ul className="text-gray-500 space-y-1">
<li> &quot;Arbeitsblatt erstellen&quot;</li>
<li> &quot;Quiz mit 10 Fragen&quot;</li>
<li> &quot;Elternbrief wegen...&quot;</li>
</ul>
</div>
<div>
<h3 className="font-medium text-gray-700 mb-2">
3. Organisation
</h3>
<ul className="text-gray-500 space-y-1">
<li> &quot;Erinner mich morgen...&quot;</li>
<li> &quot;Nachricht an Klasse 8a&quot;</li>
<li> &quot;Offene Aufgaben zeigen&quot;</li>
</ul>
</div>
</div>
</div>
{/* Privacy note */}
<div className="mt-6 text-center text-sm text-gray-400">
<p>
DSGVO-konform: Audio wird nur im Arbeitsspeicher verarbeitet und
nie gespeichert.
</p>
<p>
Alle personenbezogenen Daten werden verschluesselt gespeichert -
der Schluessel bleibt auf Ihrem Geraet.
</p>
</div>
</div>
</div>
)
}