- Replace original Subgen README with TranscriptorIO documentation - Add docs/API.md with 45+ REST endpoint documentation - Add docs/ARCHITECTURE.md with backend component details - Add docs/FRONTEND.md with Vue 3 frontend structure - Add docs/CONFIGURATION.md with settings system documentation - Remove outdated backend/README.md
14 KiB
TranscriptorIO Frontend
Technical documentation for the Vue 3 frontend application.
Table of Contents
- Overview
- Technology Stack
- Directory Structure
- Development Setup
- Views
- Components
- State Management
- API Service
- Routing
- Styling
- Build and Deployment
Overview
The TranscriptorIO frontend is a Single Page Application (SPA) built with Vue 3, featuring:
- 6 Complete Views: Dashboard, Queue, Scanner, Rules, Workers, Settings
- Real-time Updates: Polling-based status updates
- Dark Theme: Tdarr-inspired dark UI
- Type Safety: Full TypeScript support
- State Management: Pinia stores for shared state
Technology Stack
| Technology | Version | Purpose |
|---|---|---|
| Vue.js | 3.4+ | UI Framework |
| Vue Router | 4.2+ | Client-side routing |
| Pinia | 2.1+ | State management |
| Axios | 1.6+ | HTTP client |
| TypeScript | 5.3+ | Type safety |
| Vite | 5.0+ | Build tool / dev server |
Directory Structure
frontend/
├── public/ # Static assets (favicon, etc.)
├── src/
│ ├── main.ts # Application entry point
│ ├── App.vue # Root component + navigation
│ │
│ ├── views/ # Page components (routed)
│ │ ├── DashboardView.vue # System overview + resources
│ │ ├── QueueView.vue # Job management
│ │ ├── ScannerView.vue # Scanner control
│ │ ├── RulesView.vue # Scan rules CRUD
│ │ ├── WorkersView.vue # Worker pool management
│ │ └── SettingsView.vue # Settings management
│ │
│ ├── components/ # Reusable components
│ │ ├── ConnectionWarning.vue # Backend connection status
│ │ ├── PathBrowser.vue # Filesystem browser modal
│ │ └── SetupWizard.vue # First-run setup wizard
│ │
│ ├── stores/ # Pinia state stores
│ │ ├── config.ts # Configuration store
│ │ ├── system.ts # System status store
│ │ ├── workers.ts # Workers store
│ │ └── jobs.ts # Jobs store
│ │
│ ├── services/
│ │ └── api.ts # Axios API client
│ │
│ ├── router/
│ │ └── index.ts # Vue Router configuration
│ │
│ ├── types/
│ │ └── api.ts # TypeScript interfaces
│ │
│ └── assets/
│ └── css/
│ └── main.css # Global styles (dark theme)
│
├── index.html # HTML template
├── vite.config.ts # Vite configuration
├── tsconfig.json # TypeScript configuration
└── package.json # Dependencies
Development Setup
Prerequisites
- Node.js 18+ and npm
- Backend server running on port 8000
Installation
cd frontend
# Install dependencies
npm install
# Start development server (with proxy to backend)
npm run dev
Development URLs
| URL | Description |
|---|---|
| http://localhost:3000 | Frontend dev server |
| http://localhost:8000 | Backend API |
| http://localhost:8000/docs | Swagger API docs |
Scripts
npm run dev # Start dev server with HMR
npm run build # Build for production
npm run preview # Preview production build
npm run lint # Run ESLint
Views
DashboardView
Path: /
System overview with real-time resource monitoring.
Features:
- System status (running/stopped)
- CPU usage gauge
- RAM usage gauge
- GPU usage gauges (per device)
- Recent jobs list
- Worker pool summary
- Scanner status
Data Sources:
GET /api/statusGET /api/system/resourcesGET /api/jobs?page_size=10
QueueView
Path: /queue
Job queue management with filtering and pagination.
Features:
- Job list with status icons
- Status filter (All/Queued/Processing/Completed/Failed)
- Pagination controls
- Retry failed jobs
- Cancel queued/processing jobs
- Clear completed jobs
- Job progress display
- Processing time display
Data Sources:
GET /api/jobsGET /api/jobs/statsPOST /api/jobs/{id}/retryDELETE /api/jobs/{id}POST /api/jobs/queue/clear
ScannerView
Path: /scanner
Library scanner control and configuration.
Features:
- Scanner status display
- Start/stop scheduler
- Start/stop file watcher
- Manual scan trigger
- Scan results display
- Next scan time
- Total files scanned counter
Data Sources:
GET /api/scanner/statusPOST /api/scanner/scanPOST /api/scanner/scheduler/startPOST /api/scanner/scheduler/stopPOST /api/scanner/watcher/startPOST /api/scanner/watcher/stop
RulesView
Path: /rules
Scan rules CRUD management.
Features:
- Rules list with priority ordering
- Create new rule (modal)
- Edit existing rule (modal)
- Delete rule (with confirmation)
- Toggle rule enabled/disabled
- Condition configuration
- Action configuration
Data Sources:
GET /api/scan-rulesPOST /api/scan-rulesPUT /api/scan-rules/{id}DELETE /api/scan-rules/{id}POST /api/scan-rules/{id}/toggle
WorkersView
Path: /workers
Worker pool management.
Features:
- Worker list with status
- Add CPU worker
- Add GPU worker (with device selection)
- Remove worker
- Start/stop pool
- Worker statistics
- Current job display per worker
- Progress and ETA display
Data Sources:
GET /api/workersGET /api/workers/statsPOST /api/workersDELETE /api/workers/{id}POST /api/workers/pool/startPOST /api/workers/pool/stop
SettingsView
Path: /settings
Database-backed settings management.
Features:
- Settings grouped by category
- Category tabs (General, Workers, Transcription, Scanner, Bazarr)
- Edit settings in-place
- Save changes button
- Change detection (unsaved changes warning)
- Setting descriptions
Data Sources:
GET /api/settingsPUT /api/settings/{key}POST /api/settings/bulk-update
Components
ConnectionWarning
Displays warning banner when backend is unreachable.
Props: None
State: Uses systemStore.isConnected
PathBrowser
Modal component for browsing filesystem paths.
Props:
show: boolean- Show/hide modalinitialPath: string- Starting path
Emits:
select(path: string)- Path selectedclose()- Modal closed
API Calls:
GET /api/filesystem/browse?path={path}GET /api/filesystem/common-paths
SetupWizard
First-run setup wizard component.
Props: None
Features:
- Mode selection (Standalone/Bazarr)
- Library path configuration
- Scan rule creation
- Worker configuration
- Scanner interval setting
API Calls:
GET /api/setup/statusPOST /api/setup/standalonePOST /api/setup/bazarr-slavePOST /api/setup/skip
State Management
Pinia Stores
systemStore (stores/system.ts)
Global system state.
interface SystemState {
isConnected: boolean
status: SystemStatus | null
resources: SystemResources | null
loading: boolean
error: string | null
}
// Actions
fetchStatus() // Fetch /api/status
fetchResources() // Fetch /api/system/resources
startPolling() // Start auto-refresh
stopPolling() // Stop auto-refresh
workersStore (stores/workers.ts)
Worker pool state.
interface WorkersState {
workers: Worker[]
stats: WorkerStats | null
loading: boolean
error: string | null
}
// Actions
fetchWorkers() // Fetch all workers
fetchStats() // Fetch pool stats
addWorker(type, deviceId?) // Add worker
removeWorker(id) // Remove worker
startPool(cpuCount, gpuCount) // Start pool
stopPool() // Stop pool
jobsStore (stores/jobs.ts)
Job queue state.
interface JobsState {
jobs: Job[]
stats: QueueStats | null
total: number
page: number
pageSize: number
statusFilter: string | null
loading: boolean
error: string | null
}
// Actions
fetchJobs() // Fetch with current filters
fetchStats() // Fetch queue stats
retryJob(id) // Retry failed job
cancelJob(id) // Cancel job
clearCompleted() // Clear completed jobs
setStatusFilter(status) // Update filter
setPage(page) // Change page
configStore (stores/config.ts)
Settings configuration state.
interface ConfigState {
settings: Setting[]
loading: boolean
error: string | null
pendingChanges: Record<string, string>
}
// Actions
fetchSettings(category?) // Fetch settings
updateSetting(key, value) // Queue update
saveChanges() // Save all pending
discardChanges() // Discard pending
API Service
Configuration (services/api.ts)
import axios from 'axios'
const api = axios.create({
baseURL: '/api',
timeout: 30000,
headers: {
'Content-Type': 'application/json'
}
})
// Response interceptor for error handling
api.interceptors.response.use(
response => response,
error => {
console.error('API Error:', error)
return Promise.reject(error)
}
)
export default api
Usage Example
import api from '@/services/api'
// GET request
const response = await api.get('/jobs', {
params: { status_filter: 'queued', page: 1 }
})
// POST request
const job = await api.post('/jobs', {
file_path: '/media/video.mkv',
target_lang: 'spa'
})
// PUT request
await api.put('/settings/worker_cpu_count', {
value: '2'
})
// DELETE request
await api.delete(`/jobs/${jobId}`)
Routing
Route Configuration
const routes = [
{ path: '/', name: 'Dashboard', component: DashboardView },
{ path: '/workers', name: 'Workers', component: WorkersView },
{ path: '/queue', name: 'Queue', component: QueueView },
{ path: '/scanner', name: 'Scanner', component: ScannerView },
{ path: '/rules', name: 'Rules', component: RulesView },
{ path: '/settings', name: 'Settings', component: SettingsView }
]
Navigation
Navigation is handled in App.vue with a sidebar menu.
<nav class="sidebar">
<router-link to="/">Dashboard</router-link>
<router-link to="/workers">Workers</router-link>
<router-link to="/queue">Queue</router-link>
<router-link to="/scanner">Scanner</router-link>
<router-link to="/rules">Rules</router-link>
<router-link to="/settings">Settings</router-link>
</nav>
<main class="content">
<router-view />
</main>
Styling
Dark Theme
The application uses a Tdarr-inspired dark theme defined in assets/css/main.css.
Color Palette:
| Variable | Value | Usage |
|---|---|---|
| --bg-primary | #1a1a2e | Main background |
| --bg-secondary | #16213e | Card background |
| --bg-tertiary | #0f3460 | Hover states |
| --text-primary | #eaeaea | Primary text |
| --text-secondary | #a0a0a0 | Secondary text |
| --accent-primary | #e94560 | Buttons, links |
| --accent-success | #4ade80 | Success states |
| --accent-warning | #fbbf24 | Warning states |
| --accent-error | #ef4444 | Error states |
Component Styling
Components use scoped CSS with CSS variables:
<style scoped>
.card {
background: var(--bg-secondary);
border-radius: 8px;
padding: 1.5rem;
}
.btn-primary {
background: var(--accent-primary);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
}
.btn-primary:hover {
opacity: 0.9;
}
</style>
Build and Deployment
Production Build
cd frontend
npm run build
This creates a dist/ folder with:
index.html- Entry HTMLassets/- JS, CSS bundles (hashed filenames)
Deployment Options
Option 1: Served by Backend (Recommended)
The FastAPI backend automatically serves the frontend from frontend/dist/:
# backend/app.py
frontend_path = Path(__file__).parent.parent / "frontend" / "dist"
if frontend_path.exists():
app.mount("/assets", StaticFiles(directory=str(frontend_path / "assets")))
@app.get("/{full_path:path}")
async def serve_frontend(full_path: str = ""):
return FileResponse(str(frontend_path / "index.html"))
Access: http://localhost:8000
Option 2: Nginx Reverse Proxy
server {
listen 80;
server_name transcriptorio.local;
# Frontend
location / {
root /var/www/transcriptorio/frontend/dist;
try_files $uri $uri/ /index.html;
}
# Backend API
location /api {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Option 3: Docker
# Build frontend
FROM node:18-alpine AS frontend-builder
WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ ./
RUN npm run build
# Final image
FROM python:3.12-slim
COPY --from=frontend-builder /app/frontend/dist /app/frontend/dist
# ... rest of backend setup
TypeScript Interfaces
Key Types (types/api.ts)
// Job
interface Job {
id: string
file_path: string
file_name: string
status: 'queued' | 'processing' | 'completed' | 'failed' | 'cancelled'
priority: number
progress: number
// ... more fields
}
// Worker
interface Worker {
worker_id: string
worker_type: 'cpu' | 'gpu'
device_id: number | null
status: 'idle' | 'busy' | 'stopped' | 'error'
current_job_id: string | null
jobs_completed: number
jobs_failed: number
}
// Setting
interface Setting {
id: number
key: string
value: string | null
description: string | null
category: string | null
value_type: string | null
}
// ScanRule
interface ScanRule {
id: number
name: string
enabled: boolean
priority: number
conditions: ScanRuleConditions
action: ScanRuleAction
}