feat(audiobook): refactor background tasks to use asyncio for project analysis and generation

This commit is contained in:
2026-03-10 16:13:35 +08:00
parent 5037857dd4
commit 230274bbc3
2 changed files with 28 additions and 14 deletions

View File

@@ -1,8 +1,9 @@
import asyncio
import logging
from pathlib import Path
from typing import Optional
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, UploadFile, File, Form, status
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form, status
from fastapi.responses import FileResponse
from sqlalchemy.orm import Session
@@ -152,7 +153,6 @@ async def get_project(
@router.post("/projects/{project_id}/analyze")
async def analyze_project(
project_id: int,
background_tasks: BackgroundTasks,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
@@ -176,14 +176,13 @@ async def analyze_project(
finally:
async_db.close()
background_tasks.add_task(run_analysis)
asyncio.create_task(run_analysis())
return {"message": "Analysis started", "project_id": project_id}
@router.post("/projects/{project_id}/confirm")
async def confirm_characters(
project_id: int,
background_tasks: BackgroundTasks,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
@@ -207,7 +206,7 @@ async def confirm_characters(
finally:
async_db.close()
background_tasks.add_task(run_parsing)
asyncio.create_task(run_parsing())
return {"message": "Chapter parsing started", "project_id": project_id}
@@ -259,7 +258,6 @@ async def update_character(
@router.post("/projects/{project_id}/generate")
async def generate_project(
project_id: int,
background_tasks: BackgroundTasks,
data: AudiobookGenerateRequest = AudiobookGenerateRequest(),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
@@ -285,7 +283,7 @@ async def generate_project(
finally:
async_db.close()
background_tasks.add_task(run_generation)
asyncio.create_task(run_generation())
msg = f"Generation started for chapter {chapter_index}" if chapter_index is not None else "Generation started"
return {"message": msg, "project_id": project_id, "chapter_index": chapter_index}

View File

@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback, useRef } from 'react'
import { toast } from 'sonner'
import { Book, Plus, Trash2, RefreshCw, Download, ChevronDown, ChevronUp, Play, Square, Pencil, Check, X } from 'lucide-react'
import { Book, Plus, Trash2, RefreshCw, Download, ChevronDown, ChevronUp, Play, Square, Pencil, Check, X, Loader2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Textarea } from '@/components/ui/textarea'
@@ -227,7 +227,13 @@ function CreateProjectPanel({ onCreated }: { onCreated: () => void }) {
<Textarea placeholder="粘贴小说文本..." rows={6} value={text} onChange={e => setText(e.target.value)} />
)}
{sourceType === 'epub' && (
<Input type="file" accept=".epub" onChange={e => setEpubFile(e.target.files?.[0] || null)} />
<Input type="file" accept=".epub" onChange={e => {
const file = e.target.files?.[0] || null
setEpubFile(file)
if (file && !title) {
setTitle(file.name.replace(/\.epub$/i, ''))
}
}} />
)}
<Button size="sm" onClick={handleCreate} disabled={loading}>{loading ? '创建中...' : '创建项目'}</Button>
</div>
@@ -553,7 +559,10 @@ function ProjectCard({ project, onRefresh }: { project: AudiobookProject; onRefr
<span className="text-xs text-muted-foreground mx-2 flex-1">{chDone}/{chTotal} </span>
<div className="flex gap-1 shrink-0">
{chGenerating ? (
<Badge variant="secondary" className="text-xs">...</Badge>
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<Loader2 className="h-3 w-3 animate-spin" />
<span></span>
</div>
) : chAllDone ? (
<>
<Badge variant="outline" className="text-xs"></Badge>
@@ -571,7 +580,10 @@ function ProjectCard({ project, onRefresh }: { project: AudiobookProject; onRefr
disabled={loadingAction}
onClick={() => handleGenerate(chIdx)}
>
{loadingAction
? <Loader2 className="h-3 w-3 animate-spin" />
: '生成此章'
}
</Button>
)}
</div>
@@ -604,14 +616,18 @@ function ProjectCard({ project, onRefresh }: { project: AudiobookProject; onRefr
<div className="flex items-start gap-2 text-xs">
<Badge variant="outline" className="shrink-0 text-xs mt-0.5">{seg.character_name || '?'}</Badge>
<span className="text-muted-foreground flex-1 min-w-0 break-words leading-relaxed">{seg.text}</span>
{seg.status !== 'done' && (
{seg.status === 'generating' ? (
<div className="flex items-center gap-1 text-xs text-muted-foreground shrink-0 mt-0.5">
<Loader2 className="h-3 w-3 animate-spin" />
</div>
) : seg.status !== 'done' ? (
<Badge
variant={seg.status === 'error' ? 'destructive' : 'secondary'}
className="shrink-0 text-xs mt-0.5"
>
{seg.status === 'generating' ? '生成中' : seg.status === 'error' ? '出错' : '待生成'}
{seg.status === 'error' ? '出错' : '待生成'}
</Badge>
)}
) : null}
</div>
{seg.status === 'done' && (
<AudioPlayer