// src/app/page.tsx "use client"; import { useState, useRef, useEffect } from "react"; import { useChat } from "@/hooks/useChat"; import { useWhisper } from "@/hooks/useWhisper"; import { useVoiceRecorder } from "@/hooks/useVoiceRecorder"; import { stopSpeaking } from "@/lib/tts"; export default function Home() { const [textInput, setTextInput] = useState(""); const { messages, isLoading, sendMessage } = useChat(); const { status: whisperStatus, modelMessage, transcribe } = useWhisper(); const { isRecording, startRecording, stopRecording } = useVoiceRecorder(); const bottomRef = useRef(null); useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); const handleTextSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!textInput.trim()) return; sendMessage(textInput, "text"); setTextInput(""); }; const handlePTTDown = async () => { if (whisperStatus !== "ready") return; stopSpeaking(); await startRecording(); }; const handlePTTUp = async () => { if (!isRecording) return; const audioData = await stopRecording(); const text = await transcribe(audioData); if (text) sendMessage(text, "voice"); }; const pttDisabled = whisperStatus !== "ready" || isLoading; const pttLabel = () => { if (whisperStatus === "loading") return "⏳"; if (whisperStatus === "transcribing") return "💬"; if (isRecording) return "🔴"; return "🎙"; }; const statusLine = () => { if (whisperStatus === "loading") return modelMessage; if (whisperStatus === "transcribing") return "Transcribing on-device…"; if (isRecording) return "Recording… release to send"; if (whisperStatus === "ready") return "Hold to talk — Whisper ready ✓"; return "Initialising Whisper…"; }; return (
{/* Header */}
🦞

OpenClaw Voice

On-device Whisper · No API keys

{/* Messages */}
{messages.length === 0 && (

{whisperStatus === "ready" ? "Whisper loaded. Hold the button to talk or type below." : modelMessage || "Loading Whisper model…"}

)} {messages.map((msg) => (
{msg.source === "voice" && ( {msg.role === "user" ? "🎙 transcribed" : "🔊 spoken"} )} {msg.content || }
))}
{/* Controls */}
{/* PTT Button */}

{statusLine()}

{/* Text Input */}
setTextInput(e.target.value)} placeholder="Or type a message…" disabled={isLoading || isRecording} className="flex-1 bg-gray-800 rounded-xl px-4 py-2 text-sm outline-none focus:ring-2 focus:ring-indigo-500 disabled:opacity-50" />
); }