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>
236 lines
8.7 KiB
TypeScript
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>"Notiz zu Max: heute wiederholt gestoert"</li>
|
|
<li>"Erinner mich morgen an Hausaufgabenkontrolle"</li>
|
|
<li>"Erstelle Arbeitsblatt mit 3 Lueckentexten"</li>
|
|
<li>"Elternbrief wegen wiederholter Stoerungen"</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>• "Notiz zu [Name]: [Beobachtung]"</li>
|
|
<li>• "[Name] braucht extra Uebung"</li>
|
|
<li>• "Hausaufgabe kontrollieren"</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>• "Arbeitsblatt erstellen"</li>
|
|
<li>• "Quiz mit 10 Fragen"</li>
|
|
<li>• "Elternbrief wegen..."</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>• "Erinner mich morgen..."</li>
|
|
<li>• "Nachricht an Klasse 8a"</li>
|
|
<li>• "Offene Aufgaben zeigen"</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>
|
|
)
|
|
}
|