diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..637bab7 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,82 @@ +# MeetVault — Agent Guide + +## Repo layout + +```text +MeetVault/ +├── Overview/ # Architecture docs, schema, data flow +│ └── README.md +├── Wireframes/ # Desktop client design notes +│ └── Desktop/ +│ ├── Desktop_README.md +│ └── Desktop-Demo/ # Runnable Tauri + React demo +│ └── speech-engine/ # Local Whisper transcription engine +``` + +## Core architecture (remember this) + +- **User machine**: records audio/video, runs local Whisper for STT, sends transcript to backend. Keeps compute and cost on the device. +- **Backend API**: auth, meeting metadata, presigned upload URLs, transcript ingestion, summarization jobs, task extraction, reminder scheduling. +- **PostgreSQL**: structured app data + `processing_jobs` table used as a job queue (no external broker needed for MVP). +- **SeaweedFS**: S3-compatible object storage for large media files. + +## PostgreSQL gotchas + +### Enable UUID generation first + +```sql +CREATE EXTENSION IF NOT EXISTS pgcrypto; +``` + +### Always set `updated_at` manually + +PostgreSQL does not auto-update it. Create a shared trigger function and apply to every table that tracks changes: + +```sql +CREATE OR REPLACE FUNCTION set_updated_at() + RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- then attach per-table triggers, e.g.: +CREATE TRIGGER trg_meetings_updated_at + BEFORE UPDATE ON meetings FOR EACH ROW EXECUTE FUNCTION set_updated_at(); +``` + +### Job queue pattern (no external broker) + +Workers pick jobs with this exact query: + +```sql +SELECT * +FROM processing_jobs +WHERE status = 'queued' + AND available_at <= NOW() +ORDER BY priority DESC, created_at +FOR UPDATE SKIP LOCKED +LIMIT 1; +``` + +Key indexes for workers: + +```sql +CREATE INDEX idx_schedules_due ON meeting_schedules(status, scheduled_at); +CREATE INDEX idx_processing_jobs_queue ON processing_jobs(status, available_at, priority DESC); +``` + +### SeaweedFS object paths + +Files live under: `meeting-storage/users/{user_id}/meetings/{meeting_id}/original/` + +## Testing flow + +1. Run the desktop demo locally (Tauri + React) to verify UI and local transcription without backend dependencies. +2. Spin up PostgreSQL, run the schema SQL from `Overview/README.md`, then start the backend API. +3. End-to-end: record a short clip on the desktop client → upload via presigned URL → ingest transcript → trigger summarization job → verify summary appears in DB. + +## What is intentionally out of scope for MVP + +- Kafka, Elasticsearch, Kubernetes, vector DBs, Redis (unless job volume grows), microservices. PostgreSQL + SeaweedFS + workers is sufficient. diff --git a/Readme.md b/Readme.md index e69de29..1b84428 100644 --- a/Readme.md +++ b/Readme.md @@ -0,0 +1,44 @@ +# MeetVault + +Self-hosted meeting recording and intelligence platform. The user machine handles local transcription; the backend API manages auth, metadata, uploads, summarization jobs, tasks, and reminders. Large media files are stored in SeaweedFS via its S3-compatible API. + +## Architecture + +```text +User machine: + - Record audio/video + - Local Whisper STT → transcript + - Upload to SeaweedFS (presigned URLs) + - Send transcript to backend + +Backend API: + - Auth, users, subscriptions + - Meeting metadata + - Transcript ingestion + - Summarization jobs + - Task extraction + - Reminder scheduling + - PostgreSQL for structured data +``` + +## Quick start + +### Backend + +1. Start PostgreSQL and run the schema SQL from `Overview/README.md` (includes `pgcrypto` extension, all tables, indexes, triggers). +2. Run the backend API server. +3. Spin up SeaweedFS and configure presigned URL endpoints on the backend. + +### Desktop client + +Run the Tauri + React demo in `Wireframes/Desktop/MeetVault-Desktop-Demo/`. It supports local Whisper transcription, speaker diarization via diart, live overlay window, recording capture, and mock speech-engine mode for UI testing without ML models. + +## Stack (MVP) + +Frontend: React / Next.js +Backend: FastAPI or Node.js +Database: PostgreSQL + `processing_jobs` table as job queue +Media storage: SeaweedFS +Local STT: Whisper / whisper.cpp + +No Kafka, Elasticsearch, Kubernetes, vector DBs, or Redis are needed for the MVP. diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/.gitignore b/Wireframes/Desktop/MeetVault-Desktop-Demo/.gitignore new file mode 100644 index 0000000..e4fb597 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +dist/ +src-tauri/target/ +speech-engine/.venv/ +speech-engine/__pycache__/ +.env +*.pyc +.DS_Store diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/PROJECT_SCOPE.md b/Wireframes/Desktop/MeetVault-Desktop-Demo/PROJECT_SCOPE.md new file mode 100644 index 0000000..ab2e235 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/PROJECT_SCOPE.md @@ -0,0 +1,14 @@ +# Demo scope lock + +This file records the requested scope for the current MeetVault desktop demo. + +- No login UI or authentication flow. +- No subscription UI or billing logic. +- Recordings and transcript files remain local. +- Meeting Details must support local video/audio playback. +- Live transcription is local Whisper. +- Speaker identifier means session speaker diarization (`Speaker 1`, `Speaker 2`, etc.), not biometric identity recognition. +- Speaker labels can be renamed live. +- Realtime overlay is included. +- Settings are included and locally persisted. +- Backend actions are placeholders and display exactly: `Processing (The job). Please wait a moment`. diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/README.md b/Wireframes/Desktop/MeetVault-Desktop-Demo/README.md new file mode 100644 index 0000000..c052f11 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/README.md @@ -0,0 +1,421 @@ +# MeetVault Desktop Demo + +This repository is a runnable desktop-demo scaffold based on the supplied MeetVault wireframes and desktop architecture notes. + +## Demo scope + +Included now: + +- Tauri + React + TypeScript desktop UI +- Dashboard +- Meetings list +- Meeting details +- Local video/audio playback +- Live meeting screen +- Real-time Whisper transcription through a local Python speech engine +- Real-time speaker diarization through diart +- `Speaker 1`, `Speaker 2`, ... labels +- Live speaker rename +- Transparent always-on-top overlay window +- Microphone capture +- Optional system-audio/screen capture through the WebView capture API +- Local recording files +- Local transcript data +- Functional Settings screen +- Mock speech-engine mode for UI testing without ML models + +Not included in this demo: + +- User login +- User identification/account verification +- Subscription/payment +- PostgreSQL +- SeaweedFS +- Remote cloud storage +- Real summary/task/reminder backend + +The backend-facing demo state is intentionally: + +```text +Processing (The job). Please wait a moment +``` + +Summary/task/reminder controls show this processing state but do not call a production backend yet. + +--- + +## Architecture + +```text +Desktop UI (Tauri + React) + │ + ├── microphone / optional system audio + screen + │ + ├── MediaRecorder ───────────► local recording.webm + │ + └── 16 kHz PCM + │ + ▼ + Local Speech Engine + Python WebSocket server + │ + ┌──────┴───────┐ + ▼ ▼ + faster-whisper diart + transcription diarization + │ │ + └──────┬───────┘ + ▼ + speaker-aware segments + │ + ┌──────┴──────────┐ + ▼ ▼ + Live transcript Overlay window +``` + +The local speech engine is **not** the future MeetVault backend. It is a local ML side process used by the desktop demo. + +--- + +## Project structure + +```text +MeetVault-Desktop-Demo/ +├── src/ React desktop UI +│ ├── components/ +│ ├── lib/ +│ │ ├── capture.ts microphone/system capture + MediaRecorder +│ │ ├── speechClient.ts local WebSocket speech client +│ │ ├── liveBus.ts main-window ↔ overlay events +│ │ └── localFiles.ts local recording/transcript persistence +│ ├── pages/ +│ │ ├── Dashboard.tsx +│ │ ├── LiveMeeting.tsx +│ │ ├── Meetings.tsx +│ │ ├── MeetingDetails.tsx +│ │ ├── Settings.tsx +│ │ └── Overlay.tsx +│ └── store/ +├── src-tauri/ Tauri 2 desktop shell +├── speech-engine/ local Whisper + diart process +├── docs/ supplied source wireframes/README +└── scripts/ +``` + +--- + +## Prerequisites + +### Desktop UI + +- Node.js 20+ +- Rust toolchain compatible with your installed Tauri 2 release +- Platform-specific Tauri prerequisites + +### Real speech engine + +Recommended: + +- Python 3.10 or 3.11 +- ffmpeg +- PortAudio +- libsndfile +- Python packages in `speech-engine/requirements.txt` +- Hugging Face/pyannote model access required by your diart setup + +For UI-only testing, use mock speech mode instead. + +--- + +## 1. Install frontend dependencies + +```bash +npm install +``` + +--- + +## 2. Test the UI in a browser + +```bash +npm run dev +``` + +Open: + +```text +http://127.0.0.1:1420 +``` + +Browser mode is useful for UI development. Tauri-only functions such as the native overlay window and persistent AppLocalData storage use reduced fallbacks in browser mode. + +--- + +## 3. Install the local speech engine + +Create a Python 3.10/3.11 virtual environment inside `speech-engine`: + +```bash +cd speech-engine +python -m venv .venv +``` + +Windows: + +```powershell +.venv\Scripts\activate +pip install -r requirements.txt +``` + +Linux/macOS: + +```bash +source .venv/bin/activate +pip install -r requirements.txt +``` + +### Real mode + +```bash +python server.py +``` + +### Mock mode + +```bash +python server.py --mock +``` + +The application expects the local engine at: + +```text +ws://127.0.0.1:8765/ws/live +``` + +This can be changed from Settings. + +--- + +## 4. Run as a desktop application + +From the repository root: + +```bash +npm run desktop +``` + +On Windows, convenience scripts are also provided: + +```powershell +scripts\start-demo.ps1 +``` + +or for UI testing with mock speech: + +```powershell +scripts\start-demo-mock.ps1 +``` + +--- + +# Live meeting behavior + +When a meeting starts: + +1. MeetVault requests microphone access. +2. If **Capture system audio / screen** is enabled, it also opens the system screen/audio picker. +3. Audio is mixed locally. +4. The mixed audio is recorded through `MediaRecorder`. +5. The same audio is converted to mono 16 kHz signed PCM. +6. PCM is streamed to `speech-engine/server.py`. +7. Whisper produces timestamped text. +8. diart produces speaker time ranges. +9. The local engine merges the two timelines. +10. The UI receives segments such as: + +```json +{ + "speakerId": "speaker_1", + "start": 18.2, + "end": 21.7, + "text": "We should finish the API before Friday.", + "final": true +} +``` + +11. The user can rename `speaker_1` to `Alice` without changing the underlying diarization ID. +12. The main transcript and overlay immediately display the renamed speaker. + +--- + +# Live overlay + +The Tauri build creates a second transparent window. + +Properties: + +- Always on top +- Transparent +- Resizable +- No decorations +- Hidden from the taskbar +- Shows the latest transcript lines +- Speaker name can be clicked to rename +- Receives the same live transcript event stream as the main window + +The overlay is opened from **Show overlay** on the live meeting screen. + +--- + +# Local files + +There is no cloud storage in this demo. + +In Tauri mode, media is stored below the application's local-data directory: + +```text +meetings/{meeting_uuid}/recording.webm +meetings/{meeting_uuid}/meeting.json +meetings/{meeting_uuid}/transcript.json +``` + +The Meeting Details page can play the stored video/audio file. + +If screen capture is enabled, the recording normally contains video + mixed audio. + +If only microphone capture is enabled, the recording is audio-only. + +--- + +# Video playback + +The Meeting Details screen includes a real HTML5 video/audio player. + +The Tauri asset protocol is scoped only to MeetVault's local `meetings` directory. + +This allows locally recorded media to be played without uploading it anywhere. + +--- + +# Settings + +The Settings screen is functional and persisted locally. + +Current settings include: + +### General + +- Launch at startup preference +- Keep in tray preference +- Global hotkey value +- Theme preference + +The startup/tray/hotkey preferences are stored now; OS registration is intentionally left for the packaging phase. + +### Audio + +- Microphone device ID +- System audio/screen capture toggle +- Microphone level monitoring + +### Transcription + +- Whisper model: `base`, `small`, `medium` +- Compute device: auto/CPU/CUDA +- Language +- Speaker diarization toggle + +### Overlay + +- Enabled +- Opacity +- Font size +- Visible line count + +### Speech engine + +- Real Whisper + diart mode +- Mock mode +- Local WebSocket URL + +--- + +# Backend placeholder + +This demo deliberately has no remote backend implementation. + +Any backend-only action returns/displays: + +```text +Processing (The job). Please wait a moment +``` + +This currently applies to example actions such as: + +- Generate/regenerate summary +- Create reminder +- Action-item processing + +The UI is structured so a real API can replace `src/lib/backend.ts` later. + +--- + +# Current limitations + +## System audio + +System-audio capture depends on the operating system, WebView implementation, selected source, and whether that source permits audio capture. + +Microphone capture is the reliable baseline for the demo. + +## diart model setup + +Real diart execution can require pyannote model terms/authentication. The exact dependency combinations can also be sensitive to Python, PyTorch, torchaudio, and platform versions. + +Use the recommended Python version and validate the speech-engine environment on the target development machine before packaging. + +## Realtime accuracy + +Speaker diarization can temporarily swap speaker labels, especially with: + +- overlapping speakers +- short utterances +- background noise +- similar voices + +The rename function changes the display name but does not perform biometric identity recognition. + +## Long recordings + +The media recorder writes the final Blob when the meeting ends in this initial demo. Before production use, change this to incremental/chunked file writing so multi-hour meetings do not accumulate the entire media Blob in memory. + +--- + +# Recommended next development steps + +1. Validate real Whisper + diart on the target Windows machine. +2. Replace final-Blob recording with incremental local file writes. +3. Add proper microphone device enumeration. +4. Add native Windows loopback audio capture if system-audio reliability is required. +5. Add session crash recovery. +6. Add transcript segment editing/reassignment. +7. Package the Python speech engine as a sidecar or replace it with native whisper.cpp + a native/ONNX diarization runtime. +8. Implement the real backend jobs/API later. +9. Replace local-only media with SeaweedFS when backend/storage work begins. + +--- + +# Demo design decisions from the supplied specification + +```text +Login / subscription REMOVED +Remote backend PLACEHOLDER ONLY +Backend message "Processing (The job). Please wait a moment" +Media storage LOCAL +Video playback INCLUDED +Live transcription INCLUDED +Speaker diarization INCLUDED +Live speaker rename INCLUDED +Realtime overlay INCLUDED +Settings INCLUDED +``` diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/docs/MeetVault_Desktop_README_original.md b/Wireframes/Desktop/MeetVault-Desktop-Demo/docs/MeetVault_Desktop_README_original.md new file mode 100644 index 0000000..9711789 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/docs/MeetVault_Desktop_README_original.md @@ -0,0 +1,1049 @@ +# MeetVault Desktop + +MeetVault Desktop is the local desktop client for MeetVault. + +Its main purpose is to: + +- Record meeting audio/video +- Capture microphone and system audio +- Transcribe speech locally using Whisper +- Detect and separate speakers in real time using diart +- Show a real-time transcription overlay on top of other applications +- Allow users to rename detected speakers during the meeting +- Upload recordings to SeaweedFS +- Send the final transcript to the MeetVault backend +- Receive generated summaries, action items, and reminders from the backend + +The desktop application should continue to work locally during a meeting even if the backend is temporarily unavailable. + +--- + +# Core Desktop Architecture + +```text + MeetVault Desktop + + ┌─────────────────┐ + │ Desktop UI │ + │ React + Tauri │ + └────────┬────────┘ + │ + ▼ + ┌─────────────────────┐ + │ Audio Engine │ + │ │ + │ Microphone capture │ + │ System audio capture│ + │ Audio mixing │ + └─────────┬───────────┘ + │ + ┌───────────────┴───────────────┐ + │ │ + ▼ ▼ + ┌───────────────┐ ┌────────────────┐ + │ Whisper │ │ diart │ + │ │ │ │ + │ transcription │ │ diarization │ + │ + timestamps │ │ speaker labels │ + └───────┬───────┘ └───────┬────────┘ + │ │ + └──────────────┬───────────────┘ + ▼ + ┌────────────────────┐ + │ Segment Merger │ + │ │ + │ text + timestamps │ + │ speaker timeline │ + └─────────┬──────────┘ + │ + ┌─────────────┴──────────────┐ + │ │ + ▼ ▼ + ┌─────────────────┐ ┌──────────────────┐ + │ Live Transcript │ │ Realtime Overlay │ + │ Window │ │ Always-on-top │ + └─────────────────┘ └──────────────────┘ +``` + +--- + +# Recommended Desktop Stack + +```text +Desktop shell +Tauri + +UI +React + TypeScript + +Native/local processing +Rust where required + +Transcription +Whisper / whisper.cpp + +Speaker diarization +diart + +Backend API +MeetVault backend + +Structured data +PostgreSQL + +Media storage +SeaweedFS +``` + +Tauri is recommended because the application needs: + +- Multiple windows +- Transparent overlay windows +- Always-on-top behavior +- Native audio access +- Local file access +- Background processes +- System tray support +- Global shortcuts +- Lower memory usage than a full Electron application + +--- + +# Real-Time Audio Pipeline + +The application processes meeting audio locally. + +```text +Microphone + │ + ├────────────────┐ + │ │ +System Audio │ + │ │ + └──────┬─────────┘ + ▼ + Audio Mixer + │ + ▼ + Audio Stream + │ + ┌─────┴─────┐ + │ │ + ▼ ▼ + Whisper diart + │ │ + │ │ + ▼ ▼ + text + speaker + timestamps segments + │ │ + └─────┬─────┘ + ▼ + Segment Merger + │ + ▼ + Live Transcript Event + │ + ┌─────┴────────┐ + │ │ + ▼ ▼ + Main Window Overlay Window +``` + +--- + +# Whisper + +Whisper is responsible for speech-to-text. + +For the first version, a lightweight local runtime such as `whisper.cpp` is recommended. + +The transcription engine should provide: + +```text +text +start timestamp +end timestamp +confidence if available +partial/final state +``` + +Example: + +```json +{ + "start": 18.2, + "end": 21.7, + "text": "We should finish the API before Friday.", + "final": true +} +``` + +## Suggested Model Strategy + +Default: + +```text +Whisper small +``` + +Possible fallback for lower-end hardware: + +```text +Whisper base +``` + +Possible higher-accuracy option: + +```text +Whisper medium +``` + +The selected model should be configurable in Settings. + +The goal is to balance: + +```text +accuracy +latency +CPU/GPU usage +memory usage +``` + +--- + +# diart + +diart is responsible for real-time speaker diarization. + +It does not need to know a person's real identity. + +It only needs to produce stable speaker labels such as: + +```text +Speaker 1 +Speaker 2 +Speaker 3 +``` + +Example diarization result: + +```json +{ + "speaker": "speaker_1", + "start": 18.0, + "end": 22.0 +} +``` + +The application combines the diart result with the Whisper result using timestamps. + +--- + +# Transcript + Speaker Merge + +Whisper may produce: + +```text +18.2 - 21.7 + +"We should finish the API before Friday." +``` + +diart may produce: + +```text +18.0 - 22.0 + +speaker_1 +``` + +The merger produces: + +```json +{ + "speaker_id": "speaker_1", + "speaker_name": "Speaker 1", + "start": 18.2, + "end": 21.7, + "text": "We should finish the API before Friday." +} +``` + +This becomes the core live transcript segment. + +--- + +# Speaker Rename + +Detected speakers initially appear as: + +```text +Speaker 1 +Speaker 2 +Speaker 3 +``` + +The user can rename them at any time. + +Example: + +```text +Speaker 1 → Alice +Speaker 2 → Bob +``` + +The underlying diarization ID does not change. + +```text +speaker_1 + ↓ +display name + ↓ +Alice +``` + +Recommended local mapping: + +```json +{ + "speaker_1": "Alice", + "speaker_2": "Bob" +} +``` + +Existing transcript lines and future overlay lines should immediately use the new display name. + +--- + +# Real-Time Overlay + +The overlay is a dedicated transparent Tauri window. + +It should be: + +- Always on top +- Transparent or semi-transparent +- Resizable +- Draggable +- Lockable +- Optionally click-through +- Hotkey controlled +- Able to show only the latest few transcript lines + +Example: + +```text +┌──────────────────────────────────────────────┐ +│ Alice │ +│ We should finish the API before Friday. │ +│ │ +│ Bob │ +│ I'll prepare the deployment plan tonight. │ +└──────────────────────────────────────────────┘ +``` + +The overlay should update continuously while the meeting is running. + +--- + +# Overlay Update Model + +The desktop app should distinguish between: + +```text +partial transcript +final transcript +``` + +Example: + +```text +Speaker 1: +We should finish the... +``` + +updates to: + +```text +Speaker 1: +We should finish the API before Friday. +``` + +A partial line may be visually distinguished from finalized text. + +Recommended behavior: + +```text +partial +→ replace current live line + +final +→ commit transcript segment +→ append to transcript history +``` + +--- + +# Main UI Screens + +## Dashboard + +Purpose: + +- Start a new meeting +- View recent meetings +- Check local transcription state +- Check SeaweedFS/backend connectivity +- Open Settings + +Main actions: + +```text +Start Meeting +Open Meeting +Preview Overlay +Settings +``` + +--- + +# Live Meeting Screen + +The Live Meeting screen is the main control surface while recording. + +It should show: + +- Recording state +- Meeting timer +- Live transcript +- Speaker labels +- Rename controls +- Microphone state +- System audio state +- Whisper model status +- Diarization status +- Overlay toggle +- Pause +- Finish meeting + +Example: + +```text +Weekly Product Sync REC 00:18:42 + +Live transcript + +Alice +We should finalize the API contract. + +Bob +I'll update the database migration. + +Alice +Great. I'll prepare the release notes. + + +Speakers + +Alice Rename +Bob Rename + + +Microphone ON +System Audio ON +Whisper Healthy +diart Healthy + +[Pause] [Show Overlay] [Finish Meeting] +``` + +--- + +# Speaker Management + +The speaker panel should display the diarization speakers detected in the current meeting. + +Example: + +```text +speaker_1 Alice +speaker_2 Bob +speaker_3 Speaker 3 +``` + +The user may rename a speaker at any time. + +The rename should affect: + +- Current live transcript +- Overlay +- Final transcript +- Meeting review + +--- + +# Meeting Details Screen + +After a meeting ends, the Meeting Details screen provides: + +- Video/audio playback +- Meeting information +- Transcript +- Speaker names +- Summary +- Action items +- Reminders +- Export options + +Example layout: + +```text +Weekly Product Sync +Sep 27, 2026 • 48 min + +┌──────────────────────────┐ +│ Video Player │ +└──────────────────────────┘ + +Transcript | Summary | Action Items | Files + +Alice 00:18:41 +We should finalize the API contract. + +Bob 00:18:49 +I'll update the database migration. +``` + +The transcript timestamp should be clickable so playback can jump to that position. + +--- + +# Settings + +Settings should include the following sections. + +## General + +```text +Launch at startup +Keep running in tray +Theme +Global hotkey +``` + +## Audio + +```text +Microphone device +System audio device +Input level +System audio level +Audio monitoring +``` + +## Transcription + +```text +Whisper model +Compute device +Language +Chunk size +Voice activity detection +``` + +Suggested model options: + +```text +base +small +medium +``` + +## Diarization + +```text +Enable speaker diarization +Maximum expected speakers +Speaker clustering sensitivity +``` + +## Overlay + +```text +Enable overlay +Always on top +Click-through mode +Opacity +Font size +Maximum visible lines +Show speaker labels +Screen position +``` + +## Storage & Sync + +```text +Local recording folder +Automatic upload +Upload after meeting +Delete local media after successful upload +Backend connection +SeaweedFS upload status +``` + +## Account + +```text +Signed-in user +Subscription +Sign out +``` + +--- + +# Local Meeting Session Model + +During a live meeting, the desktop application should maintain a local session object. + +Example: + +```json +{ + "meeting_id": "uuid", + "started_at": "2026-09-27T09:00:00Z", + "status": "recording", + + "speakers": { + "speaker_1": "Alice", + "speaker_2": "Bob" + }, + + "segments": [ + { + "id": "segment-1", + "speaker_id": "speaker_1", + "start": 18.2, + "end": 21.7, + "text": "We should finish the API before Friday." + } + ] +} +``` + +This state should be stored locally during recording so a temporary network failure does not lose the transcript. + +--- + +# Transcript Segment Model + +A transcript should internally be represented as segments rather than only one large text string. + +Recommended structure: + +```json +{ + "id": "segment-uuid", + "speaker_id": "speaker_1", + "speaker_name": "Alice", + "start": 18.2, + "end": 21.7, + "text": "We should finish the API before Friday.", + "final": true +} +``` + +Benefits: + +- Real-time rendering +- Speaker rename +- Timestamp navigation +- Transcript editing +- Summary generation +- Export +- Search + +--- + +# Recording Flow + +```text +User clicks Start Meeting + │ + ▼ +Create local meeting session + │ + ▼ +Start microphone capture + │ + ├──── Start system audio capture + │ + ▼ +Start recording + │ + ├──── Start Whisper + │ + └──── Start diart + │ + ▼ + Live transcript events + │ + ┌──────┴──────┐ + ▼ ▼ + Main UI Overlay +``` + +--- + +# Meeting Finish Flow + +```text +User clicks Finish Meeting + │ + ▼ +Stop audio capture + │ + ▼ +Finalize Whisper segments + │ + ▼ +Finalize speaker mapping + │ + ▼ +Save local transcript + │ + ├──── upload media ─────► SeaweedFS + │ + └──── send transcript ──► Backend API + │ + ▼ + PostgreSQL + │ + ▼ + processing_jobs + │ + ┌───────┴────────┐ + ▼ ▼ + Summarizer Action Items + │ + ▼ + Reminders +``` + +--- + +# Upload Strategy + +Large media should not pass through the backend API. + +Recommended: + +```text +Desktop + │ + │ request presigned URL + ▼ +Backend + │ + │ returns temporary upload URL + ▼ +Desktop ───────────────────────► SeaweedFS + media file +``` + +The transcript is small and can be sent directly through the backend API. + +```text +Desktop + │ + │ transcript JSON + ▼ +Backend API + │ + ▼ +PostgreSQL +``` + +--- + +# Offline / Network Failure Behavior + +The desktop application should not require continuous backend connectivity during a meeting. + +If the backend becomes unavailable: + +```text +recording continues +transcription continues +diarization continues +overlay continues +local saving continues +``` + +After the connection returns: + +```text +upload recording +send transcript +sync meeting metadata +``` + +This is an important design requirement for MeetVault Desktop. + +--- + +# Background Components + +The desktop application can be separated into these logical modules: + +```text +MeetVault Desktop + +├── UI +│ ├── Dashboard +│ ├── Live Meeting +│ ├── Meeting Details +│ └── Settings +│ +├── Overlay +│ └── Realtime subtitle window +│ +├── Audio Engine +│ ├── Microphone +│ ├── System audio +│ └── Mixer +│ +├── Speech Engine +│ ├── Whisper +│ └── diart +│ +├── Transcript Engine +│ ├── Segment merger +│ ├── Speaker mapping +│ └── Transcript state +│ +├── Recorder +│ └── Media recording +│ +├── Local Storage +│ └── Session recovery/cache +│ +└── Sync Client + ├── Backend API + └── SeaweedFS upload +``` + +--- + +# Realtime Event Flow + +Internally, the UI should consume events rather than directly controlling the models. + +Example events: + +```text +audio.started +audio.stopped + +transcript.partial +transcript.final + +speaker.detected +speaker.changed + +speaker.renamed + +recording.started +recording.stopped + +upload.started +upload.progress +upload.completed +upload.failed +``` + +Example event: + +```json +{ + "type": "transcript.final", + "payload": { + "speaker_id": "speaker_1", + "start": 18.2, + "end": 21.7, + "text": "We should finish the API before Friday." + } +} +``` + +Both the main transcript screen and overlay can subscribe to the same event stream. + +--- + +# Realtime Performance Goals + +Initial targets: + +```text +Overlay update latency +< 2 seconds + +Partial transcript refresh +~ 0.5–1.5 seconds + +Final segment delay +~ 1–3 seconds + +Overlay frame impact +Minimal + +Meeting duration +Several hours without restart +``` + +Actual latency depends on: + +- Whisper model +- CPU/GPU +- Audio chunk size +- Number of speakers +- diart processing speed + +--- + +# Speaker Diarization Limitations + +Speaker diarization is not perfect. + +Expected difficult cases include: + +- Two people speaking simultaneously +- Very similar voices +- Poor microphone quality +- Strong background noise +- Echo +- Speakers far from the microphone +- Very short speech segments + +The application should allow users to correct speaker names and, later, reassign individual transcript segments if necessary. + +No biometric speaker identification is required. + +MeetVault only needs: + +```text +who spoke when +``` + +not: + +```text +who is this person in real life +``` + +--- + +# Security + +Permanent SeaweedFS credentials must never be stored in the renderer/frontend. + +The desktop app should use: + +```text +Backend authentication ++ +temporary presigned upload URLs +``` + +Sensitive local files should be stored only in the configured MeetVault application data directory. + +Authentication tokens should use the operating system's secure credential storage when possible. + +--- + +# MVP Scope + +The first desktop release should focus on: + +- Start/stop meeting recording +- Microphone capture +- System audio capture +- Local Whisper transcription +- Real-time diart speaker separation +- Speaker 1 / Speaker 2 labels +- Rename speakers +- Real-time overlay +- Local transcript storage +- Meeting playback +- Transcript review +- Upload recording to SeaweedFS +- Send transcript to backend +- Receive generated summary +- View action items +- View reminders + +Not required for the first release: + +- Speaker biometric identification +- Automatic real-name recognition +- Cloud transcription +- Kafka +- Elasticsearch +- Kubernetes +- Vector database +- Complex microservice architecture + +--- + +# Final Desktop Architecture + +```text + MeetVault Desktop + + Audio Sources + / \ + Microphone System Audio + \ / + \ / + Audio Mixer + │ + ┌─────────┴─────────┐ + │ │ + ▼ ▼ + Whisper diart + │ │ + transcript speakers + │ │ + └─────────┬─────────┘ + ▼ + Segment Merger + │ + ┌─────────────┴──────────────┐ + │ │ + ▼ ▼ + Live Meeting UI Realtime Overlay + │ + ▼ + Local Session Store + │ + meeting completes + │ + ┌───────┴────────┐ + │ │ + ▼ ▼ + SeaweedFS Backend API + media transcript + │ + ▼ + PostgreSQL + │ + ▼ + processing_jobs + │ + ┌────────┴─────────┐ + ▼ ▼ + Summary Action Items + │ + ▼ + Reminders +``` diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/docs/MeetVault_UI_Wireframes_v2.drawio b/Wireframes/Desktop/MeetVault-Desktop-Demo/docs/MeetVault_UI_Wireframes_v2.drawio new file mode 100644 index 0000000..d59fe52 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/docs/MeetVault_UI_Wireframes_v2.drawio @@ -0,0 +1,577 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/index.html b/Wireframes/Desktop/MeetVault-Desktop-Demo/index.html new file mode 100644 index 0000000..5fab724 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/index.html @@ -0,0 +1,13 @@ + + + + + + + MeetVault Demo + + +
+ + + diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/package-lock.json b/Wireframes/Desktop/MeetVault-Desktop-Demo/package-lock.json new file mode 100644 index 0000000..8cab182 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/package-lock.json @@ -0,0 +1,2271 @@ +{ + "name": "meetvault-desktop-demo", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "meetvault-desktop-demo", + "version": "0.1.0", + "dependencies": { + "@tauri-apps/api": "^2.0.0", + "@tauri-apps/plugin-dialog": "^2.0.0", + "@tauri-apps/plugin-fs": "^2.0.0", + "lucide-react": "^0.468.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^7.1.1", + "zustand": "^5.0.2" + }, + "devDependencies": { + "@tauri-apps/cli": "^2.0.0", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "~5.6.2", + "vite": "^6.0.5" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.5.tgz", + "integrity": "sha512-J25QJU+B78T4FhhBsNpLJyVWOi31mwtpcMwywHmOKH65Q9IWGA81gPj+dnwlhU8wktVriYE+tFAaQgrnJRzAZg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.5.tgz", + "integrity": "sha512-LDopB3zuZM5Ux9TT2luNEBJW/tYbGU2g1d+VpKk6I+gSKDb+/7sYE6M225gRQt4RbMX6MSwMsVR/phdjVUgRLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.5.tgz", + "integrity": "sha512-wlJEERGfeuHeBavCL2qVnNacOK43NDoZM4sjkeRPymd04OAE9T1zBqDJgmZ+CIsPTYKwdzpUC8vmOw84dwY4Tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.5.tgz", + "integrity": "sha512-4nJJGg5jbo2wwPP4JP+LfEBA3bvP8rU9CLuhp7jWvq9sxEyhjQFTFdrqi+/dHEin/pd8jpT0vcehIpnZtmEdcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.5.tgz", + "integrity": "sha512-DrZbyCDF1hneuO6jRbvZ2D7+PIBM6yIwYnJpg2vIk58T+wuFpiaGZrfUr59lDWw45bg+IrpTGLPiNi/Fk4w3Cg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.5.tgz", + "integrity": "sha512-gqfUVMJMB3mehqywxp6hTBFfgtMQykZY19+cfiaYP0toIJLb/1DZRJHVkQQGP13W4TAwfZDWeg1qBcheTRioXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.5.tgz", + "integrity": "sha512-CFmhpvAwzSaWMlN3VN7UtmoTihlZNzoP0juQib5TQRnYUyDV8dXeWOp29sobWAT6gXl/hQgAClLlEiYozQG3OQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.5.tgz", + "integrity": "sha512-Uc9H8eXCOayV6JLTH5bXKMId6qbhNHa818/BgYjm4jrlq3vZquC9cqyvHBw17xy5Mnj5f+I3gFK5JcEf3hSqrw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.5.tgz", + "integrity": "sha512-VcPr/szv/1BFw112Kt//fxulXt/JPqzzidU84iW68L2DdjnOO8QFUv2zTSYBEPHD6movBD4z+bbr5y60GYM7Jw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.5.tgz", + "integrity": "sha512-BnxtJ5/91BrIHYIkGrmjz/lbMhqEHt1dPFqIxIFR+jPn0xVc/oUSCtIT089zfp5ufwGDlYz2UC+Fe1SRBpYFbQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.5.tgz", + "integrity": "sha512-LrYcHZwF+fAMNKHYTOQ5osWM4AZF7YF6D+XtsjDyEvljtt11twc+zHVXBLNEjxVSUnKYsOhvVz4Z213eW02COQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.5.tgz", + "integrity": "sha512-nj7QKQePAAUpCpJHtg0pR0W/b92A9NO17JS3BAQmHDn/yhmkir2p8llrKY9TOhleKIaSzy1JhxS3T9FVld6coA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.5.tgz", + "integrity": "sha512-5ylkX6dWMeBKge9nTU+Rxfb+ZfaCIJ9lRqIFaK0eAMcWp7OJbYnLveLgXmm0VrvuLKb8qIK+mHyH0qu88RM+iA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.5.tgz", + "integrity": "sha512-oHK4ZHYFDKjZviK34I+NwgfbGxgI7ztrNxj2hPTSSNFgeq1a/lEd7dHV2fdGAuTH4Iym3RHJg+vAbWaWG4B7Zg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.5.tgz", + "integrity": "sha512-UcetmHZ6XOXuUByiKZyQmb55ZPr0LABr3Ec/HB9wKZn6CEAFWZkE+hsJErJ9hbPBC7nI0dKuELx7CoV6IM7TMg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.5.tgz", + "integrity": "sha512-C5CmDPQBtvjVo8cgQsBs+w6WB0JLkiixhgi6hVLV11hERWdn/p0XcPU2OUcZzac9BPOFq7SbaHFa8r3SWEysCQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.5.tgz", + "integrity": "sha512-lHVQHJFKsuuxLMi3MQO9XVL8Tje3JR82CzB+QDKC5NWBcsIWuwsn9uIM5e3lBhI+fF1/s63qnyYqsg65+8rV/w==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.5.tgz", + "integrity": "sha512-3W9bTFcQNJn71cSJVM9RKIiZOy8DO/XLDii8Uv/Pm6WKqDRj7JV3ZfuXIEfyuy5LXpIzAbB/1M4Ukp9GKNa7nA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.5.tgz", + "integrity": "sha512-VDC7rRJlee/scpki96GZ27Omf6yU87s1YXwVTpjE5841faVlDYYT565rgfmoR1U0sqL7z5ivQSDjcsF6VRXyBA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.5.tgz", + "integrity": "sha512-z86Ok2p4pTdv5xqCKZsTooO7yBEiaJR/HzU3Wx8RmWsPoLppnMKROhJusQob8B3IE1ghC343kUW9rC2r+Wf3ig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.5.tgz", + "integrity": "sha512-IzQmj+xXwQFGhMAMKMQVXkMwMZN3TqkJgAE0nSsqvVwWWciP4AIPMmWRqOQ2GfX7TUDZr+xqGFcBS36CRPGw0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.5.tgz", + "integrity": "sha512-F6qpTaPc9bwBH85kjy0/BLmLSW1uv7AoOXCoRIkg2arlgCYlWYcAbiMkvZuAcaWk9TpCRG//okznLAqLGshkMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.5.tgz", + "integrity": "sha512-igoDsTFhhwECBeGbUuLeIk7t8Y1apa+cs6mDWpx2EZ0ch7oEQgzHbFUXN9euoHekCAQzXdXApAGkV6jznS7tWw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.5.tgz", + "integrity": "sha512-U3teMeMbXFmaM5D+OTJpsOXd+wV/qftIeYF9kBKL4v73641qyJmoXFtA28DQLsnmlyayEsTe72xpLHrArq6vHw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.5.tgz", + "integrity": "sha512-ypfC34F3RKXvCXBglGqGMsUSMKlgwd1HX9AOAlx9RoZZ6GaI42YHVeKpzg3JG+wpBUJYTG+NNZhqbDWL8tBZkw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tauri-apps/api": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.12.0.tgz", + "integrity": "sha512-fUSxFX8VABYe9W9K5ROdEaKVkNUFRcMSRofjruvaxztXA+I0XnQHcYHF8m/a6iExRUxsUf0HsFWhPBuH9IgzJw==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.12.0.tgz", + "integrity": "sha512-GIShnAR4C16kSTA8NO02fo8fOw/I5z5sAUEB5S2HlPHFnBkbihL4c9a/MpkxOm1+q5S+tFd3X4QUjHWxfyGzpQ==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.12.0", + "@tauri-apps/cli-darwin-x64": "2.12.0", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.12.0", + "@tauri-apps/cli-linux-arm64-gnu": "2.12.0", + "@tauri-apps/cli-linux-arm64-musl": "2.12.0", + "@tauri-apps/cli-linux-riscv64-gnu": "2.12.0", + "@tauri-apps/cli-linux-x64-gnu": "2.12.0", + "@tauri-apps/cli-linux-x64-musl": "2.12.0", + "@tauri-apps/cli-win32-arm64-msvc": "2.12.0", + "@tauri-apps/cli-win32-ia32-msvc": "2.12.0", + "@tauri-apps/cli-win32-x64-msvc": "2.12.0" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.12.0.tgz", + "integrity": "sha512-v5J+jISihLGtih1+3yWagbsL+5dbLZORu4vaSI0vhY0Heb+MAW+T/1s1RyKxrQrwEPGBG5kqwfDDTnSbmVkuqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.12.0.tgz", + "integrity": "sha512-g27I07pfW/dMS8vcNqB9lamgKF0TqLlcy8e7t9qDGsB1iaeyjJWb2TgBF4tM9ZwLqPAZUXbsz28oXljO12WC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.12.0.tgz", + "integrity": "sha512-gXyQ710fNc99YuxFxNyeX9PLSrIpbtgBBj7GQo9tSeRT6fIK40q3/gm7X5jqZo7mWLR2m0R/2ALd3vAWo0Z32Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.12.0.tgz", + "integrity": "sha512-H9cyXuq9WgxoHh2NZ+R+oN+W78x5UwaBDEp0/fmqUUkaBoioCuOZErM9xKiIV6bpNAhmw1RoWZ49u37q/vaiAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.12.0.tgz", + "integrity": "sha512-5tFuk3JKMBa1M/iN4gN32OYlSfhY3aqb5PO3zCugxaT+sfecyMKpc3EM9VDbhmwWBE9QD4Tfj7wGhlqx2yy2pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.12.0.tgz", + "integrity": "sha512-LOrMyV+nb7pKq8P0BZtTBQuyzm1ORjn16R9MlupmxuT5LqxtuYJlw3VMwbaeXCToJ8jbP5u9JnkfmbqDlvETXw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.12.0.tgz", + "integrity": "sha512-aMIkI+X9qvD2+k80QZB3tuOYT41/cEheikC510CwAIZ31KLXORsRASYYzM4mi1/58ICmtzSLFhCC08keir7COw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.12.0.tgz", + "integrity": "sha512-9gl3NhUbfuGhSKjeGCypKSdtoezOQRcDUrkhTWtLQEjnajets6peO6NadgI2DF0OzbiiFOzrnnIGMwBS+Ap6Iw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.12.0.tgz", + "integrity": "sha512-1W+v86wsYLuyBDN0iDessD4CD9o+yNyO0m8Dp3+/vfjiZrJ/v7R7/aTRBeikaq2tLGgxeag0gxnFggBNBGw91A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.12.0.tgz", + "integrity": "sha512-SsBjvq4f7YaBGqcWoE3cycoItPf0SrQeqFqWWgfxoeZWAIz01E31LDSeZ2p/etJiIY0R9oi045chkFfb0O7WGw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.12.0.tgz", + "integrity": "sha512-9zzDKYUH4TKKBgRdDdVQX8fiRO5gJ8hD1nGsHU9DOHG5Jpm3arjbu9Xg1frR20EusG3Sl8X0dppoO/4EoUXUGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/plugin-dialog": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.8.0.tgz", + "integrity": "sha512-HSSD5C8UFE9ijD1wue84F7jjaIReFmj8AAPtS+xyjYRltJafIc4BiWHY7SJ6vbbew6u+YljEJHfyNfy5VwywXg==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.12.0" + } + }, + "node_modules/@tauri-apps/plugin-fs": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.6.0.tgz", + "integrity": "sha512-MhBAZ4rryd+h9fijd/GWDfArqiAeGI0ZNduV4wuRyUVI6wDZ+oDBY7atqtoXKDwlTUVSvpvmWJAY0ehQd/v6Uw==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.12.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.26", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.26.tgz", + "integrity": "sha512-GLQdD3y6UF8iVuMJl5fHgE4jdn/ua7n+toKfLgNlg3BqQtOZjpy68T8Tup8/wGWZCDlm7KMg7tPb4MPn7oN0TQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.1.tgz", + "integrity": "sha512-AUdjuRyCNGUYtqpqfTmWyM4fXay8yIQhmLnvYe/THMGfT9B/34X7xQd3ifKxwyNPPpowVBjLb+64BN9Rn1mizw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.25", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.438", + "node-releases": "^2.0.57", + "update-browserslist-db": "^1.3.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001812", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001812.tgz", + "integrity": "sha512-qN+QNNBr93TCmFrmte0bBCjSDMuRvt78VlHT99qIGPszm4QsqCX8lnyWUFkHi8B7ZBAPq8SH+UqyXNy/odMdng==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.439", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.439.tgz", + "integrity": "sha512-qu6QIPXhsb+CRcAiTMNjR4A1y/7tCYKkKjr5CZXRVih6qkDf79peZ2BpEU3qoDBNCRrcy3Mra3X9nG5oruuA7Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.468.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz", + "integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.57", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.57.tgz", + "integrity": "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.4.tgz", + "integrity": "sha512-PUPQcMhMGRAslLcvtlPz/kmzBEWPhLdgLFrL7pLNepBL6dX0lWj4WD2cUYVgYCuT3jxvghYFg81cDTj44DhetQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.4.tgz", + "integrity": "sha512-yrfmJHIpDG7taCpqKjT1G5B6q3O2K+RN8/fgNf0lTjCwiPbQ0ei6vXX9ZjQR+7ld8Tr7Z5xmyMnZ8YJrphWQUw==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.4" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/rollup": { + "version": "4.63.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.5.tgz", + "integrity": "sha512-KRWwmNLlPw5M7HcdYfm15oBv9n9LPtjzpzCIxS/phwqvPyxHSoKX6Y2YU3pxSPfy0CLquVgsx/j/hBi6OvH1Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.5", + "@rollup/rollup-android-arm64": "4.63.5", + "@rollup/rollup-darwin-arm64": "4.63.5", + "@rollup/rollup-darwin-x64": "4.63.5", + "@rollup/rollup-freebsd-arm64": "4.63.5", + "@rollup/rollup-freebsd-x64": "4.63.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.5", + "@rollup/rollup-linux-arm-musleabihf": "4.63.5", + "@rollup/rollup-linux-arm64-gnu": "4.63.5", + "@rollup/rollup-linux-arm64-musl": "4.63.5", + "@rollup/rollup-linux-loong64-gnu": "4.63.5", + "@rollup/rollup-linux-loong64-musl": "4.63.5", + "@rollup/rollup-linux-ppc64-gnu": "4.63.5", + "@rollup/rollup-linux-ppc64-musl": "4.63.5", + "@rollup/rollup-linux-riscv64-gnu": "4.63.5", + "@rollup/rollup-linux-riscv64-musl": "4.63.5", + "@rollup/rollup-linux-s390x-gnu": "4.63.5", + "@rollup/rollup-linux-x64-gnu": "4.63.5", + "@rollup/rollup-linux-x64-musl": "4.63.5", + "@rollup/rollup-openbsd-x64": "4.63.5", + "@rollup/rollup-openharmony-arm64": "4.63.5", + "@rollup/rollup-win32-arm64-msvc": "4.63.5", + "@rollup/rollup-win32-ia32-msvc": "4.63.5", + "@rollup/rollup-win32-x64-gnu": "4.63.5", + "@rollup/rollup-win32-x64-msvc": "4.63.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zustand": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.15.tgz", + "integrity": "sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/package.json b/Wireframes/Desktop/MeetVault-Desktop-Demo/package.json new file mode 100644 index 0000000..3103b3d --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/package.json @@ -0,0 +1,33 @@ +{ + "name": "meetvault-desktop-demo", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1 --port 1420", + "build": "tsc -b && vite build", + "preview": "vite preview --host 127.0.0.1 --port 1420", + "tauri": "tauri", + "desktop": "tauri dev", + "speech": "python speech-engine/server.py", + "speech:mock": "python speech-engine/server.py --mock" + }, + "dependencies": { + "@tauri-apps/api": "^2.0.0", + "@tauri-apps/plugin-dialog": "^2.0.0", + "@tauri-apps/plugin-fs": "^2.0.0", + "lucide-react": "^0.468.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^7.1.1", + "zustand": "^5.0.2" + }, + "devDependencies": { + "@tauri-apps/cli": "^2.0.0", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "~5.6.2", + "vite": "^6.0.5" + } +} diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/scripts/start-demo-mock.ps1 b/Wireframes/Desktop/MeetVault-Desktop-Demo/scripts/start-demo-mock.ps1 new file mode 100644 index 0000000..6641208 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/scripts/start-demo-mock.ps1 @@ -0,0 +1,5 @@ +$ErrorActionPreference = "Stop" +Write-Host "Starting MeetVault mock speech engine..." +Start-Process powershell -ArgumentList "-NoExit", "-Command", "cd '$PSScriptRoot\..\speech-engine'; python server.py --mock" +Set-Location "$PSScriptRoot\.." +npm run desktop diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/scripts/start-demo.ps1 b/Wireframes/Desktop/MeetVault-Desktop-Demo/scripts/start-demo.ps1 new file mode 100644 index 0000000..9d573c8 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/scripts/start-demo.ps1 @@ -0,0 +1,6 @@ +$ErrorActionPreference = "Stop" +Write-Host "Starting MeetVault speech engine..." +Start-Process powershell -ArgumentList "-NoExit", "-Command", "cd '$PSScriptRoot\..\speech-engine'; python server.py" +Set-Location "$PSScriptRoot\.." +Write-Host "Starting Tauri desktop app..." +npm run desktop diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/scripts/start-demo.sh b/Wireframes/Desktop/MeetVault-Desktop-Demo/scripts/start-demo.sh new file mode 100644 index 0000000..38f630c --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/scripts/start-demo.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")/.." +python speech-engine/server.py & +ENGINE_PID=$! +trap 'kill $ENGINE_PID 2>/dev/null || true' EXIT +npm run desktop diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/speech-engine/.env.example b/Wireframes/Desktop/MeetVault-Desktop-Demo/speech-engine/.env.example new file mode 100644 index 0000000..e233a54 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/speech-engine/.env.example @@ -0,0 +1,3 @@ +# diart uses pyannote models. Authenticate with Hugging Face before real mode. +# You can either run `huggingface-cli login` or provide a token here if your setup consumes it. +HF_TOKEN= diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/speech-engine/README.md b/Wireframes/Desktop/MeetVault-Desktop-Demo/speech-engine/README.md new file mode 100644 index 0000000..ad9a46f --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/speech-engine/README.md @@ -0,0 +1,66 @@ +# MeetVault Local Speech Engine + +This process is **local**, not the future MeetVault backend. It provides the demo's real-time Whisper + diart functionality. + +## Recommended environment + +Use Python **3.10 or 3.11**. `diart`/PyTorch/pyannote compatibility is much better there than on very new Python releases. + +```bash +python -m venv .venv +# Windows +.venv\Scripts\activate +# Linux/macOS +source .venv/bin/activate + +pip install -r requirements.txt +``` + +For diart, install the native audio dependencies required by your OS and authenticate with Hugging Face if the pyannote models are gated in your environment. Follow the current diart/pyannote model instructions and accept their model terms before starting real mode. + +## Run real mode + +```bash +python server.py +``` + +The desktop application connects to: + +```text +ws://127.0.0.1:8765/ws/live +``` + +## Run mock mode + +Mock mode requires only the FastAPI stack and is intended to validate UI behavior: + +```bash +python server.py --mock +``` + +You can also choose **Mock demo** under MeetVault Settings → Local speech engine. + +## Audio protocol + +- mono +- signed int16 little-endian PCM +- 16 kHz +- binary WebSocket frames + +The first WebSocket message is JSON configuration. The engine replies with JSON events such as: + +```json +{ + "type": "final", + "segment": { + "id": "uuid", + "speakerId": "speaker_1", + "start": 12.1, + "end": 15.4, + "text": "We should deploy tomorrow.", + "final": true + } +} +``` + +Speaker names such as `Alice` are **not** generated by the engine. The engine produces stable session labels (`speaker_1`, `speaker_2`, ...), and the MeetVault UI lets the user rename those labels live. diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/speech-engine/requirements.txt b/Wireframes/Desktop/MeetVault-Desktop-Demo/speech-engine/requirements.txt new file mode 100644 index 0000000..8741fa3 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/speech-engine/requirements.txt @@ -0,0 +1,13 @@ +# Recommended Python: 3.12 + +fastapi>=0.115,<1 +uvicorn[standard]>=0.30,<1 + +# diart 0.9.2 requires NumPy < 2 +numpy>=1.26.4,<2 + +faster-whisper>=1.1,<2 +python-dotenv>=1,<2 + +# diart 0.9.2 supports Python 3.10–3.12 +diart==0.9.2 \ No newline at end of file diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/speech-engine/server.py b/Wireframes/Desktop/MeetVault-Desktop-Demo/speech-engine/server.py new file mode 100644 index 0000000..86a4c37 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/speech-engine/server.py @@ -0,0 +1,415 @@ +"""MeetVault local live speech engine. + +Protocol +-------- +WebSocket: ws://127.0.0.1:8765/ws/live +1. Client sends a JSON config message. +2. Client streams signed 16-bit little-endian mono PCM at 16 kHz. +3. Server returns JSON events: ready, partial, final, health, error. + +Real mode combines faster-whisper with diart speaker diarization. Mock mode is +available so the desktop UI can be exercised without ML dependencies/models. +""" + +from __future__ import annotations + +import argparse +import asyncio +import json +import os +import queue +import threading +import time +import uuid +from dataclasses import dataclass +from typing import Optional + +import numpy as np +from fastapi import FastAPI, WebSocket, WebSocketDisconnect +from fastapi.middleware.cors import CORSMiddleware +import uvicorn + +SAMPLE_RATE = 16000 + + +@dataclass +class EngineConfig: + model: str = "small" + compute: str = "auto" + language: str = "auto" + diarization: bool = True + sample_rate: int = SAMPLE_RATE + + +class SpeakerTimeline: + """Thread-safe speaker timeline built from diart streaming predictions.""" + + def __init__(self) -> None: + self._segments: list[tuple[float, float, str]] = [] + self._label_map: dict[str, str] = {} + self._lock = threading.Lock() + + def _stable_label(self, raw: str) -> str: + with self._lock: + if raw not in self._label_map: + self._label_map[raw] = f"speaker_{len(self._label_map) + 1}" + return self._label_map[raw] + + def update_from_annotation(self, annotation) -> None: + fresh: list[tuple[float, float, str]] = [] + try: + for segment, _, label in annotation.itertracks(yield_label=True): + fresh.append((float(segment.start), float(segment.end), self._stable_label(str(label)))) + except Exception: + return + if not fresh: + return + latest_end = max(end for _, end, _ in fresh) + with self._lock: + # Keep a rolling two-minute history, then merge the latest prediction. + self._segments = [s for s in self._segments if s[1] >= latest_end - 120] + self._segments.extend(fresh) + + def resolve(self, start: float, end: float) -> str: + with self._lock: + candidates = list(self._segments) + best_label = "speaker_1" + best_overlap = 0.0 + for s, e, label in candidates: + overlap = max(0.0, min(end, e) - max(start, s)) + if overlap > best_overlap: + best_overlap = overlap + best_label = label + return best_label + + +class QueueAudioSource: + """diart AudioSource fed by PCM pushed from the WebSocket thread.""" + + def __init__(self, sample_rate: int): + from diart.sources import AudioSource + + class _Source(AudioSource): + def __init__(self, sr: int): + super().__init__("meetvault-live", sr) + self.q: queue.Queue[Optional[np.ndarray]] = queue.Queue() + self.cursor = 0 + + @property + def is_regular(self): + return False + + @property + def duration(self): + return None + + def push(self, samples: np.ndarray): + self.q.put(samples.astype(np.float32, copy=False)) + + def close(self): + self.q.put(None) + + def read(self): + from pyannote.core import SlidingWindow, SlidingWindowFeature + + while True: + item = self.q.get() + if item is None: + self.stream.on_completed() + return + start = self.cursor / self.sample_rate + window = SlidingWindow(start=start, duration=1 / self.sample_rate, step=1 / self.sample_rate) + feature = SlidingWindowFeature(item.reshape(-1, 1), window) + self.cursor += len(item) + self.stream.on_next(feature) + + self.source = _Source(sample_rate) + + def push(self, samples: np.ndarray) -> None: + self.source.push(samples) + + def close(self) -> None: + self.source.close() + + +class DiartWorker: + def __init__(self, enabled: bool, sample_rate: int = SAMPLE_RATE): + self.enabled = enabled + self.timeline = SpeakerTimeline() + self.audio_source: Optional[QueueAudioSource] = None + self.thread: Optional[threading.Thread] = None + self.error: Optional[str] = None + if enabled: + self._start(sample_rate) + + def _start(self, sample_rate: int) -> None: + try: + from diart import SpeakerDiarization + from diart.inference import StreamingInference + + pipeline = SpeakerDiarization() + expected_rate = int(getattr(pipeline.config, "sample_rate", sample_rate)) + if expected_rate != sample_rate: + raise RuntimeError(f"diart expects {expected_rate} Hz but MeetVault sends {sample_rate} Hz") + self.audio_source = QueueAudioSource(sample_rate) + inference = StreamingInference(pipeline, self.audio_source.source, do_plot=False) + + def hook(annotation_and_waveform): + try: + self.timeline.update_from_annotation(annotation_and_waveform[0]) + except Exception: + pass + + inference.attach_hooks(hook) + self.thread = threading.Thread(target=inference, name="meetvault-diart", daemon=True) + self.thread.start() + except Exception as exc: + self.error = str(exc) + self.enabled = False + + def push(self, samples: np.ndarray) -> None: + if self.enabled and self.audio_source: + self.audio_source.push(samples) + + def close(self) -> None: + if self.audio_source: + self.audio_source.close() + + def resolve(self, start: float, end: float) -> str: + return self.timeline.resolve(start, end) if self.enabled else "speaker_1" + + +class LiveSpeechSession: + def __init__(self, config: EngineConfig): + from faster_whisper import WhisperModel + + self.config = config + device = config.compute + if device == "auto": + device = "cpu" + compute_type = "float16" if device == "cuda" else "int8" + try: + self.model = WhisperModel(config.model, device=device, compute_type=compute_type) + except Exception: + # Useful fallback when CUDA was selected but is not available. + self.model = WhisperModel(config.model, device="cpu", compute_type="int8") + + self.diarizer = DiartWorker(config.diarization, config.sample_rate) + self.samples = np.empty(0, dtype=np.float32) + self.lock = threading.Lock() + self.emitted_until = 0.0 + self.last_process_samples = 0 + self.closed = False + + def push_pcm(self, raw: bytes) -> None: + pcm = np.frombuffer(raw, dtype=" max_samples: + dropped = self.samples.size - max_samples + self.samples = self.samples[dropped:] + self.emitted_until = max(0.0, self.emitted_until - dropped / self.config.sample_rate) + + def total_seconds(self) -> float: + with self.lock: + return self.samples.size / self.config.sample_rate + + def should_process(self) -> bool: + with self.lock: + new_samples = self.samples.size - self.last_process_samples + return new_samples >= int(self.config.sample_rate * 1.4) + + def transcribe_once(self) -> tuple[list[dict], Optional[str]]: + with self.lock: + total = self.samples.size + if total < self.config.sample_rate: + return [], None + window_size = min(total, self.config.sample_rate * 10) + audio = self.samples[-window_size:].copy() + self.last_process_samples = total + window_start = max(0.0, total / self.config.sample_rate - len(audio) / self.config.sample_rate) + + segments_iter, _ = self.model.transcribe( + audio, + language=None if self.config.language in ("", "auto") else self.config.language, + beam_size=1, + best_of=1, + vad_filter=True, + word_timestamps=True, + condition_on_previous_text=False, + temperature=0.0, + ) + segments = list(segments_iter) + results: list[dict] = [] + partial: Optional[str] = None + safe_cutoff = total / self.config.sample_rate - 0.65 + + for seg in segments: + text = seg.text.strip() + if not text: + continue + abs_start = window_start + float(seg.start) + abs_end = window_start + float(seg.end) + if abs_end <= self.emitted_until + 0.08: + continue + if abs_end > safe_cutoff: + partial = text + continue + speaker_id = self.diarizer.resolve(abs_start, abs_end) + results.append({ + "id": str(uuid.uuid4()), + "speakerId": speaker_id, + "start": abs_start, + "end": abs_end, + "text": text, + "final": True, + }) + self.emitted_until = max(self.emitted_until, abs_end) + + return results, partial + + def close(self) -> None: + self.closed = True + self.diarizer.close() + + +class MockSession: + def __init__(self, config: EngineConfig): + self.config = config + self.sample_count = 0 + self.index = 0 + self.lines = [ + ("speaker_1", "We should finalize the upload flow before Friday."), + ("speaker_2", "I can take the local recording and video playback work."), + ("speaker_1", "Great. Keep the backend in processing state for this demo."), + ("speaker_3", "The overlay should keep updating while another application is open."), + ] + + def push_pcm(self, raw: bytes) -> Optional[dict]: + self.sample_count += len(raw) // 2 + threshold = self.config.sample_rate * 2.4 + if self.sample_count < threshold: + return None + self.sample_count = 0 + speaker, text = self.lines[self.index % len(self.lines)] + self.index += 1 + end = self.index * 2.4 + return { + "id": str(uuid.uuid4()), "speakerId": speaker, + "start": end - 2.1, "end": end, "text": text, "final": True, + } + + def close(self) -> None: + pass + + +app = FastAPI(title="MeetVault Local Speech Engine") +app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) +MOCK_ONLY = False + + +@app.get("/health") +async def health(): + return {"ok": True, "mode": "mock" if MOCK_ONLY else "real-capable", "sample_rate": SAMPLE_RATE} + + +@app.websocket("/ws/live") +async def live(websocket: WebSocket): + await websocket.accept() + session = None + process_task = None + try: + first = await websocket.receive_text() + payload = json.loads(first) + config = EngineConfig( + model=str(payload.get("model", "small")), + compute=str(payload.get("compute", "auto")), + language=str(payload.get("language", "auto")), + diarization=bool(payload.get("diarization", True)), + sample_rate=int(payload.get("sample_rate", SAMPLE_RATE)), + ) + if config.sample_rate != SAMPLE_RATE: + await websocket.send_json({"type": "error", "message": "MeetVault speech engine currently requires 16 kHz mono PCM."}) + return + + if MOCK_ONLY: + session = MockSession(config) + await websocket.send_json({"type": "ready", "engine": "mock-whisper", "diarization": "mock-diart"}) + else: + try: + session = await asyncio.to_thread(LiveSpeechSession, config) + except Exception as exc: + await websocket.send_json({ + "type": "error", + "message": ( + f"Could not initialize Whisper/diart: {exc}. " + "Use --mock for UI testing. Real diarization may require Hugging Face authentication and accepted pyannote model terms." + ), + }) + return + diarization_state = "diart ready" if session.diarizer.enabled else f"diart unavailable: {session.diarizer.error}" + await websocket.send_json({"type": "ready", "engine": f"Whisper {config.model}", "diarization": diarization_state}) + + async def processor(): + while not session.closed: + await asyncio.sleep(0.35) + if not session.should_process(): + continue + try: + finals, partial = await asyncio.to_thread(session.transcribe_once) + for segment in finals: + await websocket.send_json({"type": "final", "segment": segment}) + if partial: + await websocket.send_json({"type": "partial", "text": partial}) + except Exception as exc: + await websocket.send_json({"type": "error", "message": f"Live transcription error: {exc}"}) + await asyncio.sleep(1) + + process_task = asyncio.create_task(processor()) + + while True: + message = await websocket.receive() + if message.get("bytes") is not None: + raw = message["bytes"] + if isinstance(session, MockSession): + segment = session.push_pcm(raw) + if segment: + await websocket.send_json({"type": "final", "segment": segment}) + else: + session.push_pcm(raw) + elif message.get("text"): + command = json.loads(message["text"]) + if command.get("type") == "stop": + break + except WebSocketDisconnect: + pass + except Exception as exc: + try: + await websocket.send_json({"type": "error", "message": str(exc)}) + except Exception: + pass + finally: + if session: + session.close() + if process_task: + process_task.cancel() + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--host", default="127.0.0.1") + parser.add_argument("--port", type=int, default=8765) + parser.add_argument("--mock", action="store_true", help="Run without Whisper/diart models") + args = parser.parse_args() + global MOCK_ONLY + MOCK_ONLY = args.mock + uvicorn.run(app, host=args.host, port=args.port, log_level="info") + + +if __name__ == "__main__": + main() diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/src-tauri/Cargo.toml b/Wireframes/Desktop/MeetVault-Desktop-Demo/src-tauri/Cargo.toml new file mode 100644 index 0000000..202d547 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/src-tauri/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "meetvault_demo" +version = "0.1.0" +description = "MeetVault desktop demo" +authors = ["MeetVault"] +edition = "2021" + +[lib] +name = "meetvault_demo_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = [] } +tauri-plugin-fs = "2" +tauri-plugin-dialog = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/src-tauri/build.rs b/Wireframes/Desktop/MeetVault-Desktop-Demo/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/src-tauri/capabilities/default.json b/Wireframes/Desktop/MeetVault-Desktop-Demo/src-tauri/capabilities/default.json new file mode 100644 index 0000000..70c1691 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/src-tauri/capabilities/default.json @@ -0,0 +1,25 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "MeetVault demo desktop permissions", + "windows": ["main", "overlay"], + "permissions": [ + "core:default", + "core:webview:allow-create-webview-window", + "core:window:allow-show", + "core:window:allow-hide", + "core:window:allow-set-focus", + "fs:allow-mkdir", + "fs:allow-write-file", + "fs:allow-write-text-file", + "fs:allow-read-file", + { + "identifier": "fs:scope", + "allow": [ + { "path": "$APPLOCALDATA/meetings/**/*" }, + { "path": "$APPLOCALDATA/meetings/*" } + ] + }, + "dialog:default" + ] +} diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/src-tauri/src/lib.rs b/Wireframes/Desktop/MeetVault-Desktop-Demo/src-tauri/src/lib.rs new file mode 100644 index 0000000..b3e9f71 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/src-tauri/src/lib.rs @@ -0,0 +1,8 @@ +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_fs::init()) + .plugin(tauri_plugin_dialog::init()) + .run(tauri::generate_context!()) + .expect("error while running MeetVault"); +} diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/src-tauri/src/main.rs b/Wireframes/Desktop/MeetVault-Desktop-Demo/src-tauri/src/main.rs new file mode 100644 index 0000000..2ddcf46 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/src-tauri/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + meetvault_demo_lib::run(); +} diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/src-tauri/tauri.conf.json b/Wireframes/Desktop/MeetVault-Desktop-Demo/src-tauri/tauri.conf.json new file mode 100644 index 0000000..b6fc504 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/src-tauri/tauri.conf.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "MeetVault Demo", + "version": "0.1.0", + "identifier": "com.meetvault.demo", + "build": { + "beforeDevCommand": "npm run dev", + "devUrl": "http://127.0.0.1:1420", + "beforeBuildCommand": "npm run build", + "frontendDist": "../dist" + }, + "app": { + "windows": [ + { + "label": "main", + "title": "MeetVault Demo", + "width": 1440, + "height": 900, + "minWidth": 1080, + "minHeight": 720, + "resizable": true, + "center": true + } + ], + "security": { + "csp": null, + "assetProtocol": { + "enable": true, + "scope": ["$APPLOCALDATA/meetings/**/*"] + } + } + }, + "bundle": { + "active": true, + "targets": "all" + } +} diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/src/App.tsx b/Wireframes/Desktop/MeetVault-Desktop-Demo/src/App.tsx new file mode 100644 index 0000000..f5a8ea4 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/src/App.tsx @@ -0,0 +1,25 @@ +import { HashRouter, Route, Routes } from 'react-router-dom'; +import { Layout } from './components/Layout'; +import { Dashboard } from './pages/Dashboard'; +import { LiveMeeting } from './pages/LiveMeeting'; +import { MeetingDetails } from './pages/MeetingDetails'; +import { Meetings } from './pages/Meetings'; +import { Overlay } from './pages/Overlay'; +import { SettingsPage } from './pages/Settings'; + +export default function App() { + return ( + + + } /> + }> + } /> + } /> + } /> + } /> + } /> + + + + ); +} diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/src/components/Layout.tsx b/Wireframes/Desktop/MeetVault-Desktop-Demo/src/components/Layout.tsx new file mode 100644 index 0000000..f690ba5 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/src/components/Layout.tsx @@ -0,0 +1,22 @@ +import { Gauge, History, Settings } from 'lucide-react'; +import { NavLink, Outlet } from 'react-router-dom'; + +export function Layout() { + return ( +
+ +
+
+ ); +} diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/src/components/ProcessingCard.tsx b/Wireframes/Desktop/MeetVault-Desktop-Demo/src/components/ProcessingCard.tsx new file mode 100644 index 0000000..d7bb83f --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/src/components/ProcessingCard.tsx @@ -0,0 +1,13 @@ +import { LoaderCircle } from 'lucide-react'; + +export function ProcessingCard() { + return ( +
+ +
+ Processing +

Processing (The job). Please wait a moment

+
+
+ ); +} diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/src/components/StatusBadge.tsx b/Wireframes/Desktop/MeetVault-Desktop-Demo/src/components/StatusBadge.tsx new file mode 100644 index 0000000..4fe9383 --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/src/components/StatusBadge.tsx @@ -0,0 +1,3 @@ +export function StatusBadge({ status }: { status: string }) { + return {status[0].toUpperCase() + status.slice(1)}; +} diff --git a/Wireframes/Desktop/MeetVault-Desktop-Demo/src/components/VideoPlayer.tsx b/Wireframes/Desktop/MeetVault-Desktop-Demo/src/components/VideoPlayer.tsx new file mode 100644 index 0000000..c0ec9cc --- /dev/null +++ b/Wireframes/Desktop/MeetVault-Desktop-Demo/src/components/VideoPlayer.tsx @@ -0,0 +1,19 @@ +import { Film } from 'lucide-react'; +import { mediaUrl } from '../lib/localFiles'; + +export function VideoPlayer({ path, mime }: { path?: string; mime?: string }) { + const url = mediaUrl(path); + if (!url) { + return ( +
+ + No local recording yet + Start a meeting with system audio/screen capture to create a video, or microphone-only for audio. +
+ ); + } + if (mime?.startsWith('audio/')) { + return
; + } + return