"""FastAPI backend для сервиса транскрибации.""" import json from contextlib import asynccontextmanager from pathlib import Path from typing import List, Optional from fastapi import FastAPI, File, UploadFile, WebSocket, WebSocketDisconnect from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse, PlainTextResponse, HTMLResponse from fastapi.staticfiles import StaticFiles from backend.queue import ( UPLOAD_DIR, PROCESSED_DIR, enqueue, get_all_tasks, get_task_status, get_processed_tree, read_file_content, set_progress_callback, start_workers, stop_workers, ) # WebSocket менеджер class ConnectionManager: def __init__(self): self.active_connections: List[WebSocket] = [] async def connect(self, websocket: WebSocket): await websocket.accept() self.active_connections.append(websocket) def disconnect(self, websocket: WebSocket): if websocket in self.active_connections: self.active_connections.remove(websocket) async def broadcast(self, message: dict): for conn in self.active_connections: try: await conn.send_json(message) except Exception: pass manager = ConnectionManager() # Устанавливаем callback для отправки прогресса через WebSocket set_progress_callback(manager.broadcast) @asynccontextmanager async def lifespan(app: FastAPI): """Управление жизненным циклом приложения.""" print("🚀 Запуск рабочих процессов...") start_workers(num_workers=1) yield print("🛑 Остановка рабочих процессов...") stop_workers() app = FastAPI( title="Transcription Service", version="1.0.0", lifespan=lifespan, ) # CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # === API Endpoints === @app.get("/", response_class=HTMLResponse) async def root(): """Главная страница.""" index_path = Path(__file__).parent / "static" / "index.html" if index_path.exists(): return index_path.read_text(encoding="utf-8") return "

Transcription Service

Frontend not built

" @app.post("/upload") async def upload_file(file: UploadFile = File(...)): """Загружает файл и добавляет в очередь обработки.""" # Сохраняем файл file_path = UPLOAD_DIR / file.filename with open(file_path, "wb") as f: content = await file.read() f.write(content) # Добавляем в очередь task_id = await enqueue(file_path) return { "task_id": task_id, "filename": file.filename, "status": "queued", "message": "Файл добавлен в очередь обработки", } @app.post("/upload-batch") async def upload_batch(files: List[UploadFile] = File(...)): """Загружает несколько файлов пакетно.""" results = [] for file in files: file_path = UPLOAD_DIR / file.filename with open(file_path, "wb") as f: content = await file.read() f.write(content) task_id = await enqueue(file_path) results.append({ "task_id": task_id, "filename": file.filename, "status": "queued", }) return { "uploaded": len(results), "tasks": results, } @app.websocket("/ws") async def websocket_endpoint(websocket: WebSocket): """WebSocket для получения прогресса обработки.""" await manager.connect(websocket) try: while True: # Ждём сообщения от клиента (ping/keepalive) data = await websocket.receive_text() msg = json.loads(data) if msg.get("action") == "get_tasks": tasks = get_all_tasks() await websocket.send_json({ "type": "tasks_list", "tasks": tasks, }) elif msg.get("action") == "get_tree": tree = get_processed_tree() await websocket.send_json({ "type": "file_tree", "tree": tree, }) except WebSocketDisconnect: manager.disconnect(websocket) except Exception: manager.disconnect(websocket) @app.get("/api/tasks") async def api_tasks(): """Возвращает список всех задач.""" return {"tasks": get_all_tasks()} @app.get("/api/tasks/{task_id}") async def api_task(task_id: str): """Возвращает статус конкретной задачи.""" status = get_task_status(task_id) if not status: return {"error": "Task not found"} return status @app.get("/api/files") async def api_files(): """Возвращает дерево обработанных файлов.""" return {"tree": get_processed_tree()} @app.get("/api/files/content") async def api_file_content(path: str): """Возвращает содержимое файла.""" try: content = read_file_content(path) return {"content": content, "path": path} except Exception as e: return {"error": str(e)} @app.get("/api/files/download") async def api_download(path: str): """Скачивает файл.""" file_path = PROCESSED_DIR / path if not file_path.exists(): return {"error": "File not found"} return FileResponse(file_path, filename=file_path.name) # Статические файлы app.mount("/static", StaticFiles(directory="backend/static"), name="static")