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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user