React + TypeScript + Tailwind single-page application.
Source: web/frontend/src/
| Technology | Purpose |
|---|---|
| React 18 | UI framework |
| TypeScript | Type safety |
| React Router | Client-side routing |
| Axios | HTTP client |
| Tailwind CSS | Styling |
| Vite | Build tool + dev server |
| Path | Page | Description |
|---|---|---|
/ |
Dashboard Page | Job list with filtering |
/jobs/new |
NewJob Page | Upload + configure + submit |
/jobs/:id |
JobDetail Page | Live progress + download |
/jobs/:id/read |
Reader Page | Full-screen browser reader for completed highlight/lip-sync jobs |
/, /jobs/new, and /jobs/:id are wrapped in a Layout component (Navbar + content area).
/jobs/:id/read renders outside that layout as a dedicated full-screen reading view.
┌────────────────────────────────────────────┐
│ Pages │
│ Dashboard Page NewJob Page JobDetail Page Reader Page│
├────────────────────────────────────────────┤
│ Components │
│ FileUploader VoiceSettings │
│ LipsyncSettings LipsyncPreviewFrame │
│ LipsyncRunPanel VideoSettings │
│ PipelineSelector ProgressBar Component│
│ JobCard Navbar │
├────────────────────────────────────────────┤
│ Hooks │
│ useJobProgress Hook │
├────────────────────────────────────────────┤
│ API Client Layer │
│ API Client Jobs API Uploads API │
│ Voices API Reader API │
├────────────────────────────────────────────┤
│ Types │
│ types/index.ts │
└────────────────────────────────────────────┘
NewJob Page
├── FileUploader → Uploads API → POST /api/uploads
├── PipelineSelector (lipsync by default, highlight, or visualization)
├── VoiceSettings / LipsyncSettings → config
├── VideoSettings → config
└── Submit → Jobs API → POST /api/jobs
└── navigate to /jobs/:id
JobDetail Page
└── useJobProgress Hook
└── EventSource → GET /api/jobs/:id/events (SSE)
├── updates ProgressBar Component
└── updates LipsyncRunPanel page timings
For active lip-sync jobs, LipsyncRunPanel requires a second confirmation before Jobs API.stopJob() sends the stop request. Cancelling the confirmation keeps the job running.
JobDetail Page
└── Reader API.getReaderStatus()
└── navigate to /jobs/:id/read
└── Reader Page
├── Reader API.getReaderManifest()
├── Reader API.getReaderAudioUrl()
├── Reader API.getReaderPresenterUrl()
├── Reader API.getReaderPageUrl()
├── Jobs API offline-reader download
└── Jobs API MP4 and EPUB export helpers
Dashboard Page
└── Jobs API.listJobs() (every 5s)
└── renders JobCard grid