feat: Enhance narrator description and instructions in LLMService and Audiobook components
This commit is contained in:
@@ -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 } from 'lucide-react'
|
||||
import { Book, Plus, Trash2, RefreshCw, Download, ChevronDown, ChevronUp, Play, Square, Pencil, Check, X, Loader2, Zap, Settings2, PanelLeftClose, PanelLeftOpen } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
@@ -334,6 +334,8 @@ function ProjectListSidebar({
|
||||
onNew,
|
||||
onLLM,
|
||||
loading,
|
||||
collapsed,
|
||||
onToggle,
|
||||
}: {
|
||||
projects: AudiobookProject[]
|
||||
selectedId: number | null
|
||||
@@ -341,41 +343,55 @@ function ProjectListSidebar({
|
||||
onNew: () => void
|
||||
onLLM: () => void
|
||||
loading: boolean
|
||||
collapsed: boolean
|
||||
onToggle: () => void
|
||||
}) {
|
||||
const { t } = useTranslation('audiobook')
|
||||
return (
|
||||
<div className="w-60 shrink-0 flex flex-col border-r overflow-hidden">
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b shrink-0">
|
||||
<span className="text-sm font-semibold">{t('title')}</span>
|
||||
<div className="flex items-center gap-0.5">
|
||||
<Button size="icon" variant="ghost" className="h-7 w-7" onClick={onLLM} title={t('llmConfig')}>
|
||||
<Settings2 className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button size="icon" variant="ghost" className="h-7 w-7" onClick={onNew} title={t('newProject')}>
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{loading ? (
|
||||
<div className="px-3 py-4 text-xs text-muted-foreground">{t('loading')}</div>
|
||||
) : 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 px-3 py-2 flex flex-col gap-0.5 hover:bg-muted/50 transition-colors border-b border-border/40 ${selectedId === p.id ? 'bg-muted' : ''}`}
|
||||
>
|
||||
<span className="text-sm font-medium truncate">{p.title}</span>
|
||||
<Badge variant={(STATUS_COLORS[p.status] || 'secondary') as any} className="text-[10px] h-4 px-1 self-start">
|
||||
{t(`status.${p.status}`, { defaultValue: p.status })}
|
||||
</Badge>
|
||||
</button>
|
||||
))
|
||||
<div className={`${collapsed ? 'w-10' : 'w-60'} shrink-0 flex flex-col bg-muted/30 overflow-hidden transition-all duration-200`}>
|
||||
<div className="h-16 flex items-center shrink-0 px-2 gap-1">
|
||||
{!collapsed && (
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0 ml-1">
|
||||
<img src="/qwen.svg" alt="Qwen" className="h-5 w-5 shrink-0" />
|
||||
<span className="text-sm font-semibold truncate">{t('title')}</span>
|
||||
</div>
|
||||
)}
|
||||
<Button size="icon" variant="ghost" className="h-8 w-8 shrink-0" onClick={onToggle}>
|
||||
{collapsed ? <PanelLeftOpen className="h-4 w-4" /> : <PanelLeftClose className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
{!collapsed && (
|
||||
<>
|
||||
<div className="flex items-center justify-end px-2 pb-1 gap-0.5 shrink-0">
|
||||
<Button size="icon" variant="ghost" className="h-7 w-7" onClick={onLLM} title={t('llmConfig')}>
|
||||
<Settings2 className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button size="icon" variant="ghost" className="h-7 w-7" onClick={onNew} title={t('newProject')}>
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{loading ? (
|
||||
<div className="px-3 py-4 text-xs text-muted-foreground">{t('loading')}</div>
|
||||
) : 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 px-3 py-2 flex flex-col gap-0.5 hover:bg-muted/50 transition-colors border-b border-border/40 ${selectedId === p.id ? 'bg-muted' : ''}`}
|
||||
>
|
||||
<span className="text-sm font-medium truncate">{p.title}</span>
|
||||
<Badge variant={(STATUS_COLORS[p.status] || 'secondary') as any} className="text-[10px] h-4 px-1 self-start">
|
||||
{t(`status.${p.status}`, { defaultValue: p.status })}
|
||||
</Badge>
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -465,8 +481,8 @@ function CharactersPanel({
|
||||
const charCount = detail?.characters.length ?? 0
|
||||
|
||||
return (
|
||||
<div className="w-72 shrink-0 flex flex-col border-r overflow-hidden">
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b shrink-0">
|
||||
<div className="w-72 shrink-0 flex flex-col border-r border-blue-500/20 bg-blue-500/5 overflow-hidden">
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b border-blue-500/20 shrink-0">
|
||||
<span className="text-xs font-medium text-blue-400/80">
|
||||
{t('projectCard.characters.title', { count: charCount })}
|
||||
</span>
|
||||
@@ -659,8 +675,8 @@ function ChaptersPanel({
|
||||
const hasChapters = detail && detail.chapters.length > 0 && ['ready', 'generating', 'done'].includes(status)
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
<div className="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between px-3 py-2 border-b shrink-0">
|
||||
<div className="flex-1 flex flex-col bg-emerald-500/5 overflow-hidden">
|
||||
<div className="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between px-3 py-2 border-b border-emerald-500/20 shrink-0">
|
||||
<span className="text-xs font-medium text-emerald-400/80">
|
||||
{t('projectCard.chapters.title', { count: detail?.chapters.length ?? 0 })}
|
||||
</span>
|
||||
@@ -818,6 +834,7 @@ export default function Audiobook() {
|
||||
const [sequentialPlayingId, setSequentialPlayingId] = useState<number | null>(null)
|
||||
const [showCreate, setShowCreate] = useState(false)
|
||||
const [showLLM, setShowLLM] = useState(false)
|
||||
const [sidebarOpen, setSidebarOpen] = useState(true)
|
||||
const prevStatusRef = useRef<string>('')
|
||||
const autoExpandedRef = useRef(new Set<string>())
|
||||
|
||||
@@ -1145,24 +1162,26 @@ export default function Audiobook() {
|
||||
const isTurboMode = ['analyzing', 'parsing', 'processing'].includes(displayStatus)
|
||||
|
||||
return (
|
||||
<div className="h-screen flex flex-col overflow-hidden">
|
||||
<Navbar />
|
||||
<div className="flex-1 flex overflow-hidden">
|
||||
<ProjectListSidebar
|
||||
projects={projects}
|
||||
selectedId={selectedProjectId}
|
||||
onSelect={(id) => {
|
||||
if (id !== selectedProjectId) {
|
||||
setSelectedProjectId(id)
|
||||
setIsPolling(false)
|
||||
setGeneratingChapterIndices(new Set())
|
||||
}
|
||||
}}
|
||||
onNew={() => { setShowCreate(v => !v); setShowLLM(false) }}
|
||||
onLLM={() => { setShowLLM(v => !v); setShowCreate(false) }}
|
||||
loading={loading}
|
||||
/>
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
<div className="h-screen overflow-hidden flex bg-background">
|
||||
<ProjectListSidebar
|
||||
projects={projects}
|
||||
selectedId={selectedProjectId}
|
||||
onSelect={(id) => {
|
||||
if (id !== selectedProjectId) {
|
||||
setSelectedProjectId(id)
|
||||
setIsPolling(false)
|
||||
setGeneratingChapterIndices(new Set())
|
||||
}
|
||||
}}
|
||||
onNew={() => { setShowCreate(v => !v); setShowLLM(false) }}
|
||||
onLLM={() => { setShowLLM(v => !v); setShowCreate(false) }}
|
||||
loading={loading}
|
||||
collapsed={!sidebarOpen}
|
||||
onToggle={() => setSidebarOpen(v => !v)}
|
||||
/>
|
||||
<div className="flex-1 flex flex-col overflow-hidden bg-muted/30">
|
||||
<Navbar />
|
||||
<div className="flex-1 flex flex-col overflow-hidden bg-background rounded-tl-2xl">
|
||||
{showLLM && (
|
||||
<div className="shrink-0 border-b px-4 py-3">
|
||||
<LLMConfigPanel onSaved={() => setShowLLM(false)} />
|
||||
|
||||
Reference in New Issue
Block a user