@@ -7,7 +7,7 @@ import { Input } from '@/components/ui/input'
import { Textarea } from '@/components/ui/textarea'
import { Badge } from '@/components/ui/badge'
import { Progress } from '@/components/ui/progress'
import { Dialog , DialogContent , DialogHeader , DialogTitle } from '@/components/ui/dialog'
import { Dialog , DialogContent , DialogHeader , DialogTitle , DialogFooter } from '@/components/ui/dialog'
import { Navbar } from '@/components/Navbar'
import { AudioPlayer } from '@/components/AudioPlayer'
import { ChapterPlayer } from '@/components/ChapterPlayer'
@@ -1245,6 +1245,65 @@ function CharactersPanel({
< / div >
< / div >
< Dialog open = { editingCharId !== null } onOpenChange = { open = > { if ( ! open ) setEditingCharId ( null ) } } >
< DialogContent className = "sm:max-w-md" >
< DialogHeader >
< DialogTitle > { t ( 'projectCard.characters.editTitle' , { name : detail?.characters.find ( c = > c . id === editingCharId ) ? . name ? ? '' } ) } < / DialogTitle >
< / DialogHeader >
< div className = "space-y-3 py-2" >
< div className = "space-y-1" >
< label className = "text-sm font-medium" > { t ( 'projectCard.characters.namePlaceholder' ) } < / label >
< Input
value = { editFields . name }
onChange = { e = > setEditFields ( f = > ( { . . . f , name : e.target.value } ) ) }
placeholder = { t ( 'projectCard.characters.namePlaceholder' ) }
/ >
< / div >
< div className = "space-y-1" >
< label className = "text-sm font-medium" > { t ( 'projectCard.characters.genderPlaceholder' ) } < / label >
< select
className = "w-full h-9 rounded-md border border-input bg-background px-3 py-1 text-sm"
value = { editFields . gender }
onChange = { e = > setEditFields ( f = > ( { . . . f , gender : e.target.value } ) ) }
>
< option value = "" > { t ( 'projectCard.characters.genderPlaceholder' ) } < / option >
< option value = "男" > { t ( 'projectCard.characters.genderMale' ) } < / option >
< option value = "女" > { t ( 'projectCard.characters.genderFemale' ) } < / option >
< option value = "未知" > { t ( 'projectCard.characters.genderUnknown' ) } < / option >
< / select >
< / div >
< div className = "space-y-1" >
< label className = "text-sm font-medium" > { t ( 'projectCard.characters.instructPlaceholder' ) } < / label >
< Textarea
value = { editFields . instruct }
onChange = { e = > setEditFields ( f = > ( { . . . f , instruct : e.target.value } ) ) }
placeholder = { t ( 'projectCard.characters.instructPlaceholder' ) }
rows = { 4 }
className = "resize-none"
/ >
< / div >
< div className = "space-y-1" >
< label className = "text-sm font-medium" > { t ( 'projectCard.characters.descPlaceholder' ) } < / label >
< Input
value = { editFields . description }
onChange = { e = > setEditFields ( f = > ( { . . . f , description : e.target.value } ) ) }
placeholder = { t ( 'projectCard.characters.descPlaceholder' ) }
/ >
< / div >
< / div >
< DialogFooter >
< Button variant = "ghost" onClick = { ( ) = > setEditingCharId ( null ) } > { t ( 'common:cancel' ) } < / Button >
{ editingCharId !== null && detail && ( ( ) = > {
const editingChar = detail . characters . find ( c = > c . id === editingCharId )
return editingChar ? (
< Button onClick = { ( ) = > saveEditChar ( editingChar ) } >
< Check className = "h-3 w-3 mr-1" / > { t ( 'common:save' ) }
< / Button >
) : null
} ) ( ) }
< / DialogFooter >
< / DialogContent >
< / Dialog >
{ ( ! detail || charCount === 0 ) ? (
< div className = "flex-1 flex items-center justify-center text-xs text-muted-foreground px-3 text-center" >
{ status === 'pending' ? t ( 'stepHints.pending' ) : t ( 'projectCard.characters.title' , { count : 0 } ) }
@@ -1253,65 +1312,13 @@ function CharactersPanel({
< div className = "flex-1 overflow-y-auto px-2 py-2 space-y-1.5" >
{ detail . characters . map ( char = > (
< div key = { char . id } className = "border rounded-lg px-3 py-2 bg-muted/30 shadow-sm" >
{ editingCharId === char . id ? (
< div className = "space-y-2" >
< Input
value = { editFields . name }
onChange = { e = > setEditFields ( f = > ( { . . . f , name : e.target.value } ) ) }
placeholder = { t ( 'projectCard.characters.namePlaceholder' ) }
/ >
< select
className = "w-full h-9 rounded-md border border-input bg-background px-3 py-1 text-sm"
value = { editFields . gender }
onChange = { e = > setEditFields ( f = > ( { . . . f , gender : e.target.value } ) ) }
>
< option value = "" > { t ( 'projectCard.characters.genderPlaceholder' ) } < / option >
< option value = "男" > { t ( 'projectCard.characters.genderMale' ) } < / option >
< option value = "女" > { t ( 'projectCard.characters.genderFemale' ) } < / option >
< option value = "未知" > { t ( 'projectCard.characters.genderUnknown' ) } < / option >
< / select >
< Input
value = { editFields . instruct }
onChange = { e = > setEditFields ( f = > ( { . . . f , instruct : e.target.value } ) ) }
placeholder = { t ( 'projectCard.characters.instructPlaceholder' ) }
/ >
< Input
value = { editFields . description }
onChange = { e = > setEditFields ( f = > ( { . . . f , description : e.target.value } ) ) }
placeholder = { t ( 'projectCard.characters.descPlaceholder' ) }
/ >
< label className = "flex items-center gap-2 text-sm cursor-pointer select-none" >
< input
type = "checkbox"
checked = { editFields . use_indextts2 }
onChange = { e = > setEditFields ( f = > ( { . . . f , use_indextts2 : e.target.checked } ) ) }
className = "w-4 h-4"
/ >
< Zap className = "h-3 w-3 text-amber-400" / >
< span > 使 用 IndexTTS2 ( 需 要 音 色 克 隆 参 考 音 频 ) < / span >
< / label >
< div className = "flex gap-2" >
< Button size = "sm" onClick = { ( ) = > saveEditChar ( char ) } >
< Check className = "h-3 w-3 mr-1" / > { t ( 'common:save' ) }
< / Button >
< Button size = "sm" variant = "ghost" onClick = { ( ) = > setEditingCharId ( null ) } >
< X className = "h-3 w-3 mr-1" / > { t ( 'common:cancel' ) }
< / Button >
< / div >
< / div >
) : (
< div className = "flex flex-col gap-1 text-sm" >
< div className = "flex flex-col gap-1 text-sm" >
< div className = "flex items-center justify-between gap-1" >
< div className = "flex items-center gap-1.5 min-w-0" >
< span className = { ` font-medium truncate ${ char . gender === '男' ? 'text-blue-400' : char . gender === '女' ? 'text-pink-400' : '' } ` } > { char . name } < / span >
< / div >
< div className = "flex items-center gap-1 shrink-0" >
{ char . use_indextts2 && (
< Badge variant = "outline" className = "text-xs border-amber-400/50 text-amber-400" >
< Zap className = "h-2.5 w-2.5 mr-0.5" / > IndexTTS2
< / Badge >
) }
{ status === 'characters_ready' && (
{ status === 'characters_ready' && (
< Button size = "icon" variant = "ghost" className = "h-6 w-6" onClick = { ( ) = > startEditChar ( char ) } >
< Pencil className = "h-3 w-3" / >
< / Button >
@@ -1320,13 +1327,10 @@ function CharactersPanel({
< / div >
{ char . description && < span className = "text-xs text-muted-foreground" > { char . description } < / span > }
{ char . instruct && < span className = "text-xs text-muted-foreground/70" > { char . instruct } < / span > }
< div className = "text-xs text-muted-foreground/60" >
{ char . voice_design_id
? ( char . voice_design_name || ` # ${ char . voice_design_id } ` )
: t ( 'projectCard.characters.noVoice' ) }
< / div >
{ ! char . voice_design_id && (
< div className = "text-xs text-muted-foreground/60" > { t ( 'projectCard.characters.noVoice' ) } < / div >
)}
< / div >
) }
{ ! editingCharId && char . voice_design_id && (
< div className = "mt-2 flex items-center gap-2" >
< div className = "flex-1 min-w-0" >
@@ -1363,7 +1367,7 @@ function CharactersPanel({
< Button
className = "w-full"
onClick = { onConfirm }
disabled = { loadingAction || editingCharId !== null }
disabled = { loadingAction }
>
{ loadingAction
? t ( 'projectCard.confirm.loading' )