Combines homeai-character (service status, character profiles, editor) and homeai-desktop (chat with voice I/O) into homeai-dashboard on port 5173. - 4-page sidebar layout: Dashboard, Chat, Characters, Editor - Merged Vite middleware: health checks, service restart, bridge proxy - Bridge upgraded to ThreadingHTTPServer (fixes LAN request queuing) - TTS strips emojis before synthesis - Updated start.sh with new launchd service names - Added preload-models to startup sequence Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
57 lines
1.5 KiB
JavaScript
57 lines
1.5 KiB
JavaScript
import { useState, useRef, useCallback } from 'react'
|
|
import { synthesize } from '../lib/api'
|
|
|
|
export function useTtsPlayback(voice) {
|
|
const [isPlaying, setIsPlaying] = useState(false)
|
|
const audioCtxRef = useRef(null)
|
|
const sourceRef = useRef(null)
|
|
|
|
const getAudioContext = () => {
|
|
if (!audioCtxRef.current || audioCtxRef.current.state === 'closed') {
|
|
audioCtxRef.current = new AudioContext()
|
|
}
|
|
return audioCtxRef.current
|
|
}
|
|
|
|
const speak = useCallback(async (text) => {
|
|
if (!text) return
|
|
|
|
// Stop any current playback
|
|
if (sourceRef.current) {
|
|
try { sourceRef.current.stop() } catch {}
|
|
}
|
|
|
|
setIsPlaying(true)
|
|
try {
|
|
const audioData = await synthesize(text, voice)
|
|
const ctx = getAudioContext()
|
|
if (ctx.state === 'suspended') await ctx.resume()
|
|
|
|
const audioBuffer = await ctx.decodeAudioData(audioData)
|
|
const source = ctx.createBufferSource()
|
|
source.buffer = audioBuffer
|
|
source.connect(ctx.destination)
|
|
sourceRef.current = source
|
|
|
|
source.onended = () => {
|
|
setIsPlaying(false)
|
|
sourceRef.current = null
|
|
}
|
|
source.start()
|
|
} catch (err) {
|
|
console.error('TTS playback error:', err)
|
|
setIsPlaying(false)
|
|
}
|
|
}, [voice])
|
|
|
|
const stop = useCallback(() => {
|
|
if (sourceRef.current) {
|
|
try { sourceRef.current.stop() } catch {}
|
|
sourceRef.current = null
|
|
}
|
|
setIsPlaying(false)
|
|
}, [])
|
|
|
|
return { isPlaying, speak, stop }
|
|
}
|