feat: implement TTS event handling for creation, completion, and failure in TTSPage and SocketContext
This commit is contained in:
@@ -22,6 +22,7 @@ import { Badge } from '@/components/ui/badge'
|
|||||||
import { Label } from '@/components/ui/label'
|
import { Label } from '@/components/ui/label'
|
||||||
import { Loader2, Mic } from 'lucide-react'
|
import { Loader2, Mic } from 'lucide-react'
|
||||||
import { ttsService, type TTSProvider } from '@/lib/api/services/tts'
|
import { ttsService, type TTSProvider } from '@/lib/api/services/tts'
|
||||||
|
import { TTS_EVENTS, ttsEvents } from '@/lib/events'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
interface FormData {
|
interface FormData {
|
||||||
@@ -88,8 +89,8 @@ export function CreateTTSDialog({ open, onOpenChange }: CreateTTSDialogProps) {
|
|||||||
onOpenChange(false)
|
onOpenChange(false)
|
||||||
handleReset()
|
handleReset()
|
||||||
|
|
||||||
// Trigger refresh of parent list if needed
|
// Emit event for new TTS created
|
||||||
window.dispatchEvent(new CustomEvent('tts-generated'))
|
ttsEvents.emit(TTS_EVENTS.TTS_CREATED, response.tts)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast.error(error.response?.data?.detail || 'Failed to generate TTS')
|
toast.error(error.response?.data?.detail || 'Failed to generate TTS')
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -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 { Badge } from '@/components/ui/badge'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
@@ -57,22 +57,36 @@ export function TTSTable({ ttsHistory, onTTSDeleted }: TTSTableProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getStatusBadge = (tts: TTSResponse) => {
|
const getStatusBadge = (tts: TTSResponse) => {
|
||||||
const isCompleted = tts.sound_id !== null
|
switch (tts.status) {
|
||||||
|
case 'completed':
|
||||||
if (isCompleted) {
|
return (
|
||||||
return (
|
<Badge variant="secondary" className="gap-1">
|
||||||
<Badge variant="secondary" className="gap-1">
|
<CheckCircle className="h-3 w-3" />
|
||||||
<CheckCircle className="h-3 w-3" />
|
Completed
|
||||||
Complete
|
</Badge>
|
||||||
</Badge>
|
)
|
||||||
)
|
case 'processing':
|
||||||
} else {
|
return (
|
||||||
return (
|
<Badge variant="outline" className="gap-1">
|
||||||
<Badge variant="outline" className="gap-1">
|
<Loader className="h-3 w-3 animate-spin" />
|
||||||
<Loader className="h-3 w-3 animate-spin" />
|
Processing
|
||||||
Processing
|
</Badge>
|
||||||
</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>
|
<TableRow>
|
||||||
<TableHead>Text</TableHead>
|
<TableHead>Text</TableHead>
|
||||||
<TableHead>Provider</TableHead>
|
<TableHead>Provider</TableHead>
|
||||||
<TableHead>Status</TableHead>
|
|
||||||
<TableHead>Options</TableHead>
|
<TableHead>Options</TableHead>
|
||||||
|
<TableHead>Status</TableHead>
|
||||||
<TableHead>Created</TableHead>
|
<TableHead>Created</TableHead>
|
||||||
<TableHead className="w-[120px]">Actions</TableHead>
|
<TableHead className="w-[120px]">Actions</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -103,9 +117,6 @@ export function TTSTable({ ttsHistory, onTTSDeleted }: TTSTableProps) {
|
|||||||
{tts.provider.toUpperCase()}
|
{tts.provider.toUpperCase()}
|
||||||
</Badge>
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
|
||||||
{getStatusBadge(tts)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{Object.keys(tts.options).length > 0 ? (
|
{Object.keys(tts.options).length > 0 ? (
|
||||||
<div className="flex gap-1 flex-wrap">
|
<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>
|
<span className="text-muted-foreground text-sm">None</span>
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</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>
|
<TableCell>
|
||||||
<div className="flex items-center gap-1 text-sm text-muted-foreground">
|
<div className="flex items-center gap-1 text-sm text-muted-foreground">
|
||||||
<Calendar className="h-3 w-3" />
|
<Calendar className="h-3 w-3" />
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ import {
|
|||||||
EXTRACTION_EVENTS,
|
EXTRACTION_EVENTS,
|
||||||
PLAYER_EVENTS,
|
PLAYER_EVENTS,
|
||||||
SOUND_EVENTS,
|
SOUND_EVENTS,
|
||||||
|
TTS_EVENTS,
|
||||||
USER_EVENTS,
|
USER_EVENTS,
|
||||||
authEvents,
|
authEvents,
|
||||||
extractionEvents,
|
extractionEvents,
|
||||||
playerEvents,
|
playerEvents,
|
||||||
soundEvents,
|
soundEvents,
|
||||||
|
ttsEvents,
|
||||||
userEvents,
|
userEvents,
|
||||||
} from '../lib/events'
|
} from '../lib/events'
|
||||||
import { extractionsService } from '../lib/api/services/extractions'
|
import { extractionsService } from '../lib/api/services/extractions'
|
||||||
@@ -128,10 +130,10 @@ export function SocketProvider({ children }: SocketProviderProps) {
|
|||||||
// Listen for extraction status updates
|
// Listen for extraction status updates
|
||||||
newSocket.on('extraction_status_update', data => {
|
newSocket.on('extraction_status_update', data => {
|
||||||
const { extraction_id, status, title, error } = data
|
const { extraction_id, status, title, error } = data
|
||||||
|
|
||||||
// Emit local event for other components to listen to
|
// Emit local event for other components to listen to
|
||||||
extractionEvents.emit(EXTRACTION_EVENTS.EXTRACTION_STATUS_UPDATED, data)
|
extractionEvents.emit(EXTRACTION_EVENTS.EXTRACTION_STATUS_UPDATED, data)
|
||||||
|
|
||||||
// Handle specific status events
|
// Handle specific status events
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'processing':
|
case 'processing':
|
||||||
@@ -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
|
return newSocket
|
||||||
}, [user, fetchAndShowOngoingExtractions])
|
}, [user, fetchAndShowOngoingExtractions])
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export interface TTSResponse {
|
|||||||
text: string
|
text: string
|
||||||
provider: string
|
provider: string
|
||||||
options: Record<string, any>
|
options: Record<string, any>
|
||||||
|
status: string
|
||||||
|
error: string | null
|
||||||
sound_id: number | null
|
sound_id: number | null
|
||||||
user_id: number
|
user_id: number
|
||||||
created_at: string
|
created_at: string
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export const playerEvents = new EventEmitter()
|
|||||||
export const soundEvents = new EventEmitter()
|
export const soundEvents = new EventEmitter()
|
||||||
export const userEvents = new EventEmitter()
|
export const userEvents = new EventEmitter()
|
||||||
export const extractionEvents = new EventEmitter()
|
export const extractionEvents = new EventEmitter()
|
||||||
|
export const ttsEvents = new EventEmitter()
|
||||||
|
|
||||||
// Auth event types
|
// Auth event types
|
||||||
export const AUTH_EVENTS = {
|
export const AUTH_EVENTS = {
|
||||||
@@ -69,3 +70,11 @@ export const EXTRACTION_EVENTS = {
|
|||||||
EXTRACTION_COMPLETED: 'extraction_completed',
|
EXTRACTION_COMPLETED: 'extraction_completed',
|
||||||
EXTRACTION_FAILED: 'extraction_failed',
|
EXTRACTION_FAILED: 'extraction_failed',
|
||||||
} as const
|
} 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
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
type TTSSortOrder,
|
type TTSSortOrder,
|
||||||
ttsService,
|
ttsService,
|
||||||
} from '@/lib/api/services/tts'
|
} from '@/lib/api/services/tts'
|
||||||
|
import { TTS_EVENTS, ttsEvents } from '@/lib/events'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
@@ -82,15 +83,30 @@ export function TTSPage() {
|
|||||||
}
|
}
|
||||||
}, [debouncedSearchQuery, sortBy, sortOrder, pageSize])
|
}, [debouncedSearchQuery, sortBy, sortOrder, pageSize])
|
||||||
|
|
||||||
// Listen for TTS generation events to refresh the list
|
// Listen for TTS events to refresh the list
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleTTSGenerated = () => {
|
const handleTTSCompleted = () => {
|
||||||
fetchTTSHistory()
|
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 () => {
|
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])
|
}, [fetchTTSHistory])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user