201 lines
5.6 KiB
TypeScript
201 lines
5.6 KiB
TypeScript
import { AppLayout } from '@/components/AppLayout'
|
|
import { AppPagination } from '@/components/AppPagination'
|
|
import { CreateTTSDialog } from '@/components/tts/CreateTTSDialog'
|
|
import { TTSHeader } from '@/components/tts/TTSHeader'
|
|
import {
|
|
TTSEmpty,
|
|
TTSError,
|
|
TTSLoading,
|
|
} from '@/components/tts/TTSLoadingStates'
|
|
import { TTSTable } from '@/components/tts/TTSTable'
|
|
import {
|
|
type TTSResponse,
|
|
type TTSSortField,
|
|
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'
|
|
|
|
export function TTSPage() {
|
|
const [ttsHistory, setTTSHistory] = useState<TTSResponse[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
// Search and sorting state
|
|
const [searchQuery, setSearchQuery] = useState('')
|
|
const [sortBy, setSortBy] = useState<TTSSortField>('created_at')
|
|
const [sortOrder, setSortOrder] = useState<TTSSortOrder>('desc')
|
|
|
|
// Pagination state
|
|
const [currentPage, setCurrentPage] = useState(1)
|
|
const [totalPages, setTotalPages] = useState(1)
|
|
const [totalCount, setTotalCount] = useState(0)
|
|
const [pageSize, setPageSize] = useState(10)
|
|
|
|
// Create TTS dialog state
|
|
const [showCreateDialog, setShowCreateDialog] = useState(false)
|
|
|
|
// Debounce search query
|
|
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(searchQuery)
|
|
|
|
useEffect(() => {
|
|
const handler = setTimeout(() => {
|
|
setDebouncedSearchQuery(searchQuery)
|
|
}, 300)
|
|
|
|
return () => clearTimeout(handler)
|
|
}, [searchQuery])
|
|
|
|
const fetchTTSHistory = useCallback(async () => {
|
|
try {
|
|
setLoading(true)
|
|
setError(null)
|
|
const response = await ttsService.getTTSHistory({
|
|
search: debouncedSearchQuery.trim() || undefined,
|
|
sort_by: sortBy,
|
|
sort_order: sortOrder,
|
|
page: currentPage,
|
|
limit: pageSize,
|
|
})
|
|
setTTSHistory(response.tts)
|
|
setTotalPages(response.total_pages)
|
|
setTotalCount(response.total)
|
|
} catch (err) {
|
|
const errorMessage =
|
|
err instanceof Error ? err.message : 'Failed to fetch TTS history'
|
|
setError(errorMessage)
|
|
toast.error(errorMessage)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}, [debouncedSearchQuery, sortBy, sortOrder, currentPage, pageSize])
|
|
|
|
useEffect(() => {
|
|
fetchTTSHistory()
|
|
}, [fetchTTSHistory])
|
|
|
|
// Reset to page 1 when filters change
|
|
useEffect(() => {
|
|
if (currentPage !== 1) {
|
|
setCurrentPage(1)
|
|
}
|
|
}, [debouncedSearchQuery, sortBy, sortOrder, pageSize])
|
|
|
|
// Listen for TTS events to refresh the list
|
|
useEffect(() => {
|
|
const handleTTSCompleted = () => {
|
|
fetchTTSHistory()
|
|
}
|
|
|
|
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 () => {
|
|
// 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])
|
|
|
|
const handlePageChange = (page: number) => {
|
|
setCurrentPage(page)
|
|
}
|
|
|
|
const handlePageSizeChange = (size: number) => {
|
|
setPageSize(size)
|
|
setCurrentPage(1) // Reset to first page when changing page size
|
|
}
|
|
|
|
const handleTTSDeleted = (ttsId: number) => {
|
|
// Remove the deleted TTS from the current list
|
|
setTTSHistory(prev => prev.filter(tts => tts.id !== ttsId))
|
|
|
|
// Update total count
|
|
setTotalCount(prev => prev - 1)
|
|
|
|
// If current page is now empty and not the first page, go to previous page
|
|
const remainingOnCurrentPage = ttsHistory.length - 1
|
|
if (remainingOnCurrentPage === 0 && currentPage > 1) {
|
|
setCurrentPage(currentPage - 1)
|
|
}
|
|
|
|
// Refresh the full list to ensure accuracy
|
|
fetchTTSHistory()
|
|
}
|
|
|
|
const renderContent = () => {
|
|
if (loading) {
|
|
return <TTSLoading />
|
|
}
|
|
|
|
if (error) {
|
|
return <TTSError error={error} onRetry={fetchTTSHistory} />
|
|
}
|
|
|
|
if (!ttsHistory || ttsHistory.length === 0) {
|
|
return <TTSEmpty searchQuery={searchQuery} />
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<TTSTable
|
|
ttsHistory={ttsHistory}
|
|
onTTSDeleted={handleTTSDeleted}
|
|
/>
|
|
<AppPagination
|
|
currentPage={currentPage}
|
|
totalPages={totalPages}
|
|
totalCount={totalCount}
|
|
pageSize={pageSize}
|
|
onPageChange={handlePageChange}
|
|
onPageSizeChange={handlePageSizeChange}
|
|
itemName="TTS generations"
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<AppLayout
|
|
breadcrumb={{
|
|
items: [{ label: 'Dashboard', href: '/' }, { label: 'Text to Speech' }],
|
|
}}
|
|
>
|
|
<div className="flex-1 rounded-xl bg-muted/50 p-4">
|
|
<TTSHeader
|
|
searchQuery={searchQuery}
|
|
onSearchChange={setSearchQuery}
|
|
sortBy={sortBy}
|
|
onSortByChange={setSortBy}
|
|
sortOrder={sortOrder}
|
|
onSortOrderChange={setSortOrder}
|
|
onRefresh={fetchTTSHistory}
|
|
onCreateClick={() => setShowCreateDialog(true)}
|
|
loading={loading}
|
|
error={error}
|
|
ttsCount={totalCount}
|
|
/>
|
|
|
|
<CreateTTSDialog
|
|
open={showCreateDialog}
|
|
onOpenChange={setShowCreateDialog}
|
|
/>
|
|
|
|
{renderContent()}
|
|
</div>
|
|
</AppLayout>
|
|
)
|
|
} |