feat: add continue script functionality for AI-generated audiobook projects
This commit is contained in:
@@ -699,6 +699,47 @@ function AIScriptDialog({ open, onClose, onCreated }: { open: boolean; onClose:
|
||||
)
|
||||
}
|
||||
|
||||
function ContinueScriptDialog({ open, onClose, onConfirm }: { open: boolean; onClose: () => void; onConfirm: (n: number) => Promise<void> }) {
|
||||
const { t } = useTranslation('audiobook')
|
||||
const [count, setCount] = useState(4)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const handleSubmit = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
await onConfirm(count)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={v => { if (!v) onClose() }}>
|
||||
<DialogContent className="sm:max-w-sm">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('projectCard.continueScriptDialog.title')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-3 py-2">
|
||||
<label className="flex flex-col gap-1 text-sm">
|
||||
<span className="text-muted-foreground text-xs">{t('projectCard.continueScriptDialog.label')}</span>
|
||||
<Input
|
||||
type="number" min={1} max={20} value={count}
|
||||
onChange={e => setCount(Math.min(20, Math.max(1, Number(e.target.value))))}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<Button size="sm" variant="ghost" onClick={onClose} disabled={loading}>{t('projectCard.segments.cancel')}</Button>
|
||||
<Button size="sm" onClick={handleSubmit} disabled={loading}>
|
||||
{loading ? <Loader2 className="h-3 w-3 animate-spin mr-1" /> : null}
|
||||
{loading ? t('projectCard.continueScriptDialog.starting') : t('projectCard.continueScriptDialog.start')}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
function ProjectListSidebar({
|
||||
projects,
|
||||
selectedId,
|
||||
@@ -1079,6 +1120,7 @@ function ChaptersPanel({
|
||||
onParseAll,
|
||||
onGenerateAll,
|
||||
onProcessAll,
|
||||
onContinueScript,
|
||||
onDownload,
|
||||
onSequentialPlayingChange,
|
||||
onUpdateSegment,
|
||||
@@ -1097,6 +1139,7 @@ function ChaptersPanel({
|
||||
onParseAll: () => void
|
||||
onGenerateAll: () => void
|
||||
onProcessAll: () => void
|
||||
onContinueScript?: () => void
|
||||
onDownload: (chapterIndex?: number) => void
|
||||
onSequentialPlayingChange: (id: number | null) => void
|
||||
onUpdateSegment: (segmentId: number, data: { text: string; emo_text?: string | null; emo_alpha?: number | null }) => Promise<void>
|
||||
@@ -1227,6 +1270,12 @@ function ChaptersPanel({
|
||||
{loadingAction ? <Loader2 className="h-3 w-3 animate-spin" /> : t('projectCard.chapters.processAll')}
|
||||
</Button>
|
||||
)}
|
||||
{isAIMode && onContinueScript && (
|
||||
<Button size="xs" variant="outline" disabled={loadingAction} onClick={onContinueScript}>
|
||||
<Plus className="h-3 w-3 mr-1" />
|
||||
{t('projectCard.chapters.continueScript')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -1464,6 +1513,7 @@ export default function Audiobook() {
|
||||
const [sequentialPlayingId, setSequentialPlayingId] = useState<number | null>(null)
|
||||
const [showCreate, setShowCreate] = useState(false)
|
||||
const [showAIScript, setShowAIScript] = useState(false)
|
||||
const [showContinueScript, setShowContinueScript] = useState(false)
|
||||
const [showLLM, setShowLLM] = useState(false)
|
||||
const [sidebarOpen, setSidebarOpen] = useState(true)
|
||||
const [charactersCollapsed, setCharactersCollapsed] = useState(false)
|
||||
@@ -1718,6 +1768,24 @@ export default function Audiobook() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleContinueScript = async (additionalChapters: number) => {
|
||||
if (!selectedProject) return
|
||||
setLoadingAction(true)
|
||||
setIsPolling(true)
|
||||
try {
|
||||
await audiobookApi.continueScript(selectedProject.id, additionalChapters)
|
||||
toast.success(t('projectCard.chapters.continueScriptStarted'))
|
||||
setShowContinueScript(false)
|
||||
fetchProjects()
|
||||
fetchDetail()
|
||||
} catch (e: any) {
|
||||
setIsPolling(false)
|
||||
toast.error(formatApiError(e))
|
||||
} finally {
|
||||
setLoadingAction(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancelBatch = async () => {
|
||||
if (!selectedProject) return
|
||||
try {
|
||||
@@ -1843,6 +1911,7 @@ export default function Audiobook() {
|
||||
<LLMConfigDialog open={showLLM} onClose={() => setShowLLM(false)} />
|
||||
<CreateProjectDialog open={showCreate} onClose={() => setShowCreate(false)} onCreated={fetchProjects} />
|
||||
<AIScriptDialog open={showAIScript} onClose={() => setShowAIScript(false)} onCreated={() => { fetchProjects(); setShowAIScript(false) }} />
|
||||
<ContinueScriptDialog open={showContinueScript} onClose={() => setShowContinueScript(false)} onConfirm={handleContinueScript} />
|
||||
{!selectedProject ? (
|
||||
<EmptyState />
|
||||
) : (
|
||||
@@ -1889,9 +1958,9 @@ export default function Audiobook() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{status === 'analyzing' && (
|
||||
{(status === 'analyzing' || (status === 'generating' && selectedProject.source_type === 'ai_generated')) && (
|
||||
<div className="shrink-0 mx-4 mt-2">
|
||||
<LogStream projectId={selectedProject.id} active={status === 'analyzing'} />
|
||||
<LogStream projectId={selectedProject.id} active={true} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1981,6 +2050,7 @@ export default function Audiobook() {
|
||||
onParseAll={handleParseAll}
|
||||
onGenerateAll={handleGenerateAll}
|
||||
onProcessAll={handleProcessAll}
|
||||
onContinueScript={selectedProject.source_type === 'ai_generated' ? () => setShowContinueScript(true) : undefined}
|
||||
onDownload={handleDownload}
|
||||
onSequentialPlayingChange={setSequentialPlayingId}
|
||||
onUpdateSegment={handleUpdateSegment}
|
||||
|
||||
Reference in New Issue
Block a user