feat: API router + pipeline — toggle, status, transcripts, WebSocket, auth stub

This commit is contained in:
2026-04-01 02:26:47 +02:00
parent ad84ad5aa3
commit 319db8c788
3 changed files with 211 additions and 0 deletions
+108
View File
@@ -0,0 +1,108 @@
import asyncio
import os
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends
from api.state import state, Status
from config import load as load_config
from output import list_transcripts
router = APIRouter()
_ws_clients: list[WebSocket] = []
# ---------------------------------------------------------------------------
# Auth stub — replaced by auth.py in Task 13.
# current_user() returns a dict with at least {"username": str, "output_dir": str}.
# Until auth is wired up, every request runs as an anonymous guest using the
# global output path from config.
# ---------------------------------------------------------------------------
def _guest_user() -> dict:
cfg = load_config()
return {"username": "guest", "output_dir": cfg["output"]["path"]}
def current_user(user: dict = Depends(_guest_user)) -> dict:
return user
# ---------------------------------------------------------------------------
# Endpoints
# ---------------------------------------------------------------------------
@router.get("/status")
async def get_status():
return {"status": state.status}
@router.post("/toggle")
async def toggle_recording(user: dict = Depends(current_user)):
from api.pipeline import run_pipeline
if state.status == Status.RECORDING:
asyncio.create_task(run_pipeline())
return {"action": "stopped"}
if state.status == Status.IDLE:
from audio import AudioRecorder
state._recorder = AudioRecorder()
state._recorder.start()
state.recording_user = user["username"]
state._recording_output_dir = user["output_dir"]
state._recording_instructions = user.get("instructions", "")
await state.set_status(Status.RECORDING)
return {"action": "started"}
return {"action": "busy", "status": state.status}
@router.post("/instructions")
async def set_instructions(body: dict, user: dict = Depends(current_user)):
# Instructions are per-user; stored on state only for the active recording.
# Full per-user persistence comes in Task 13.
user["instructions"] = body.get("instructions", "")
return {"ok": True}
@router.get("/transcripts")
async def get_transcripts(user: dict = Depends(current_user)):
return list_transcripts(user["output_dir"])
@router.get("/config")
async def get_config():
return load_config()
@router.put("/config")
async def put_config(body: dict):
cfg = load_config()
cfg.update(body)
return cfg
@router.post("/open")
async def open_file(body: dict):
import subprocess
path = body.get("path", "")
if path and os.path.exists(path):
subprocess.Popen(["xdg-open", path])
return {"ok": True}
@router.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
await ws.accept()
_ws_clients.append(ws)
try:
while True:
await ws.receive_text()
except WebSocketDisconnect:
if ws in _ws_clients:
_ws_clients.remove(ws)
async def broadcast(message: dict):
for ws in list(_ws_clients):
try:
await ws.send_json(message)
except Exception:
if ws in _ws_clients:
_ws_clients.remove(ws)