Фоновые задачи: плейлисты, длинные лекции, вкладка настроек
- таблица jobs + очередь с воркером: POST /jobs сразу отвечает, элементы
плейлиста обрабатываются последовательно, есть отмена и живой прогресс
(скачивание %, минуты распознанного Vosk-аудио)
- expand_url: плейлист раскрывается в список видео
- keep_video=false теперь качает только аудио (для 4-5ч лекций)
- настройки (LLM-ключ, модель, длина выжимки, хранение видео, путь Vosk)
хранятся в Postgres: переживают перезапуск и перезагрузку страницы
- длина выжимки прокидывается в LLM-промпт ({n} предложений)
- UI: вкладки «Главная»/«Настройки», карточка задач с автообновлением
каждые 3 сек, прогресс-бар, отмена, библиотека обновляется по мере
готовности элементов
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,9 @@ from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from api.config import settings
|
||||
from api.models.base import Base
|
||||
import api.models.job # noqa: F401 - register model with Base.metadata
|
||||
import api.models.section # noqa: F401 - register model with Base.metadata
|
||||
import api.models.setting # noqa: F401 - register model with Base.metadata
|
||||
import api.models.video # noqa: F401 - register model with Base.metadata
|
||||
|
||||
|
||||
@@ -69,3 +71,6 @@ async def init_db() -> None:
|
||||
"REFERENCES sections(id) ON DELETE SET NULL"
|
||||
)
|
||||
)
|
||||
await conn.execute(
|
||||
text("ALTER TABLE videos ADD COLUMN IF NOT EXISTS job_id VARCHAR")
|
||||
)
|
||||
|
||||
53
api/db/job.py
Normal file
53
api/db/job.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from sqlalchemy import select, update
|
||||
|
||||
from api.db.connection import SessionLocal
|
||||
from api.models.job import Job
|
||||
|
||||
|
||||
async def create_job(job_id: str, url: str) -> Job:
|
||||
async with SessionLocal() as session:
|
||||
j = Job(id=job_id, url=url, status="queued")
|
||||
session.add(j)
|
||||
await session.commit()
|
||||
await session.refresh(j)
|
||||
return j
|
||||
|
||||
|
||||
async def get_job(job_id: str) -> Job | None:
|
||||
async with SessionLocal() as session:
|
||||
return await session.get(Job, job_id)
|
||||
|
||||
|
||||
async def list_jobs(limit: int = 50) -> list[Job]:
|
||||
async with SessionLocal() as session:
|
||||
res = await session.execute(
|
||||
select(Job).order_by(Job.created_at.desc()).limit(limit)
|
||||
)
|
||||
return list(res.scalars().all())
|
||||
|
||||
|
||||
async def update_job(job_id: str, **fields) -> None:
|
||||
async with SessionLocal() as session:
|
||||
await session.execute(update(Job).where(Job.id == job_id).values(**fields))
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def delete_job(job_id: str) -> bool:
|
||||
async with SessionLocal() as session:
|
||||
j = await session.get(Job, job_id)
|
||||
if j is None:
|
||||
return False
|
||||
await session.delete(j)
|
||||
await session.commit()
|
||||
return True
|
||||
|
||||
|
||||
async def mark_interrupted() -> None:
|
||||
"""На старте приложения: задачи, шедшие до перезапуска, отмечаем прерванными."""
|
||||
async with SessionLocal() as session:
|
||||
await session.execute(
|
||||
update(Job)
|
||||
.where(Job.status.in_(("queued", "running")))
|
||||
.values(status="interrupted", current_item="")
|
||||
)
|
||||
await session.commit()
|
||||
21
api/db/setting.py
Normal file
21
api/db/setting.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from sqlalchemy import select
|
||||
|
||||
from api.db.connection import SessionLocal
|
||||
from api.models.setting import Setting
|
||||
|
||||
|
||||
async def get_all_settings() -> dict[str, str]:
|
||||
async with SessionLocal() as session:
|
||||
res = await session.execute(select(Setting))
|
||||
return {s.key: s.value for s in res.scalars().all()}
|
||||
|
||||
|
||||
async def set_settings(values: dict[str, str]) -> None:
|
||||
async with SessionLocal() as session:
|
||||
for key, value in values.items():
|
||||
existing = await session.get(Setting, key)
|
||||
if existing is None:
|
||||
session.add(Setting(key=key, value=str(value)))
|
||||
else:
|
||||
existing.value = str(value)
|
||||
await session.commit()
|
||||
@@ -14,12 +14,14 @@ async def create_video(
|
||||
text_summary_path: str,
|
||||
transcription_method: str,
|
||||
section_id: int | None = None,
|
||||
job_id: str | None = None,
|
||||
) -> Video:
|
||||
async with SessionLocal() as session:
|
||||
v = Video(
|
||||
uuid=uuid,
|
||||
source_url=source_url,
|
||||
section_id=section_id,
|
||||
job_id=job_id,
|
||||
title=title or "",
|
||||
video_path=video_path or "",
|
||||
text_full_path=text_full_path or "",
|
||||
|
||||
Reference in New Issue
Block a user