fix: Improve Whisper server startup with async polling and reduce logs

- Make server startup async to avoid Bun's 10s timeout
- Add frontend polling to detect when server is ready
- Use PowerShell Get-NetTCPConnection for reliable port detection
- Add starting state to prevent multiple simultaneous starts
- Reduce verbose logging, keep only essential info
- Add dev-dist and nul to gitignore
This commit is contained in:
2026-02-14 01:02:54 -06:00
parent 9f1e10b8d5
commit 5be0fb91ab
5 changed files with 180 additions and 73 deletions

View File

@@ -135,35 +135,59 @@ function initRecognition() {
// ============ WHISPER FUNCTIONS ============
async function checkWhisperStatus() {
async function checkWhisperStatus(updateLoading = true) {
try {
const res = await fetch(`http://${window.location.hostname}:4100/api/whisper/status`)
const data = await res.json()
useWhisper.value = data.enabled
whisperReady.value = data.running
if (updateLoading) {
whisperLoading.value = data.starting || false
}
return data
} catch {
useWhisper.value = false
whisperReady.value = false
if (updateLoading) {
whisperLoading.value = false
}
return null
}
}
async function toggleWhisperMode() {
// Prevent multiple clicks
if (whisperLoading.value) {
console.log('[Voice] Toggle already in progress, ignoring')
return
}
whisperLoading.value = true
error.value = ''
// Show immediate feedback
if (!useWhisper.value) {
canvasStore.showNotification('Starting Whisper GPU server...', 'info', 10000)
}
try {
const res = await fetch(`http://${window.location.hostname}:4100/api/whisper/toggle`, {
method: 'POST'
})
const data = await res.json()
// Server is starting - poll until ready
if (data.starting) {
console.log('[Voice] Server starting, polling for status...')
await pollWhisperStatus()
return
}
useWhisper.value = data.enabled
whisperReady.value = data.running
if (data.enabled) {
canvasStore.showNotification('Whisper GPU enabled', 'success')
canvasStore.showNotification('Whisper GPU ready!', 'success')
connectWhisperSocket()
} else {
canvasStore.showNotification('Using Web Speech API', 'info')
@@ -171,12 +195,61 @@ async function toggleWhisperMode() {
}
} catch (e: any) {
error.value = 'Failed to toggle Whisper'
canvasStore.showNotification('Error starting Whisper server', 'error')
console.error('[Voice] Whisper toggle error:', e)
} finally {
whisperLoading.value = false
}
}
// Poll server status until ready or failed
async function pollWhisperStatus() {
const maxAttempts = 60 // 2 minutes max
let attempts = 0
while (attempts < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, 2000))
attempts++
try {
const status = await checkWhisperStatus(false) // Don't update loading state
if (!status) {
console.log('[Voice] Failed to get status')
continue
}
// Still starting
if (status.starting) {
console.log(`[Voice] Still starting... (${attempts * 2}s)`)
continue
}
// Started successfully
if (status.running && status.enabled) {
console.log('[Voice] Server ready!')
canvasStore.showNotification('Whisper GPU ready!', 'success')
connectWhisperSocket()
whisperLoading.value = false
return
}
// Failed to start
console.log('[Voice] Server failed to start')
canvasStore.showNotification('Whisper server failed to start', 'error')
whisperLoading.value = false
return
} catch (e) {
console.error('[Voice] Polling error:', e)
}
}
// Timeout
canvasStore.showNotification('Whisper server timeout', 'error')
whisperLoading.value = false
}
function connectWhisperSocket() {
if (whisperSocket?.readyState === WebSocket.OPEN) return
@@ -671,8 +744,13 @@ onMounted(async () => {
document.addEventListener('keyup', handleKeyUp, { capture: true })
// Check Whisper status on mount
await checkWhisperStatus()
if (useWhisper.value) {
const status = await checkWhisperStatus()
// If server is starting (page was reloaded during startup), continue polling
if (status?.starting) {
console.log('[Voice] Server is starting, resuming polling...')
pollWhisperStatus()
} else if (useWhisper.value) {
connectWhisperSocket()
}
})
@@ -743,8 +821,9 @@ defineExpose({
<button
class="whisper-toggle"
:class="{ active: useWhisper, loading: whisperLoading }"
:disabled="whisperLoading"
@click.stop="toggleWhisperMode"
:title="useWhisper ? 'Using Whisper GPU - Click to use Web Speech' : 'Using Web Speech - Click to use Whisper GPU'"
:title="whisperLoading ? 'Starting Whisper server...' : (useWhisper ? 'Using Whisper GPU - Click to use Web Speech' : 'Using Web Speech - Click to use Whisper GPU')"
>
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="4" y="4" width="16" height="16" rx="2"/>
@@ -917,10 +996,15 @@ defineExpose({
transition: all 0.15s;
}
.whisper-toggle:hover {
.whisper-toggle:hover:not(:disabled) {
background: rgba(255, 255, 255, 0.5);
}
.whisper-toggle:disabled {
cursor: not-allowed;
opacity: 0.6;
}
.whisper-toggle.active {
background: linear-gradient(180deg, #10b981 0%, #059669 100%);
border-color: #047857;