From ca3409ebeffa562e566ff2bbf69742f3d01ce5ff Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Sun, 25 Jan 2026 02:54:31 -0800 Subject: [PATCH] Update dependencies and enhance project structure. Upgraded Tailwind CSS to version 4.1.0, removed obsolete configuration files, and added a comprehensive CURRENT_STATE.md file detailing project features, architecture, and future plans. Improved history management in the backend with new response models and optimized API endpoints for better data handling. --- CURRENT_STATE.md | 447 +++++++++++++++++++++++++++++++++++++++++ app/index.html | 2 +- app/package.json | 3 +- app/postcss.config.js | 6 - app/src/index.css | 153 ++++++++------ app/tailwind.config.js | 57 ------ app/vite.config.ts | 3 +- backend/history.py | 41 +++- backend/main.py | 37 +++- backend/models.py | 22 ++ bun.lock | 95 +-------- tauri/index.html | 2 +- tauri/src/main.tsx | 1 + tauri/vite.config.ts | 4 + 14 files changed, 636 insertions(+), 237 deletions(-) create mode 100644 CURRENT_STATE.md delete mode 100644 app/postcss.config.js delete mode 100644 app/tailwind.config.js diff --git a/CURRENT_STATE.md b/CURRENT_STATE.md new file mode 100644 index 00000000..0a885860 --- /dev/null +++ b/CURRENT_STATE.md @@ -0,0 +1,447 @@ +# voicebox - Current State Overview + +**Last Updated:** January 25, 2026 +**Status:** โœ… MVP Core Features Working - Voice generation from Tauri app successful! + +--- + +## ๐ŸŽฏ What We Have + +### โœ… **Fully Implemented & Working** + +#### **Backend (Python FastAPI)** +- **Voice Profile Management** + - Create, read, update, delete profiles + - Add multiple audio samples per profile + - Multi-reference voice combination (combines multiple samples) + - Profile storage in SQLite + file system (`data/profiles/`) + +- **Voice Generation** + - Qwen3-TTS model integration (1.7B and 0.6B support) + - Automatic model downloading from HuggingFace Hub + - Voice prompt caching for instant re-generation + - Support for English and Chinese + - Seed-based reproducibility + - GPU/CPU/MPS device detection + +- **Generation History** + - Full CRUD operations + - Search by text content + - Filter by profile + - Pagination support + - Statistics endpoint + - Audio file storage (`data/generations/`) + +- **Audio Transcription** + - Whisper integration for speech-to-text + - Language detection/selection + - Used for reference text extraction from samples + +- **Database** + - SQLite with SQLAlchemy ORM + - Tables: `profiles`, `profile_samples`, `generations`, `projects` (ready for future) + - Automatic schema initialization + +- **API Endpoints** + - RESTful API with FastAPI + - OpenAPI schema generation + - CORS enabled + - Health check endpoint + - File serving for audio files + +#### **Frontend (React + TypeScript + Tauri)** +- **Voice Profile UI** + - Profile list with cards + - Create/edit profile dialog + - Upload audio samples with transcription + - Sample management (view/delete) + - Profile detail view + +- **Generation UI** + - Form with profile selection + - Text input (up to 5000 chars) + - Language selection (en/zh) + - Optional seed input + - Loading states and error handling + +- **History UI** + - Table view with pagination + - Search functionality + - Play audio inline + - Download audio files + - Delete generations + +- **Server Settings** + - Connection form (local/remote mode) + - Server status display + - Health check integration + +- **State Management** + - React Query for server state + - Zustand for client state (server URL, connection status) + - Type-safe API client + +- **UI Components** + - shadcn/ui component library + - Tailwind CSS styling + - Responsive design + - Toast notifications + - Form validation with Zod + +#### **Tauri Desktop App** +- **Rust Backend** + - Sidecar management for Python server + - Start/stop server commands + - Remote mode support (0.0.0.0 binding) + - Process lifecycle management + +- **Build System** + - Tauri v2 configuration + - Platform-specific builds + - Dev tools in debug mode + +--- + +## ๐Ÿ—๏ธ Architecture + +### **Project Structure** +``` +voicebox/ +โ”œโ”€โ”€ app/ # Shared React frontend +โ”‚ โ”œโ”€โ”€ src/ +โ”‚ โ”‚ โ”œโ”€โ”€ components/ # React components +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ VoiceProfiles/ โœ… Complete +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Generation/ โœ… Complete +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ History/ โœ… Complete +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ ServerSettings/ โœ… Complete +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ AudioStudio/ ๐Ÿ“ฆ Placeholder (future) +โ”‚ โ”‚ โ”œโ”€โ”€ lib/ +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ api/ # Type-safe API client โœ… +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ hooks/ # React Query hooks โœ… +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ utils/ # Utilities โœ… +โ”‚ โ”‚ โ””โ”€โ”€ stores/ # Zustand stores โœ… +โ”‚ +โ”œโ”€โ”€ backend/ # Python FastAPI server +โ”‚ โ”œโ”€โ”€ main.py # FastAPI app + routes โœ… +โ”‚ โ”œโ”€โ”€ models.py # Pydantic models โœ… +โ”‚ โ”œโ”€โ”€ database.py # SQLAlchemy ORM โœ… +โ”‚ โ”œโ”€โ”€ profiles.py # Profile management โœ… +โ”‚ โ”œโ”€โ”€ history.py # History management โœ… +โ”‚ โ”œโ”€โ”€ tts.py # Qwen3-TTS integration โœ… +โ”‚ โ”œโ”€โ”€ transcribe.py # Whisper integration โœ… +โ”‚ โ”œโ”€โ”€ studio.py # Audio studio (future) +โ”‚ โ””โ”€โ”€ utils/ +โ”‚ โ”œโ”€โ”€ audio.py # Audio processing โœ… +โ”‚ โ”œโ”€โ”€ cache.py # Voice prompt caching โœ… +โ”‚ โ””โ”€โ”€ validation.py # Validation helpers โœ… +โ”‚ +โ”œโ”€โ”€ tauri/ # Tauri desktop wrapper +โ”‚ โ”œโ”€โ”€ src/ # React entry point โœ… +โ”‚ โ””โ”€โ”€ src-tauri/ # Rust backend โœ… +โ”‚ โ””โ”€โ”€ src/main.rs # Sidecar management โœ… +โ”‚ +โ”œโ”€โ”€ data/ # User data directory +โ”‚ โ”œโ”€โ”€ profiles/ # Profile audio samples +โ”‚ โ”œโ”€โ”€ generations/ # Generated audio files +โ”‚ โ”œโ”€โ”€ cache/ # Cached voice prompts +โ”‚ โ””โ”€โ”€ voicebox.db # SQLite database +โ”‚ +โ””โ”€โ”€ scripts/ # Build & generation scripts + โ”œโ”€โ”€ generate-api.sh # OpenAPI client generation + โ””โ”€โ”€ build-server.sh # Python binary build +``` + +### **Data Flow** + +``` +User Action (Tauri App) + โ†“ +React Component (Form Submit) + โ†“ +React Query Hook (useGeneration) + โ†“ +API Client (apiClient.generateSpeech) + โ†“ +HTTP Request โ†’ FastAPI Backend + โ†“ +Backend Route Handler (/generate) + โ†“ +Business Logic: + 1. Get profile from DB + 2. Create voice prompt (with caching) + 3. Generate audio with Qwen3-TTS + 4. Save audio file + 5. Create history entry + โ†“ +Response (GenerationResponse) + โ†“ +React Query Cache Update + โ†“ +UI Refresh (History table updates) +``` + +### **Key Technologies** + +| Layer | Technology | Purpose | +|-------|-----------|---------| +| **Desktop Framework** | Tauri v2 | Native desktop app wrapper | +| **Frontend Framework** | React 18 | UI components | +| **Language** | TypeScript | Type safety | +| **Styling** | Tailwind CSS | Utility-first CSS | +| **UI Components** | shadcn/ui | Component library | +| **State Management** | React Query + Zustand | Server & client state | +| **Form Handling** | React Hook Form + Zod | Form validation | +| **Backend Framework** | FastAPI | Async REST API | +| **Database** | SQLite + SQLAlchemy | Data persistence | +| **ML Models** | Qwen3-TTS + Whisper | Voice cloning + transcription | +| **Audio Processing** | librosa + soundfile | Audio I/O and processing | +| **Package Manager** | Bun | Fast JS/TS package management | +| **Build Tool** | Vite | Frontend bundling | + +--- + +## ๐Ÿ”‘ Key Features & Capabilities + +### **1. Voice Profile System** +- **Multi-sample support**: Add multiple audio samples per profile +- **Automatic combination**: Multiple samples are combined for better quality +- **Voice prompt caching**: Re-use voice prompts for instant re-generation +- **Audio validation**: Ensures samples meet quality requirements + +### **2. Generation Pipeline** +- **Lazy model loading**: Model loads on first use +- **Device detection**: Automatically uses GPU if available +- **Caching layer**: Voice prompts cached by audio hash + text +- **Error handling**: Graceful degradation and clear error messages + +### **3. History & Search** +- **Full-text search**: Search generations by text content +- **Pagination**: Efficient loading of large histories +- **Audio playback**: Inline audio player +- **File management**: Download and delete operations + +### **4. Server/Client Architecture** +- **Local mode**: Backend runs alongside Tauri app +- **Remote mode**: Connect to remote GPU machine +- **One-click server**: Start server from UI +- **Connection management**: Persistent server URL storage + +--- + +## ๐Ÿ“Š Database Schema + +### **Tables** + +```sql +-- Voice Profiles +profiles + - id (PK, UUID) + - name (unique) + - description + - language (en/zh) + - created_at + - updated_at + +-- Profile Samples +profile_samples + - id (PK, UUID) + - profile_id (FK โ†’ profiles.id) + - audio_path + - reference_text + +-- Generations +generations + - id (PK, UUID) + - profile_id (FK โ†’ profiles.id) + - text + - language + - audio_path + - duration (seconds) + - seed (optional) + - created_at + +-- Projects (ready for future) +projects + - id (PK, UUID) + - name + - data (JSON) + - created_at + - updated_at +``` + +--- + +## ๐ŸŽจ UI Components Status + +| Component | Status | Features | +|-----------|--------|----------| +| **ProfileList** | โœ… Complete | List, create, empty state | +| **ProfileCard** | โœ… Complete | Display profile info | +| **ProfileForm** | โœ… Complete | Create/edit dialog | +| **ProfileDetail** | โœ… Complete | View samples, add samples | +| **SampleUpload** | โœ… Complete | File upload + transcription | +| **GenerationForm** | โœ… Complete | Full generation form | +| **HistoryTable** | โœ… Complete | Table, search, pagination, play/download | +| **ConnectionForm** | โœ… Complete | Server URL input | +| **ServerStatus** | โœ… Complete | Health check display | +| **AudioStudio** | ๐Ÿ“ฆ Placeholder | Timeline editor (future) | + +--- + +## ๐Ÿ”Œ API Endpoints + +### **Profiles** +- `POST /profiles` - Create profile +- `GET /profiles` - List all profiles +- `GET /profiles/{id}` - Get profile +- `PUT /profiles/{id}` - Update profile +- `DELETE /profiles/{id}` - Delete profile +- `POST /profiles/{id}/samples` - Add sample +- `GET /profiles/{id}/samples` - List samples +- `DELETE /profiles/samples/{id}` - Delete sample + +### **Generation** +- `POST /generate` - Generate speech + +### **History** +- `GET /history` - List generations (with filters) +- `GET /history/{id}` - Get generation +- `DELETE /history/{id}` - Delete generation +- `GET /history/stats` - Get statistics + +### **Transcription** +- `POST /transcribe` - Transcribe audio + +### **Audio** +- `GET /audio/{id}` - Serve audio file + +### **Health** +- `GET /health` - Health check with model status + +### **Model Management** +- `POST /models/load` - Load TTS model +- `POST /models/unload` - Unload TTS model + +--- + +## ๐Ÿš€ What's Next (Planned Features) + +### **Phase 2: Advanced Features** +- [ ] Multi-reference voice combination UI +- [ ] Batch generation (multiple variations) +- [ ] Advanced audio normalization +- [ ] Export options (MP3, OGG, etc.) +- [ ] M3GAN voice effect + +### **Phase 3: Audio Studio** +- [ ] Timeline-based audio editor +- [ ] Word-level timestamps +- [ ] Project system (save/load sessions) +- [ ] Audio effects and filters +- [ ] Multi-track editing + +### **Phase 4: Voice Design** +- [ ] Text-to-voice (no reference needed) +- [ ] Preset voices with style control +- [ ] Conversation mode (multi-speaker) +- [ ] Custom audio effects library + +--- + +## ๐Ÿ“ Code Quality Standards + +- โœ… **Type safety**: TypeScript strict mode, Pydantic models +- โœ… **Modular architecture**: No files over 500 lines +- โœ… **Error handling**: Comprehensive error messages +- โœ… **Caching**: Voice prompt caching for performance +- โœ… **Database**: SQLAlchemy ORM with proper relationships +- โœ… **API design**: RESTful with OpenAPI schema +- โœ… **UI/UX**: Responsive, accessible, loading states + +--- + +## ๐Ÿงช Testing Status + +- โœ… **Manual testing**: Voice generation working end-to-end +- ๐Ÿ“ฆ **Unit tests**: Not yet implemented +- ๐Ÿ“ฆ **Integration tests**: Not yet implemented +- ๐Ÿ“ฆ **E2E tests**: Not yet implemented + +--- + +## ๐Ÿ“ฆ Dependencies + +### **Backend** +- FastAPI - Web framework +- SQLAlchemy - ORM +- Pydantic - Validation +- Qwen3-TTS - Voice cloning model +- Whisper - Speech recognition +- librosa - Audio processing +- soundfile - Audio I/O +- PyTorch - ML framework + +### **Frontend** +- React 18 - UI framework +- TypeScript - Type safety +- React Query - Server state +- Zustand - Client state +- React Hook Form - Forms +- Zod - Schema validation +- Tailwind CSS - Styling +- shadcn/ui - Components +- Lucide React - Icons + +### **Desktop** +- Tauri v2 - Desktop framework +- Rust - System backend + +--- + +## ๐ŸŽฏ Current Capabilities Summary + +โœ… **Working End-to-End:** +1. Create voice profiles with audio samples +2. Generate speech from text using cloned voices +3. View and manage generation history +4. Play and download generated audio +5. Search and filter history +6. Connect to local or remote backend +7. Automatic model downloading +8. Voice prompt caching for speed + +๐ŸŽ‰ **You just successfully generated voice from the Tauri app!** + +--- + +## ๐Ÿ” Key Files Reference + +### **Backend Core** +- `backend/main.py` - FastAPI app and routes +- `backend/tts.py` - Qwen3-TTS model wrapper +- `backend/profiles.py` - Profile business logic +- `backend/history.py` - History business logic +- `backend/database.py` - Database models + +### **Frontend Core** +- `app/src/App.tsx` - Main app component +- `app/src/lib/api/client.ts` - API client +- `app/src/lib/hooks/` - React Query hooks +- `app/src/stores/` - Zustand stores + +### **Tauri** +- `tauri/src-tauri/src/main.rs` - Rust backend +- `tauri/src/main.tsx` - React entry point + +--- + +## ๐Ÿ’ก Development Workflow + +1. **Start backend**: `bun run dev:backend` (or via Tauri) +2. **Start frontend**: `bun run dev` (Tauri) or `bun run dev:web` (web) +3. **Generate API client**: `bun run generate:api` (after backend changes) +4. **Build server binary**: `bun run build:server` (for Tauri bundling) + +--- + +**Ready to build more features! ๐Ÿš€** diff --git a/app/index.html b/app/index.html index 1ae976ad..c7a4be9f 100644 --- a/app/index.html +++ b/app/index.html @@ -1,5 +1,5 @@ - + diff --git a/app/package.json b/app/package.json index b18c6ef6..dbba8d6d 100644 --- a/app/package.json +++ b/app/package.json @@ -46,8 +46,7 @@ "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.0", - "tailwindcss": "^3.4.0", - "tailwindcss-animate": "^1.0.7", + "tailwindcss": "^4.1.0", "typescript": "^5.6.0", "vite": "^5.4.0" } diff --git a/app/postcss.config.js b/app/postcss.config.js deleted file mode 100644 index 2aa7205d..00000000 --- a/app/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/app/src/index.css b/app/src/index.css index 9214d186..aaf31c1f 100644 --- a/app/src/index.css +++ b/app/src/index.css @@ -1,68 +1,105 @@ -@import "tailwindcss"; +@import "tailwindcss" source("."); -@layer base { - :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; - --radius: 0.5rem; - --chart-1: 12 76% 61%; - --chart-2: 173 58% 39%; - --chart-3: 197 37% 24%; - --chart-4: 43 74% 66%; - --chart-5: 27 87% 67%; - } +@theme { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); - .dark { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; - --chart-1: 220 70% 50%; - --chart-2: 160 60% 45%; - --chart-3: 30 80% 55%; - --chart-4: 280 65% 60%; - --chart-5: 340 75% 55%; - } + --color-background: hsl(var(--background)); + --color-foreground: hsl(var(--foreground)); + + --color-card: hsl(var(--card)); + --color-card-foreground: hsl(var(--card-foreground)); + + --color-popover: hsl(var(--popover)); + --color-popover-foreground: hsl(var(--popover-foreground)); + + --color-primary: hsl(var(--primary)); + --color-primary-foreground: hsl(var(--primary-foreground)); + + --color-secondary: hsl(var(--secondary)); + --color-secondary-foreground: hsl(var(--secondary-foreground)); + + --color-muted: hsl(var(--muted)); + --color-muted-foreground: hsl(var(--muted-foreground)); + + --color-accent: hsl(var(--accent)); + --color-accent-foreground: hsl(var(--accent-foreground)); + + --color-destructive: hsl(var(--destructive)); + --color-destructive-foreground: hsl(var(--destructive-foreground)); + + --color-border: hsl(var(--border)); + --color-input: hsl(var(--input)); + --color-ring: hsl(var(--ring)); + + --color-chart-1: hsl(var(--chart-1)); + --color-chart-2: hsl(var(--chart-2)); + --color-chart-3: hsl(var(--chart-3)); + --color-chart-4: hsl(var(--chart-4)); + --color-chart-5: hsl(var(--chart-5)); +} + +:root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 222.2 84% 4.9%; + --radius: 0.5rem; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; +} + +.dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 11.2%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; } @layer base { * { - border-color: hsl(var(--border)); + @apply border-border; } body { - background-color: hsl(var(--background)); - color: hsl(var(--foreground)); + @apply bg-background text-foreground; } } diff --git a/app/tailwind.config.js b/app/tailwind.config.js deleted file mode 100644 index 550bd074..00000000 --- a/app/tailwind.config.js +++ /dev/null @@ -1,57 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -export default { - darkMode: ['class'], - content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], - theme: { - extend: { - borderRadius: { - lg: 'var(--radius)', - md: 'calc(var(--radius) - 2px)', - sm: 'calc(var(--radius) - 4px)', - }, - colors: { - background: 'hsl(var(--background))', - foreground: 'hsl(var(--foreground))', - card: { - DEFAULT: 'hsl(var(--card))', - foreground: 'hsl(var(--card-foreground))', - }, - popover: { - DEFAULT: 'hsl(var(--popover))', - foreground: 'hsl(var(--popover-foreground))', - }, - primary: { - DEFAULT: 'hsl(var(--primary))', - foreground: 'hsl(var(--primary-foreground))', - }, - secondary: { - DEFAULT: 'hsl(var(--secondary))', - foreground: 'hsl(var(--secondary-foreground))', - }, - muted: { - DEFAULT: 'hsl(var(--muted))', - foreground: 'hsl(var(--muted-foreground))', - }, - accent: { - DEFAULT: 'hsl(var(--accent))', - foreground: 'hsl(var(--accent-foreground))', - }, - destructive: { - DEFAULT: 'hsl(var(--destructive))', - foreground: 'hsl(var(--destructive-foreground))', - }, - border: 'hsl(var(--border))', - input: 'hsl(var(--input))', - ring: 'hsl(var(--ring))', - chart: { - 1: 'hsl(var(--chart-1))', - 2: 'hsl(var(--chart-2))', - 3: 'hsl(var(--chart-3))', - 4: 'hsl(var(--chart-4))', - 5: 'hsl(var(--chart-5))', - }, - }, - }, - }, - plugins: [require('tailwindcss-animate')], -}; diff --git a/app/vite.config.ts b/app/vite.config.ts index a9f82e1d..69105abc 100644 --- a/app/vite.config.ts +++ b/app/vite.config.ts @@ -1,9 +1,10 @@ import path from 'node:path'; +import tailwindcss from '@tailwindcss/vite'; import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; export default defineConfig({ - plugins: [react()], + plugins: [tailwindcss(), react()], resolve: { alias: { '@': path.resolve(__dirname, './src'), diff --git a/backend/history.py b/backend/history.py index 38df887b..03c35342 100644 --- a/backend/history.py +++ b/backend/history.py @@ -10,8 +10,8 @@ from pathlib import Path from sqlalchemy.orm import Session from sqlalchemy import or_ -from .models import GenerationRequest, GenerationResponse, HistoryQuery -from .database import Generation as DBGeneration +from .models import GenerationRequest, GenerationResponse, HistoryQuery, HistoryResponse, HistoryListResponse +from .database import Generation as DBGeneration, VoiceProfile as DBVoiceProfile # Generations storage directory @@ -85,7 +85,7 @@ async def get_generation( async def list_generations( query: HistoryQuery, db: Session, -) -> Tuple[List[GenerationResponse], int]: +) -> HistoryListResponse: """ List generations with optional filters. @@ -94,10 +94,16 @@ async def list_generations( db: Database session Returns: - Tuple of (generations, total_count) + HistoryListResponse with items and total count """ - # Build base query - q = db.query(DBGeneration) + # Build base query with join to get profile name + q = db.query( + DBGeneration, + DBVoiceProfile.name.label('profile_name') + ).join( + DBVoiceProfile, + DBGeneration.profile_id == DBVoiceProfile.id + ) # Apply profile filter if query.profile_id: @@ -118,11 +124,26 @@ async def list_generations( q = q.offset(query.offset).limit(query.limit) # Execute query - generations = q.all() + results = q.all() - return ( - [GenerationResponse.model_validate(g) for g in generations], - total_count, + # Convert to HistoryResponse with profile_name + items = [] + for generation, profile_name in results: + items.append(HistoryResponse( + id=generation.id, + profile_id=generation.profile_id, + profile_name=profile_name, + text=generation.text, + language=generation.language, + audio_path=generation.audio_path, + duration=generation.duration, + seed=generation.seed, + created_at=generation.created_at, + )) + + return HistoryListResponse( + items=items, + total=total_count, ) diff --git a/backend/main.py b/backend/main.py index eed019d9..1a624c35 100644 --- a/backend/main.py +++ b/backend/main.py @@ -18,7 +18,7 @@ from pathlib import Path import uuid from . import database, models, profiles, history, tts, transcribe -from .database import get_db, init_db +from .database import get_db, init_db, Generation as DBGeneration, VoiceProfile as DBVoiceProfile # Initialize database init_db() @@ -240,7 +240,7 @@ async def generate_speech( # HISTORY ENDPOINTS # ============================================ -@app.get("/history", response_model=List[models.GenerationResponse]) +@app.get("/history", response_model=models.HistoryListResponse) async def list_history( profile_id: Optional[str] = None, search: Optional[str] = None, @@ -255,20 +255,41 @@ async def list_history( limit=limit, offset=offset, ) - generations, total = await history.list_generations(query, db) - return generations + return await history.list_generations(query, db) -@app.get("/history/{generation_id}", response_model=models.GenerationResponse) +@app.get("/history/{generation_id}", response_model=models.HistoryResponse) async def get_generation( generation_id: str, db: Session = Depends(get_db), ): """Get a generation by ID.""" - generation = await history.get_generation(generation_id, db) - if not generation: + # Get generation with profile name + result = db.query( + DBGeneration, + DBVoiceProfile.name.label('profile_name') + ).join( + DBVoiceProfile, + DBGeneration.profile_id == DBVoiceProfile.id + ).filter( + DBGeneration.id == generation_id + ).first() + + if not result: raise HTTPException(status_code=404, detail="Generation not found") - return generation + + gen, profile_name = result + return models.HistoryResponse( + id=gen.id, + profile_id=gen.profile_id, + profile_name=profile_name, + text=gen.text, + language=gen.language, + audio_path=gen.audio_path, + duration=gen.duration, + seed=gen.seed, + created_at=gen.created_at, + ) @app.delete("/history/{generation_id}") diff --git a/backend/models.py b/backend/models.py index 6dd1f9b5..7bd48ddb 100644 --- a/backend/models.py +++ b/backend/models.py @@ -74,6 +74,28 @@ class HistoryQuery(BaseModel): offset: int = Field(default=0, ge=0) +class HistoryResponse(BaseModel): + """Response model for history entry (includes profile name).""" + id: str + profile_id: str + profile_name: str + text: str + language: str + audio_path: str + duration: float + seed: Optional[int] + created_at: datetime + + class Config: + from_attributes = True + + +class HistoryListResponse(BaseModel): + """Response model for history list.""" + items: List[HistoryResponse] + total: int + + class TranscriptionRequest(BaseModel): """Request model for audio transcription.""" language: Optional[str] = Field(None, pattern="^(en|zh)$") diff --git a/bun.lock b/bun.lock index 73ff8215..8326aeb0 100644 --- a/bun.lock +++ b/bun.lock @@ -48,8 +48,7 @@ "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.0", - "tailwindcss": "^3.4.0", - "tailwindcss-animate": "^1.0.7", + "tailwindcss": "^4.1.0", "typescript": "^5.6.0", "vite": "^5.4.0", }, @@ -98,8 +97,6 @@ }, }, "packages": { - "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], - "@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], "@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="], @@ -496,12 +493,6 @@ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], - - "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], - - "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], @@ -512,8 +503,6 @@ "baseline-browser-mapping": ["baseline-browser-mapping@2.9.18", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA=="], - "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], - "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], @@ -522,14 +511,10 @@ "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], - "caniuse-lite": ["caniuse-lite@1.0.30001766", "", {}, "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA=="], "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], - "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], @@ -538,16 +523,12 @@ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], "date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="], @@ -560,12 +541,8 @@ "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], - "didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="], - "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], - "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], - "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], "electron-to-chromium": ["electron-to-chromium@1.5.278", "", {}, "sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw=="], @@ -608,8 +585,6 @@ "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], - "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - "file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="], "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], @@ -624,8 +599,6 @@ "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], @@ -644,8 +617,6 @@ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], @@ -656,10 +627,6 @@ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], - - "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], @@ -670,7 +637,7 @@ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - "jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="], + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], @@ -714,10 +681,6 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], - "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], - - "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], - "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], @@ -738,20 +701,12 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], - "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], - - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - - "object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], - "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], @@ -768,32 +723,14 @@ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], - "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="], - - "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], - "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], - "postcss-import": ["postcss-import@15.1.0", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew=="], - - "postcss-js": ["postcss-js@4.1.0", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw=="], - - "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], - - "postcss-nested": ["postcss-nested@6.2.0", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="], - - "postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], - - "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], @@ -814,12 +751,6 @@ "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], - "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], - - "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - - "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], - "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], @@ -846,12 +777,8 @@ "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - "tailwind-merge": ["tailwind-merge@2.6.0", "", {}, "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA=="], "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="], @@ -862,18 +789,10 @@ "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], - "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], - - "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], - - "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], - "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], "ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="], - "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], @@ -894,8 +813,6 @@ "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], - "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], - "vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="], "wavesurfer.js": ["wavesurfer.js@7.12.1", "", {}, "sha512-NswPjVHxk0Q1F/VMRemCPUzSojjuHHisQrBqQiRXg7MVbe3f5vQ6r0rTTXA/a/neC/4hnOEC4YpXca4LpH0SUg=="], @@ -940,8 +857,6 @@ "@radix-ui/react-separator/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], - "@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], @@ -958,14 +873,8 @@ "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "@voicebox/app/tailwindcss": ["tailwindcss@3.4.19", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="], - - "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - "tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], } } diff --git a/tauri/index.html b/tauri/index.html index d45f05cd..a1bf100a 100644 --- a/tauri/index.html +++ b/tauri/index.html @@ -1,5 +1,5 @@ - + diff --git a/tauri/src/main.tsx b/tauri/src/main.tsx index 24d87eef..a946a7e3 100644 --- a/tauri/src/main.tsx +++ b/tauri/src/main.tsx @@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import App from '@/App'; +// Import CSS from app directory using alias so Tailwind can scan the source files import '@/index.css'; const queryClient = new QueryClient({ diff --git a/tauri/vite.config.ts b/tauri/vite.config.ts index 4288249a..3caa4fbb 100644 --- a/tauri/vite.config.ts +++ b/tauri/vite.config.ts @@ -21,6 +21,10 @@ export default defineConfig({ server: { port: 5173, strictPort: true, + // Watch files in the app directory for changes + watch: { + ignored: ['!**/../app/**'], + }, }, envPrefix: ['VITE_', 'TAURI_'], build: {