'use client' import { useState, useEffect, useRef, useCallback } from 'react' import { VoiceAPI, VoiceMessage, VoiceTask } from '@/lib/voice/voice-api' import { VoiceIndicator } from './VoiceIndicator' interface Message { id: string role: 'user' | 'assistant' content: string timestamp: Date intent?: string task?: VoiceTask } interface VoiceCommandBarProps { onTaskCreated?: (task: VoiceTask) => void onTaskApproved?: (taskId: string) => void className?: string } /** * Full voice command bar with conversation history * Shows transcript, responses, and pending tasks */ export function VoiceCommandBar({ onTaskCreated, onTaskApproved, className = '', }: VoiceCommandBarProps) { const voiceApiRef = useRef(null) const messagesEndRef = useRef(null) const [isInitialized, setIsInitialized] = useState(false) const [isConnected, setIsConnected] = useState(false) const [isListening, setIsListening] = useState(false) const [status, setStatus] = useState('idle') const [messages, setMessages] = useState([]) const [pendingTasks, setPendingTasks] = useState([]) const [error, setError] = useState(null) // Auto-scroll to bottom useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) }, [messages]) // Initialize voice API useEffect(() => { const init = async () => { try { const api = new VoiceAPI() await api.initialize() voiceApiRef.current = api api.setOnMessage(handleMessage) api.setOnError(handleError) api.setOnStatusChange(handleStatusChange) setIsInitialized(true) } catch (e) { console.error('Failed to initialize:', e) setError('Sprachdienst konnte nicht initialisiert werden') } } init() return () => { voiceApiRef.current?.disconnect() } }, []) const handleMessage = useCallback( (message: VoiceMessage) => { switch (message.type) { case 'transcript': if (message.final) { setMessages((prev) => [ ...prev, { id: `msg-${Date.now()}`, role: 'user', content: message.text, timestamp: new Date(), }, ]) } break case 'intent': // Update last user message with intent setMessages((prev) => { const updated = [...prev] const lastUserMsg = [...updated].reverse().find((m) => m.role === 'user') if (lastUserMsg) { lastUserMsg.intent = message.intent } return updated }) break case 'response': setMessages((prev) => [ ...prev, { id: `msg-${Date.now()}`, role: 'assistant', content: message.text, timestamp: new Date(), }, ]) break case 'task_created': const task: VoiceTask = { id: message.task_id, session_id: '', type: message.task_type, state: message.state, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), result_available: false, } setPendingTasks((prev) => [...prev, task]) onTaskCreated?.(task) // Update last assistant message with task setMessages((prev) => { const updated = [...prev] const lastAssistantMsg = [...updated].reverse().find((m) => m.role === 'assistant') if (lastAssistantMsg) { lastAssistantMsg.task = task } return updated }) break case 'error': setError(message.message) break } }, [onTaskCreated] ) const handleError = useCallback((error: Error) => { setError(error.message) setIsListening(false) }, []) const handleStatusChange = useCallback((newStatus: string) => { setStatus(newStatus) setIsConnected(newStatus !== 'idle' && newStatus !== 'disconnected') setIsListening(newStatus === 'listening') }, []) const toggleListening = async () => { if (!voiceApiRef.current) return try { setError(null) if (isListening) { voiceApiRef.current.stopCapture() } else { if (!isConnected) { await voiceApiRef.current.connect() } await voiceApiRef.current.startCapture() } } catch (e) { console.error('Failed to toggle listening:', e) setError('Mikrofon konnte nicht aktiviert werden') } } const approveTask = async (taskId: string) => { try { await voiceApiRef.current?.approveTask(taskId) setPendingTasks((prev) => prev.filter((t) => t.id !== taskId)) onTaskApproved?.(taskId) } catch (e) { console.error('Failed to approve task:', e) } } const rejectTask = async (taskId: string) => { try { await voiceApiRef.current?.rejectTask(taskId) setPendingTasks((prev) => prev.filter((t) => t.id !== taskId)) } catch (e) { console.error('Failed to reject task:', e) } } if (!isInitialized) { return (
) } return (
{/* Header */}
Breakpilot Voice
{/* Messages */}
{messages.length === 0 ? (

Willkommen bei Breakpilot Voice!

Klicken Sie auf das Mikrofon und sprechen Sie Ihren Befehl.

) : ( messages.map((msg) => (

{msg.content}

{msg.intent && (

Intent: {msg.intent}

)} {msg.task && msg.task.state === 'ready' && (
)}
)) )}
{/* Error */} {error && (
{error}
)} {/* Input area */}
{/* Microphone button */} {/* Text hint */}
{isListening ? 'Ich hoere zu... Sprechen Sie jetzt.' : status === 'processing' ? 'Verarbeite...' : 'Tippen Sie auf das Mikrofon um zu sprechen'}
{/* Pending tasks indicator */} {pendingTasks.length > 0 && (
{pendingTasks.length} Aufgabe(n)
)}
) }