feat: add segment tracking to audiobook projects and update UI to display progress

This commit is contained in:
2026-03-13 16:00:31 +08:00
parent d1503b08cb
commit cdb9d2ebb8
4 changed files with 59 additions and 22 deletions

View File

@@ -36,6 +36,8 @@ export interface AudiobookProject {
script_config?: Record<string, unknown>
created_at: string
updated_at: string
segment_total: number
segment_done: number
}
export interface AudiobookCharacter {

View File

@@ -1,7 +1,7 @@
import { useState, useEffect, useCallback, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { Book, Plus, Trash2, RefreshCw, Download, ChevronDown, ChevronUp, Play, Square, Pencil, Check, X, Loader2, Zap, Settings2, PanelLeftClose, PanelLeftOpen, Wand2, Volume2, Bot, Flame } from 'lucide-react'
import { Book, BookOpen, Plus, Trash2, RefreshCw, Download, ChevronDown, ChevronUp, Play, Square, Pencil, Check, X, Loader2, Zap, Settings2, PanelLeftClose, PanelLeftOpen, Wand2, Volume2, Bot, Flame } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Textarea } from '@/components/ui/textarea'
@@ -1062,25 +1062,48 @@ function ProjectListSidebar({
) : projects.length === 0 ? (
<div className="px-3 py-4 text-xs text-muted-foreground">{t('noProjects')}</div>
) : (
projects.map(p => (
<button
key={p.id}
onClick={() => onSelect(p.id)}
className={`w-full text-left rounded-lg border p-3 flex flex-col gap-1.5 transition-colors hover:bg-muted/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 ${
selectedId === p.id
? 'bg-background border-border shadow-sm'
: 'bg-background/50 border-border/40'
}`}
>
<div className="flex items-start gap-2">
<Book className="h-3.5 w-3.5 shrink-0 mt-0.5 text-muted-foreground" />
<span className="text-sm font-medium leading-snug break-words min-w-0 flex-1">{p.title}</span>
</div>
<Badge variant={(STATUS_COLORS[p.status] || 'secondary') as any} className="text-[10px] h-4 px-1 self-start ml-5">
{t(`status.${p.status}`, { defaultValue: p.status })}
</Badge>
</button>
))
projects.map(p => {
const isNsfw = p.source_type === 'ai_generated' && !!p.script_config?.nsfw_mode
const ProjectIcon = p.source_type === 'epub'
? BookOpen
: isNsfw
? Flame
: p.source_type === 'ai_generated'
? Wand2
: Book
const iconClass = isNsfw
? 'h-3.5 w-3.5 shrink-0 mt-0.5 text-orange-500'
: p.source_type === 'ai_generated'
? 'h-3.5 w-3.5 shrink-0 mt-0.5 text-violet-500'
: 'h-3.5 w-3.5 shrink-0 mt-0.5 text-muted-foreground'
const showProgress = p.segment_total > 0 && ['processing', 'generating', 'done'].includes(p.status)
return (
<button
key={p.id}
onClick={() => onSelect(p.id)}
className={`w-full text-left rounded-lg border p-3 flex flex-col gap-1.5 transition-colors hover:bg-muted/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 ${
selectedId === p.id
? 'bg-background border-border shadow-sm'
: 'bg-background/50 border-border/40'
}`}
>
<div className="flex items-start gap-2">
<ProjectIcon className={iconClass} />
<span className="text-sm font-medium leading-snug break-words min-w-0 flex-1">{p.title}</span>
</div>
<div className="flex items-center gap-1.5 ml-5">
<Badge variant={(STATUS_COLORS[p.status] || 'secondary') as any} className="text-[10px] h-4 px-1">
{t(`status.${p.status}`, { defaultValue: p.status })}
</Badge>
{showProgress && (
<span className="text-[10px] text-muted-foreground tabular-nums">
{p.segment_done}/{p.segment_total}
</span>
)}
</div>
</button>
)
})
)}
</div>
</>