feat: update button sizes and styles for improved UI consistency in Audiobook and button components
This commit is contained in:
@@ -457,7 +457,7 @@ class IndexTTS2Backend:
|
|||||||
vec = [0.0] * 8
|
vec = [0.0] * 8
|
||||||
score = 0.8 if len(matched) == 1 else 0.5
|
score = 0.8 if len(matched) == 1 else 0.5
|
||||||
for idx in matched:
|
for idx in matched:
|
||||||
vec[idx] = score
|
vec[idx] = 0.2 if idx == 1 else score
|
||||||
return vec
|
return vec
|
||||||
|
|
||||||
async def generate(
|
async def generate(
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const buttonVariants = cva(
|
|||||||
size: {
|
size: {
|
||||||
default: "h-10 px-4 py-2",
|
default: "h-10 px-4 py-2",
|
||||||
sm: "h-9 rounded-md px-3",
|
sm: "h-9 rounded-md px-3",
|
||||||
|
xs: "h-6 rounded-md px-2 text-xs",
|
||||||
lg: "h-11 rounded-md px-8",
|
lg: "h-11 rounded-md px-8",
|
||||||
icon: "h-10 w-10",
|
icon: "h-10 w-10",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -409,7 +409,7 @@ function ProjectListSidebar({
|
|||||||
<button
|
<button
|
||||||
key={p.id}
|
key={p.id}
|
||||||
onClick={() => onSelect(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 ${
|
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
|
selectedId === p.id
|
||||||
? 'bg-background border-border shadow-sm'
|
? 'bg-background border-border shadow-sm'
|
||||||
: 'bg-background/50 border-border/40'
|
: 'bg-background/50 border-border/40'
|
||||||
@@ -541,7 +541,7 @@ function CharactersPanel({
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={ch.id}
|
key={ch.id}
|
||||||
className="w-full flex items-center gap-1.5 px-2 py-1.5 text-left hover:bg-muted/60 border-b border-border/30 transition-colors"
|
className="w-full flex items-center gap-1.5 px-2 py-1.5 text-left hover:bg-muted/60 border-b border-border/30 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
|
||||||
onClick={() => onScrollToChapter(ch.id)}
|
onClick={() => onScrollToChapter(ch.id)}
|
||||||
>
|
>
|
||||||
<span className={`w-1.5 h-1.5 rounded-full shrink-0 ${dotClass}`} />
|
<span className={`w-1.5 h-1.5 rounded-full shrink-0 ${dotClass}`} />
|
||||||
@@ -562,7 +562,7 @@ function CharactersPanel({
|
|||||||
</span>
|
</span>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{!isActive && status !== 'pending' && charCount > 0 && (
|
{!isActive && status !== 'pending' && charCount > 0 && (
|
||||||
<Button size="sm" variant="ghost" className="h-6 text-xs px-2 text-muted-foreground" onClick={onAnalyze} disabled={loadingAction}>
|
<Button size="xs" variant="ghost" className="text-muted-foreground" onClick={onAnalyze} disabled={loadingAction}>
|
||||||
{t('projectCard.reanalyze')}
|
{t('projectCard.reanalyze')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@@ -758,6 +758,7 @@ function ChaptersPanel({
|
|||||||
const [regeneratingSegs, setRegeneratingSegs] = useState<Set<number>>(new Set())
|
const [regeneratingSegs, setRegeneratingSegs] = useState<Set<number>>(new Set())
|
||||||
const [audioVersions, setAudioVersions] = useState<Record<number, number>>({})
|
const [audioVersions, setAudioVersions] = useState<Record<number, number>>({})
|
||||||
const prevSegStatusRef = useRef<Record<number, string>>({})
|
const prevSegStatusRef = useRef<Record<number, string>>({})
|
||||||
|
const initialExpandDoneRef = useRef(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!scrollToChapterId) return
|
if (!scrollToChapterId) return
|
||||||
@@ -823,12 +824,23 @@ function ChaptersPanel({
|
|||||||
const generatingChapterIds = detail.chapters
|
const generatingChapterIds = detail.chapters
|
||||||
.filter(ch => segments.some(s => s.chapter_index === ch.chapter_index && s.status === 'generating'))
|
.filter(ch => segments.some(s => s.chapter_index === ch.chapter_index && s.status === 'generating'))
|
||||||
.map(ch => ch.id)
|
.map(ch => ch.id)
|
||||||
if (generatingChapterIds.length === 0) return
|
if (generatingChapterIds.length > 0) {
|
||||||
setExpandedChapters(prev => {
|
setExpandedChapters(prev => {
|
||||||
const next = new Set(prev)
|
const next = new Set(prev)
|
||||||
generatingChapterIds.forEach(id => next.add(id))
|
generatingChapterIds.forEach(id => next.add(id))
|
||||||
return next.size === prev.size ? prev : next
|
return next.size === prev.size ? prev : next
|
||||||
})
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!initialExpandDoneRef.current) {
|
||||||
|
initialExpandDoneRef.current = true
|
||||||
|
const chapterIdsWithSegs = detail.chapters
|
||||||
|
.filter(ch => segments.some(s => s.chapter_index === ch.chapter_index))
|
||||||
|
.map(ch => ch.id)
|
||||||
|
if (chapterIdsWithSegs.length > 0) {
|
||||||
|
setExpandedChapters(new Set(chapterIdsWithSegs))
|
||||||
|
}
|
||||||
|
}
|
||||||
}, [segments, detail])
|
}, [segments, detail])
|
||||||
|
|
||||||
const hasChapters = detail && detail.chapters.length > 0 && ['ready', 'generating', 'done'].includes(status)
|
const hasChapters = detail && detail.chapters.length > 0 && ['ready', 'generating', 'done'].includes(status)
|
||||||
@@ -842,17 +854,17 @@ function ChaptersPanel({
|
|||||||
{hasChapters && (
|
{hasChapters && (
|
||||||
<div className="flex items-center gap-1 flex-wrap">
|
<div className="flex items-center gap-1 flex-wrap">
|
||||||
{detail!.chapters.some(c => ['pending', 'error', 'ready'].includes(c.status)) && (
|
{detail!.chapters.some(c => ['pending', 'error', 'ready'].includes(c.status)) && (
|
||||||
<Button size="sm" variant="outline" className="h-6 text-xs px-2" disabled={loadingAction} onClick={onParseAll}>
|
<Button size="xs" variant="outline" disabled={loadingAction} onClick={onParseAll}>
|
||||||
{t('projectCard.chapters.parseAll')}
|
{t('projectCard.chapters.parseAll')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{detail!.chapters.some(c => c.status === 'ready') && (
|
{detail!.chapters.some(c => c.status === 'ready') && (
|
||||||
<Button size="sm" variant="outline" className="h-6 text-xs px-2" disabled={loadingAction} onClick={onGenerateAll}>
|
<Button size="xs" variant="outline" disabled={loadingAction} onClick={onGenerateAll}>
|
||||||
{t('projectCard.chapters.generateAll')}
|
{t('projectCard.chapters.generateAll')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{detail!.chapters.some(c => ['pending', 'error'].includes(c.status)) && detail!.chapters.some(c => c.status === 'ready') && (
|
{detail!.chapters.some(c => ['pending', 'error'].includes(c.status)) && detail!.chapters.some(c => c.status === 'ready') && (
|
||||||
<Button size="sm" className="h-6 text-xs px-2" disabled={loadingAction} onClick={onProcessAll}>
|
<Button size="xs" disabled={loadingAction} onClick={onProcessAll}>
|
||||||
{loadingAction ? <Loader2 className="h-3 w-3 animate-spin" /> : t('projectCard.chapters.processAll')}
|
{loadingAction ? <Loader2 className="h-3 w-3 animate-spin" /> : t('projectCard.chapters.processAll')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@@ -882,7 +894,7 @@ function ChaptersPanel({
|
|||||||
<div key={ch.id} id={`ch-${ch.id}`}>
|
<div key={ch.id} id={`ch-${ch.id}`}>
|
||||||
{/* Chapter header — flat, full-width, click to expand */}
|
{/* Chapter header — flat, full-width, click to expand */}
|
||||||
<button
|
<button
|
||||||
className="w-full flex items-center gap-2 px-3 py-2.5 bg-muted/40 hover:bg-muted/70 border-b text-left transition-colors"
|
className="w-full flex items-center gap-2 px-3 py-2.5 bg-muted/40 hover:bg-muted/70 border-b text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
|
||||||
onClick={toggleChExpand}
|
onClick={toggleChExpand}
|
||||||
>
|
>
|
||||||
<span className="shrink-0 text-muted-foreground">
|
<span className="shrink-0 text-muted-foreground">
|
||||||
@@ -891,7 +903,7 @@ function ChaptersPanel({
|
|||||||
<span className="text-xs font-medium flex-1 truncate">{chTitle}</span>
|
<span className="text-xs font-medium flex-1 truncate">{chTitle}</span>
|
||||||
<span className="shrink-0 flex items-center gap-1.5" onClick={e => e.stopPropagation()}>
|
<span className="shrink-0 flex items-center gap-1.5" onClick={e => e.stopPropagation()}>
|
||||||
{ch.status === 'pending' && (
|
{ch.status === 'pending' && (
|
||||||
<Button size="sm" variant="outline" className="h-5 text-[11px] px-2" onClick={() => onParseChapter(ch.id, ch.title)}>
|
<Button size="xs" variant="outline" onClick={() => onParseChapter(ch.id, ch.title)}>
|
||||||
{t('projectCard.chapters.parse')}
|
{t('projectCard.chapters.parse')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@@ -902,13 +914,13 @@ function ChaptersPanel({
|
|||||||
)}
|
)}
|
||||||
{ch.status === 'ready' && !chGenerating && !chAllDone && !generatingChapterIndices.has(ch.chapter_index) && (
|
{ch.status === 'ready' && !chGenerating && !chAllDone && !generatingChapterIndices.has(ch.chapter_index) && (
|
||||||
<>
|
<>
|
||||||
<Button size="sm" variant="outline" className="h-5 text-[11px] px-2" disabled={loadingAction} onClick={() => {
|
<Button size="xs" variant="outline" disabled={loadingAction} onClick={() => {
|
||||||
setExpandedChapters(prev => { const n = new Set(prev); n.add(ch.id); return n })
|
setExpandedChapters(prev => { const n = new Set(prev); n.add(ch.id); return n })
|
||||||
onGenerate(ch.chapter_index)
|
onGenerate(ch.chapter_index)
|
||||||
}}>
|
}}>
|
||||||
{t('projectCard.chapters.generate')}
|
{t('projectCard.chapters.generate')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" variant="ghost" className="h-5 text-[11px] px-1.5 text-muted-foreground" disabled={loadingAction} onClick={() => onParseChapter(ch.id, ch.title)}>
|
<Button size="xs" variant="ghost" className="text-muted-foreground" disabled={loadingAction} onClick={() => onParseChapter(ch.id, ch.title)}>
|
||||||
{t('projectCard.chapters.reparse')}
|
{t('projectCard.chapters.reparse')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
@@ -918,22 +930,22 @@ function ChaptersPanel({
|
|||||||
<Loader2 className="h-3 w-3 animate-spin" />{t('projectCard.chapters.segmentProgress', { done: chDone, total: chTotal })}
|
<Loader2 className="h-3 w-3 animate-spin" />{t('projectCard.chapters.segmentProgress', { done: chDone, total: chTotal })}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{ch.status === 'ready' && chAllDone && (
|
{(ch.status === 'done' || (ch.status === 'ready' && chAllDone)) && (
|
||||||
<>
|
<>
|
||||||
<span className="text-[11px] text-muted-foreground">{t('projectCard.chapters.doneBadge', { count: chDone })}</span>
|
<span className="text-[11px] text-muted-foreground">{t('projectCard.chapters.doneBadge', { count: chDone })}</span>
|
||||||
<Button size="sm" variant="ghost" className="h-5 text-[11px] px-1.5 text-muted-foreground" disabled={loadingAction} onClick={() => {
|
<Button size="xs" variant="ghost" className="text-muted-foreground" disabled={loadingAction} onClick={() => {
|
||||||
setExpandedChapters(prev => { const n = new Set(prev); n.add(ch.id); return n })
|
setExpandedChapters(prev => { const n = new Set(prev); n.add(ch.id); return n })
|
||||||
onGenerate(ch.chapter_index, true)
|
onGenerate(ch.chapter_index, true)
|
||||||
}}>
|
}}>
|
||||||
<RefreshCw className="h-3 w-3 mr-1" />{t('projectCard.chapters.generate')}
|
<RefreshCw className="h-3 w-3" />{t('projectCard.chapters.generate')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" variant="ghost" className="h-5 w-5 p-0" onClick={() => onDownload(ch.chapter_index)} title={t('projectCard.downloadAll')}>
|
<Button size="icon" variant="ghost" className="h-6 w-6" onClick={() => onDownload(ch.chapter_index)} title={t('projectCard.downloadAll')}>
|
||||||
<Download className="h-3 w-3" />
|
<Download className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{ch.status === 'error' && (
|
{ch.status === 'error' && (
|
||||||
<Button size="sm" variant="outline" className="h-5 text-[11px] px-2 text-destructive border-destructive/40" onClick={() => onParseChapter(ch.id, ch.title)}>
|
<Button size="xs" variant="outline" className="text-destructive border-destructive/40" onClick={() => onParseChapter(ch.id, ch.title)}>
|
||||||
{t('projectCard.chapters.reparse')}
|
{t('projectCard.chapters.reparse')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@@ -979,19 +991,19 @@ function ChaptersPanel({
|
|||||||
<div className="ml-auto flex items-center gap-0.5 shrink-0">
|
<div className="ml-auto flex items-center gap-0.5 shrink-0">
|
||||||
{!isEditing ? (
|
{!isEditing ? (
|
||||||
<>
|
<>
|
||||||
<Button size="icon" variant="ghost" className="h-5 w-5" onClick={() => startEdit(seg)} disabled={isRegenerating} title={t('projectCard.segments.edit')}>
|
<Button size="icon" variant="ghost" className="h-6 w-6" onClick={() => startEdit(seg)} disabled={isRegenerating} title={t('projectCard.segments.edit')}>
|
||||||
<Pencil className="h-3 w-3" />
|
<Pencil className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="icon" variant="ghost" className="h-5 w-5" onClick={() => handleRegenerate(seg.id)} disabled={isRegenerating} title={t('projectCard.segments.regenerate')}>
|
<Button size="icon" variant="ghost" className="h-6 w-6" onClick={() => handleRegenerate(seg.id)} disabled={isRegenerating} title={t('projectCard.segments.regenerate')}>
|
||||||
<RefreshCw className="h-3 w-3" />
|
<RefreshCw className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Button size="icon" variant="ghost" className="h-5 w-5 text-destructive" onClick={cancelEdit} title={t('projectCard.segments.cancel')}>
|
<Button size="icon" variant="ghost" className="h-6 w-6 text-destructive" onClick={cancelEdit} title={t('projectCard.segments.cancel')}>
|
||||||
<X className="h-3 w-3" />
|
<X className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="icon" variant="ghost" className="h-5 w-5 text-primary" onClick={() => saveEdit(seg.id)} disabled={isSaving} title={t('projectCard.segments.save')}>
|
<Button size="icon" variant="ghost" className="h-6 w-6 text-primary" onClick={() => saveEdit(seg.id)} disabled={isSaving} title={t('projectCard.segments.save')}>
|
||||||
{isSaving ? <Loader2 className="h-3 w-3 animate-spin" /> : <Check className="h-3 w-3" />}
|
{isSaving ? <Loader2 className="h-3 w-3 animate-spin" /> : <Check className="h-3 w-3" />}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
@@ -1484,22 +1496,22 @@ export default function Audiobook() {
|
|||||||
{t(`status.${displayStatus}`, { defaultValue: displayStatus })}
|
{t(`status.${displayStatus}`, { defaultValue: displayStatus })}
|
||||||
</Badge>
|
</Badge>
|
||||||
{status === 'pending' && (
|
{status === 'pending' && (
|
||||||
<Button size="sm" className="h-7 text-xs px-2" onClick={handleAnalyze} disabled={loadingAction}>
|
<Button size="sm" onClick={handleAnalyze} disabled={loadingAction}>
|
||||||
{t('projectCard.analyze')}
|
{t('projectCard.analyze')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{status === 'ready' && (
|
{status === 'ready' && (
|
||||||
<Button size="sm" className="h-7 text-xs px-2" onClick={handleProcessAll} disabled={loadingAction}>
|
<Button size="sm" onClick={handleProcessAll} disabled={loadingAction}>
|
||||||
{loadingAction ? <Loader2 className="h-3 w-3 animate-spin mr-1" /> : null}
|
{loadingAction ? <Loader2 className="h-3 w-3 animate-spin" /> : null}
|
||||||
{t('projectCard.processAll')}
|
{t('projectCard.processAll')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{status === 'done' && (
|
{status === 'done' && (
|
||||||
<Button size="sm" variant="outline" className="h-7 text-xs px-2" onClick={() => handleDownload()} disabled={loadingAction}>
|
<Button size="sm" variant="outline" onClick={() => handleDownload()} disabled={loadingAction}>
|
||||||
<Download className="h-3 w-3 mr-1" />{t('projectCard.downloadAll')}
|
<Download className="h-3 w-3" />{t('projectCard.downloadAll')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button size="icon" variant="ghost" className="h-7 w-7 shrink-0" onClick={handleDelete}>
|
<Button size="icon" variant="ghost" className="h-8 w-8 shrink-0" onClick={handleDelete}>
|
||||||
<Trash2 className="h-4 w-4 text-destructive" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1537,14 +1549,14 @@ export default function Audiobook() {
|
|||||||
{chaptersError > 0 && (
|
{chaptersError > 0 && (
|
||||||
<>
|
<>
|
||||||
<span className="text-destructive">({t('projectCard.chaptersError', { count: chaptersError })})</span>
|
<span className="text-destructive">({t('projectCard.chaptersError', { count: chaptersError })})</span>
|
||||||
<Button size="sm" variant="outline" className="h-5 text-[10px] px-1.5 text-destructive border-destructive/40" onClick={handleRetryFailed}>
|
<Button size="xs" variant="outline" className="text-destructive border-destructive/40" onClick={handleRetryFailed}>
|
||||||
{t('projectCard.retryFailed')}
|
{t('projectCard.retryFailed')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{chaptersParsing > 0 && totalCount > 0 && (
|
{chaptersParsing > 0 && totalCount > 0 && (
|
||||||
<Button size="sm" variant="ghost" className="h-5 text-[10px] px-1.5 text-destructive" onClick={handleCancelBatch}>
|
<Button size="xs" variant="ghost" className="text-destructive" onClick={handleCancelBatch}>
|
||||||
{t('projectCard.cancelParsing')}
|
{t('projectCard.cancelParsing')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@@ -1560,7 +1572,7 @@ export default function Audiobook() {
|
|||||||
<span>{t('projectCard.segmentsProgress', { done: doneCount, total: totalCount })}</span>
|
<span>{t('projectCard.segmentsProgress', { done: doneCount, total: totalCount })}</span>
|
||||||
</div>
|
</div>
|
||||||
{!chaptersParsing && hasGenerating && (
|
{!chaptersParsing && hasGenerating && (
|
||||||
<Button size="sm" variant="ghost" className="h-5 text-[10px] px-1.5 text-destructive" onClick={handleCancelBatch}>
|
<Button size="xs" variant="ghost" className="text-destructive" onClick={handleCancelBatch}>
|
||||||
{t('projectCard.cancelGenerating')}
|
{t('projectCard.cancelGenerating')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@@ -1570,7 +1582,7 @@ export default function Audiobook() {
|
|||||||
)}
|
)}
|
||||||
{chaptersParsing > 0 && !totalCount && (
|
{chaptersParsing > 0 && !totalCount && (
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button size="sm" variant="ghost" className="h-5 text-[10px] px-1.5 text-destructive" onClick={handleCancelBatch}>
|
<Button size="xs" variant="ghost" className="text-destructive" onClick={handleCancelBatch}>
|
||||||
{t('projectCard.cancelParsing')}
|
{t('projectCard.cancelParsing')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1591,6 +1603,7 @@ export default function Audiobook() {
|
|||||||
onScrollToChapter={(id) => setScrollToChapterId(id)}
|
onScrollToChapter={(id) => setScrollToChapterId(id)}
|
||||||
/>
|
/>
|
||||||
<ChaptersPanel
|
<ChaptersPanel
|
||||||
|
key={selectedProject.id}
|
||||||
project={selectedProject}
|
project={selectedProject}
|
||||||
detail={detail}
|
detail={detail}
|
||||||
segments={segments}
|
segments={segments}
|
||||||
|
|||||||
Reference in New Issue
Block a user