A runnable, self-hosted web interface so the whole workflow can be tested end to end: record or upload audio in the browser, watch it transcribe and summarise, browse a library, play back, and export. What changed: - server/app/store.py: SQLite + on-disk audio storage for recordings (stdlib only). - server/app/pipeline.py: background task audio -> transcribe -> summarise via the existing provider layer, updating status (queued/transcribing/summarising/done/error). - server/app/main.py: web API - POST upload, list, detail, audio (Range), delete, and export (txt/md/srt/vtt/json) - and serves the SPA at /. - server/app/web/: Plaud-style single-page UI (index.html, styles.css, app.js). Sidebar library, in-browser recording (MediaRecorder) + file upload, live status polling, audio player, summary (overview/key points/actions), timestamped transcript, exports. - server/Dockerfile + README: two-minute run instructions (default provider: Groq free tier for both Whisper + LLM), and a Docker option. - config: env prefix switched OPENSCRIBE_ -> NIGHTJAR_ to match the brand and the site tutorials; .env.example rewritten with a ready Groq quick-start. - state/TODO: web app recorded as done. Why: - User asked for a Plaud-like web interface to test how it all works. Nothing testable existed before (marketing site is a brochure; pipeline was unwired). This delivers a real, runnable product demo and effectively lands M5/M6 for the HTTP providers. Notes: - Slim by design: AI is offloaded to the configured provider, so no local ML deps needed for the demo. Byte-compiles clean; JS passes node --check. Local faster-whisper still needs its model (M5) for the fully-offline path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
36 lines
1.3 KiB
Python
36 lines
1.3 KiB
Python
# SPDX-License-Identifier: GPL-3.0-only
|
|
"""The processing pipeline: audio -> transcript -> summary, using the provider layer.
|
|
|
|
Run as a background task after upload. Updates the recording's status as it goes so the
|
|
web UI can show progress (queued -> transcribing -> summarising -> done).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from . import store
|
|
from .providers import build_summariser, build_transcriber
|
|
|
|
|
|
def process(rec_id: str) -> None:
|
|
path = store.audio_path(rec_id)
|
|
try:
|
|
store.set_status(rec_id, "transcribing")
|
|
transcript = build_transcriber().transcribe(path)
|
|
|
|
store.set_status(rec_id, "summarising")
|
|
summary = build_summariser().summarise(rec_id, transcript.text)
|
|
|
|
store.set_result(
|
|
rec_id,
|
|
language=transcript.language,
|
|
transcript={
|
|
"text": transcript.text,
|
|
"segments": [s.model_dump() for s in transcript.segments],
|
|
},
|
|
summary={
|
|
"overview": summary.overview,
|
|
"key_points": summary.key_points,
|
|
"action_items": summary.action_items,
|
|
},
|
|
)
|
|
except Exception as exc: # surface any provider/network error to the UI
|
|
store.set_status(rec_id, "error", f"{type(exc).__name__}: {exc}")
|