feat: add force option to project generation for reprocessing completed segments
This commit is contained in:
@@ -457,12 +457,13 @@ async def generate_project(
|
|||||||
from core.database import SessionLocal
|
from core.database import SessionLocal
|
||||||
|
|
||||||
chapter_index = data.chapter_index
|
chapter_index = data.chapter_index
|
||||||
|
force = data.force
|
||||||
|
|
||||||
async def run_generation():
|
async def run_generation():
|
||||||
async_db = SessionLocal()
|
async_db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
db_user = crud.get_user_by_id(async_db, current_user.id)
|
db_user = crud.get_user_by_id(async_db, current_user.id)
|
||||||
await _generate(project_id, db_user, async_db, chapter_index=chapter_index)
|
await _generate(project_id, db_user, async_db, chapter_index=chapter_index, force=force)
|
||||||
finally:
|
finally:
|
||||||
async_db.close()
|
async_db.close()
|
||||||
|
|
||||||
|
|||||||
@@ -536,7 +536,7 @@ async def _bootstrap_character_voices(segments, user, backend, backend_type: str
|
|||||||
logger.error(f"Failed to bootstrap voice for design_id={design.id}: {e}", exc_info=True)
|
logger.error(f"Failed to bootstrap voice for design_id={design.id}: {e}", exc_info=True)
|
||||||
|
|
||||||
|
|
||||||
async def generate_project(project_id: int, user: User, db: Session, chapter_index: Optional[int] = None, cancel_event: Optional[asyncio.Event] = None) -> None:
|
async def generate_project(project_id: int, user: User, db: Session, chapter_index: Optional[int] = None, cancel_event: Optional[asyncio.Event] = None, force: bool = False) -> None:
|
||||||
project = db.query(AudiobookProject).filter(AudiobookProject.id == project_id).first()
|
project = db.query(AudiobookProject).filter(AudiobookProject.id == project_id).first()
|
||||||
if not project:
|
if not project:
|
||||||
return
|
return
|
||||||
@@ -551,6 +551,11 @@ async def generate_project(project_id: int, user: User, db: Session, chapter_ind
|
|||||||
crud.update_audiobook_project_status(db, project_id, "generating")
|
crud.update_audiobook_project_status(db, project_id, "generating")
|
||||||
|
|
||||||
segments = crud.list_audiobook_segments(db, project_id, chapter_index=chapter_index)
|
segments = crud.list_audiobook_segments(db, project_id, chapter_index=chapter_index)
|
||||||
|
if force:
|
||||||
|
for s in segments:
|
||||||
|
if s.status == "done":
|
||||||
|
crud.update_audiobook_segment_status(db, s.id, "pending")
|
||||||
|
segments = crud.list_audiobook_segments(db, project_id, chapter_index=chapter_index)
|
||||||
pending_segments = [s for s in segments if s.status in ("pending", "error")]
|
pending_segments = [s for s in segments if s.status in ("pending", "error")]
|
||||||
if not pending_segments:
|
if not pending_segments:
|
||||||
if chapter_index is None:
|
if chapter_index is None:
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ class AudiobookAnalyzeRequest(BaseModel):
|
|||||||
|
|
||||||
class AudiobookGenerateRequest(BaseModel):
|
class AudiobookGenerateRequest(BaseModel):
|
||||||
chapter_index: Optional[int] = None
|
chapter_index: Optional[int] = None
|
||||||
|
force: bool = False
|
||||||
|
|
||||||
|
|
||||||
class AudiobookCharacterUpdate(BaseModel):
|
class AudiobookCharacterUpdate(BaseModel):
|
||||||
|
|||||||
@@ -118,9 +118,10 @@ export const audiobookApi = {
|
|||||||
await apiClient.post(`/audiobook/projects/${projectId}/chapters/${chapterId}/parse`)
|
await apiClient.post(`/audiobook/projects/${projectId}/chapters/${chapterId}/parse`)
|
||||||
},
|
},
|
||||||
|
|
||||||
generate: async (id: number, chapterIndex?: number): Promise<void> => {
|
generate: async (id: number, chapterIndex?: number, force?: boolean): Promise<void> => {
|
||||||
await apiClient.post(`/audiobook/projects/${id}/generate`, {
|
await apiClient.post(`/audiobook/projects/${id}/generate`, {
|
||||||
chapter_index: chapterIndex ?? null,
|
chapter_index: chapterIndex ?? null,
|
||||||
|
force: force ?? false,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -737,7 +737,7 @@ function ChaptersPanel({
|
|||||||
generatingChapterIndices: Set<number>
|
generatingChapterIndices: Set<number>
|
||||||
sequentialPlayingId: number | null
|
sequentialPlayingId: number | null
|
||||||
onParseChapter: (chapterId: number, title?: string) => void
|
onParseChapter: (chapterId: number, title?: string) => void
|
||||||
onGenerate: (chapterIndex?: number) => void
|
onGenerate: (chapterIndex?: number, force?: boolean) => void
|
||||||
onParseAll: () => void
|
onParseAll: () => void
|
||||||
onGenerateAll: () => void
|
onGenerateAll: () => void
|
||||||
onProcessAll: () => void
|
onProcessAll: () => void
|
||||||
@@ -921,6 +921,12 @@ function ChaptersPanel({
|
|||||||
{ch.status === 'ready' && chAllDone && (
|
{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={() => {
|
||||||
|
setExpandedChapters(prev => { const n = new Set(prev); n.add(ch.id); return n })
|
||||||
|
onGenerate(ch.chapter_index, true)
|
||||||
|
}}>
|
||||||
|
<RefreshCw className="h-3 w-3 mr-1" />{t('projectCard.chapters.generate')}
|
||||||
|
</Button>
|
||||||
<Button size="sm" variant="ghost" className="h-5 w-5 p-0" onClick={() => onDownload(ch.chapter_index)} title={t('projectCard.downloadAll')}>
|
<Button size="sm" variant="ghost" className="h-5 w-5 p-0" onClick={() => onDownload(ch.chapter_index)} title={t('projectCard.downloadAll')}>
|
||||||
<Download className="h-3 w-3" />
|
<Download className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -1179,7 +1185,7 @@ export default function Audiobook() {
|
|||||||
}
|
}
|
||||||
}, [segments, generatingChapterIndices])
|
}, [segments, generatingChapterIndices])
|
||||||
|
|
||||||
const shouldPoll = isPolling || ['analyzing', 'generating'].includes(status) || hasParsingChapter || generatingChapterIndices.size > 0
|
const shouldPoll = isPolling || ['analyzing', 'generating'].includes(status) || hasParsingChapter || generatingChapterIndices.size > 0 || segments.some(s => s.status === 'generating')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!shouldPoll || !selectedProjectId) return
|
if (!shouldPoll || !selectedProjectId) return
|
||||||
@@ -1235,7 +1241,7 @@ export default function Audiobook() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleGenerate = async (chapterIndex?: number) => {
|
const handleGenerate = async (chapterIndex?: number, force?: boolean) => {
|
||||||
if (!selectedProject) return
|
if (!selectedProject) return
|
||||||
setLoadingAction(true)
|
setLoadingAction(true)
|
||||||
if (chapterIndex !== undefined) {
|
if (chapterIndex !== undefined) {
|
||||||
@@ -1244,7 +1250,7 @@ export default function Audiobook() {
|
|||||||
setIsPolling(true)
|
setIsPolling(true)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await audiobookApi.generate(selectedProject.id, chapterIndex)
|
await audiobookApi.generate(selectedProject.id, chapterIndex, force)
|
||||||
toast.success(chapterIndex !== undefined
|
toast.success(chapterIndex !== undefined
|
||||||
? t('projectCard.chapters.generateStarted', { index: chapterIndex + 1 })
|
? t('projectCard.chapters.generateStarted', { index: chapterIndex + 1 })
|
||||||
: t('projectCard.chapters.generateAllStarted'))
|
: t('projectCard.chapters.generateAllStarted'))
|
||||||
@@ -1365,7 +1371,6 @@ export default function Audiobook() {
|
|||||||
if (!selectedProject) return
|
if (!selectedProject) return
|
||||||
try {
|
try {
|
||||||
await audiobookApi.regenerateSegment(selectedProject.id, segmentId)
|
await audiobookApi.regenerateSegment(selectedProject.id, segmentId)
|
||||||
setIsPolling(true)
|
|
||||||
fetchSegments()
|
fetchSegments()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
toast.error(formatApiError(e))
|
toast.error(formatApiError(e))
|
||||||
|
|||||||
Reference in New Issue
Block a user