Files
Story-extractor/backend/app/api/extraction.py
2026-04-24 16:02:16 +08:00

113 lines
3.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import asyncio
import json
from typing import Annotated, Optional
from fastapi import APIRouter, HTTPException, Depends, Query
from fastapi.responses import StreamingResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.database import get_db
from app.models.transcript import Transcript
from app.state import task_manager
from app.services.extractor import run_extraction
router = APIRouter(prefix="/extraction", tags=["故事提取"])
@router.post("/start")
async def start_extraction(
task_id: Optional[str] = Query(None),
db: Annotated[AsyncSession, Depends(get_db)] = None,
):
"""启动提取任务,支持多用户并发"""
# 如果传了 task_id复用已有任务
if task_id:
state = task_manager.get_task(task_id)
if state and state.is_running:
return {"task_id": task_id, "message": "任务正在进行中"}
else:
state = None
# 检查是否有待处理的文字稿
result = await db.execute(select(Transcript).where(Transcript.status == "pending"))
pending = result.scalars().all()
if not pending:
# 没有 pending 的,把所有非 pending 的文件重置为 pending包括 completed/processing/error
result2 = await db.execute(
select(Transcript).where(Transcript.status != "pending")
)
others = result2.scalars().all()
if others:
for t in others:
t.status = "pending"
t.error_message = ""
await db.commit()
pending = others
else:
raise HTTPException(400, "没有可提取的文字稿,请先上传文件")
total = len(pending)
# 创建新任务
state = task_manager.create_task()
state.reset()
state.is_running = True
asyncio.create_task(run_extraction(state))
return {"task_id": state.task_id, "total_files": total}
@router.get("/status")
async def get_status(task_id: Optional[str] = Query(None)):
"""获取提取进度"""
if task_id:
state = task_manager.get_task(task_id)
if state:
return {**state.progress, "task_id": state.task_id}
return {"status": "idle", "percent": 0}
@router.get("/status/stream")
async def stream_status(task_id: Optional[str] = Query(None)):
"""SSE 实时进度推送(支持多用户)"""
if not task_id:
raise HTTPException(400, "缺少 task_id 参数")
state = task_manager.get_task(task_id)
if not state:
raise HTTPException(404, "任务不存在")
async def event_generator():
# 立即推送当前状态
yield f"event: progress\ndata: {json.dumps({**state.progress, 'task_id': state.task_id}, ensure_ascii=False)}\n\n"
if state.progress.get("status") in ("completed", "error", "stopped"):
return
while True:
data = await state.get_update()
yield f"event: progress\ndata: {json.dumps({**data, 'task_id': state.task_id}, ensure_ascii=False)}\n\n"
if data.get("status") in ("completed", "error", "stopped"):
break
return StreamingResponse(
event_generator(),
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
)
@router.post("/stop")
async def stop_extraction(task_id: Optional[str] = Query(None)):
"""停止提取任务"""
if task_id:
state = task_manager.get_task(task_id)
else:
state = None
if not state or not state.is_running:
raise HTTPException(400, "没有正在进行的提取任务")
state.cancel_event.set()
return {"message": "正在停止...", "task_id": state.task_id}