feat: implement TTS event handling for creation, completion, and failure in TTSPage and SocketContext

This commit is contained in:
JSC
2025-09-21 14:39:15 +02:00
parent 75b52caf85
commit 92846c6d3a
6 changed files with 107 additions and 29 deletions

View File

@@ -22,6 +22,7 @@ import { Badge } from '@/components/ui/badge'
import { Label } from '@/components/ui/label'
import { Loader2, Mic } from 'lucide-react'
import { ttsService, type TTSProvider } from '@/lib/api/services/tts'
import { TTS_EVENTS, ttsEvents } from '@/lib/events'
import { toast } from 'sonner'
interface FormData {
@@ -88,8 +89,8 @@ export function CreateTTSDialog({ open, onOpenChange }: CreateTTSDialogProps) {
onOpenChange(false)
handleReset()
// Trigger refresh of parent list if needed
window.dispatchEvent(new CustomEvent('tts-generated'))
// Emit event for new TTS created
ttsEvents.emit(TTS_EVENTS.TTS_CREATED, response.tts)
} catch (error: any) {
toast.error(error.response?.data?.detail || 'Failed to generate TTS')
} finally {

View File

@@ -1,4 +1,4 @@
import { Calendar, CheckCircle, Loader, Mic, Trash2, Volume2 } from 'lucide-react'
import { AlertCircle, Calendar, CheckCircle, Clock, Loader, Mic, Trash2, Volume2, XCircle } from 'lucide-react'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
@@ -57,22 +57,36 @@ export function TTSTable({ ttsHistory, onTTSDeleted }: TTSTableProps) {
}
const getStatusBadge = (tts: TTSResponse) => {
const isCompleted = tts.sound_id !== null
if (isCompleted) {
switch (tts.status) {
case 'completed':
return (
<Badge variant="secondary" className="gap-1">
<CheckCircle className="h-3 w-3" />
Complete
Completed
</Badge>
)
} else {
case 'processing':
return (
<Badge variant="outline" className="gap-1">
<Loader className="h-3 w-3 animate-spin" />
Processing
</Badge>
)
case 'failed':
return (
<Badge variant="destructive" className="gap-1">
<XCircle className="h-3 w-3" />
Failed
</Badge>
)
case 'pending':
default:
return (
<Badge variant="outline" className="gap-1">
<Clock className="h-3 w-3" />
Pending
</Badge>
)
}
}
@@ -83,8 +97,8 @@ export function TTSTable({ ttsHistory, onTTSDeleted }: TTSTableProps) {
<TableRow>
<TableHead>Text</TableHead>
<TableHead>Provider</TableHead>
<TableHead>Status</TableHead>
<TableHead>Options</TableHead>
<TableHead>Status</TableHead>
<TableHead>Created</TableHead>
<TableHead className="w-[120px]">Actions</TableHead>
</TableRow>
@@ -103,9 +117,6 @@ export function TTSTable({ ttsHistory, onTTSDeleted }: TTSTableProps) {
{tts.provider.toUpperCase()}
</Badge>
</TableCell>
<TableCell>
{getStatusBadge(tts)}
</TableCell>
<TableCell>
{Object.keys(tts.options).length > 0 ? (
<div className="flex gap-1 flex-wrap">
@@ -119,6 +130,19 @@ export function TTSTable({ ttsHistory, onTTSDeleted }: TTSTableProps) {
<span className="text-muted-foreground text-sm">None</span>
)}
</TableCell>
<TableCell>
<div className="space-y-1">
{getStatusBadge(tts)}
{tts.error && (
<div
className="text-xs text-destructive max-w-48 truncate"
title={tts.error}
>
{tts.error}
</div>
)}
</div>
</TableCell>
<TableCell>
<div className="flex items-center gap-1 text-sm text-muted-foreground">
<Calendar className="h-3 w-3" />

View File

@@ -12,11 +12,13 @@ import {
EXTRACTION_EVENTS,
PLAYER_EVENTS,
SOUND_EVENTS,
TTS_EVENTS,
USER_EVENTS,
authEvents,
extractionEvents,
playerEvents,
soundEvents,
ttsEvents,
userEvents,
} from '../lib/events'
import { extractionsService } from '../lib/api/services/extractions'
@@ -158,6 +160,30 @@ export function SocketProvider({ children }: SocketProviderProps) {
}
})
// Listen for TTS status updates
newSocket.on('tts_completed', data => {
const { tts_id, sound_id } = data
// Emit local event for other components to listen to
ttsEvents.emit(TTS_EVENTS.TTS_COMPLETED, data)
toast.success('TTS generation completed', {
duration: 3000,
})
})
newSocket.on('tts_failed', data => {
const { tts_id, error } = data
// Emit local event for other components to listen to
ttsEvents.emit(TTS_EVENTS.TTS_FAILED, data)
toast.error('TTS generation failed', {
description: error,
duration: 5000,
})
})
return newSocket
}, [user, fetchAndShowOngoingExtractions])

View File

@@ -11,6 +11,8 @@ export interface TTSResponse {
text: string
provider: string
options: Record<string, any>
status: string
error: string | null
sound_id: number | null
user_id: number
created_at: string

View File

@@ -37,6 +37,7 @@ export const playerEvents = new EventEmitter()
export const soundEvents = new EventEmitter()
export const userEvents = new EventEmitter()
export const extractionEvents = new EventEmitter()
export const ttsEvents = new EventEmitter()
// Auth event types
export const AUTH_EVENTS = {
@@ -69,3 +70,11 @@ export const EXTRACTION_EVENTS = {
EXTRACTION_COMPLETED: 'extraction_completed',
EXTRACTION_FAILED: 'extraction_failed',
} as const
// TTS event types
export const TTS_EVENTS = {
TTS_STATUS_UPDATED: 'tts_status_updated',
TTS_CREATED: 'tts_created',
TTS_COMPLETED: 'tts_completed',
TTS_FAILED: 'tts_failed',
} as const

View File

@@ -14,6 +14,7 @@ import {
type TTSSortOrder,
ttsService,
} from '@/lib/api/services/tts'
import { TTS_EVENTS, ttsEvents } from '@/lib/events'
import { useCallback, useEffect, useState } from 'react'
import { toast } from 'sonner'
@@ -82,15 +83,30 @@ export function TTSPage() {
}
}, [debouncedSearchQuery, sortBy, sortOrder, pageSize])
// Listen for TTS generation events to refresh the list
// Listen for TTS events to refresh the list
useEffect(() => {
const handleTTSGenerated = () => {
const handleTTSCompleted = () => {
fetchTTSHistory()
}
window.addEventListener('tts-generated', handleTTSGenerated)
const handleTTSFailed = () => {
fetchTTSHistory()
}
const handleTTSCreated = () => {
fetchTTSHistory()
}
// Subscribe to TTS events
ttsEvents.on(TTS_EVENTS.TTS_COMPLETED, handleTTSCompleted)
ttsEvents.on(TTS_EVENTS.TTS_FAILED, handleTTSFailed)
ttsEvents.on(TTS_EVENTS.TTS_CREATED, handleTTSCreated)
return () => {
window.removeEventListener('tts-generated', handleTTSGenerated)
// Cleanup event listeners
ttsEvents.off(TTS_EVENTS.TTS_COMPLETED, handleTTSCompleted)
ttsEvents.off(TTS_EVENTS.TTS_FAILED, handleTTSFailed)
ttsEvents.off(TTS_EVENTS.TTS_CREATED, handleTTSCreated)
}
}, [fetchTTSHistory])