From 88aac112ac366789a1900bf44c5b1d03a7d048c6 Mon Sep 17 00:00:00 2001 From: gopal-raj-suresh Date: Thu, 9 Apr 2026 09:04:32 -0700 Subject: [PATCH] add: intial codebase --- .env.example | 57 + .github/pull_request_template.md | 39 + .github/workflows/code-scans.yaml | 104 + .gitignore | 86 + CONTRIBUTING.md | 338 ++ DISCLAIMER.md | 22 + LICENSE.md | 21 + SECURITY.md | 38 + TERMS_AND_CONDITIONS.md | 19 + TROUBLESHOOTING.md | 164 + api/.dockerignore | 13 + api/Dockerfile | 17 + api/config.py | 52 + api/models.py | 116 + api/requirements.txt | 9 + api/server.py | 543 ++++ api/services/__init__.py | 6 + api/services/chroma_client.py | 100 + api/services/flowise_client.py | 90 + api/services/flowise_provisioner.py | 663 ++++ api/services/llm_client.py | 179 ++ api/services/pdf_service.py | 26 + api/services/whisper_client.py | 251 ++ docker-compose.yaml | 142 + docs/assets/InnovationHub-HeaderImage.png | Bin 0 -> 184925 bytes ui/.dockerignore | 5 + ui/Dockerfile | 27 + ui/index.html | 17 + ui/nginx.conf | 28 + ui/package-lock.json | 3251 ++++++++++++++++++++ ui/package.json | 27 + ui/postcss.config.js | 6 + ui/public/cloud2labs-logo.png | Bin 0 -> 16782 bytes ui/src/App.jsx | 67 + ui/src/components/AudioRecorder.jsx | 175 ++ ui/src/components/ClinicalChat.jsx | 163 + ui/src/components/ConsultationRecorder.jsx | 449 +++ ui/src/components/FlowCanvas.jsx | 205 ++ ui/src/components/Header.jsx | 101 + ui/src/components/KnowledgeBase.jsx | 171 + ui/src/components/LandingPage.jsx | 151 + ui/src/components/SoapNoteEditor.jsx | 347 +++ ui/src/components/StatusBadge.jsx | 16 + ui/src/index.css | 109 + ui/src/main.jsx | 10 + ui/tailwind.config.js | 54 + ui/vite.config.js | 16 + 47 files changed, 8490 insertions(+) create mode 100644 .env.example create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/code-scans.yaml create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 DISCLAIMER.md create mode 100644 LICENSE.md create mode 100644 SECURITY.md create mode 100644 TERMS_AND_CONDITIONS.md create mode 100644 TROUBLESHOOTING.md create mode 100644 api/.dockerignore create mode 100644 api/Dockerfile create mode 100644 api/config.py create mode 100644 api/models.py create mode 100644 api/requirements.txt create mode 100644 api/server.py create mode 100644 api/services/__init__.py create mode 100644 api/services/chroma_client.py create mode 100644 api/services/flowise_client.py create mode 100644 api/services/flowise_provisioner.py create mode 100644 api/services/llm_client.py create mode 100644 api/services/pdf_service.py create mode 100644 api/services/whisper_client.py create mode 100644 docker-compose.yaml create mode 100644 docs/assets/InnovationHub-HeaderImage.png create mode 100644 ui/.dockerignore create mode 100644 ui/Dockerfile create mode 100644 ui/index.html create mode 100644 ui/nginx.conf create mode 100644 ui/package-lock.json create mode 100644 ui/package.json create mode 100644 ui/postcss.config.js create mode 100644 ui/public/cloud2labs-logo.png create mode 100644 ui/src/App.jsx create mode 100644 ui/src/components/AudioRecorder.jsx create mode 100644 ui/src/components/ClinicalChat.jsx create mode 100644 ui/src/components/ConsultationRecorder.jsx create mode 100644 ui/src/components/FlowCanvas.jsx create mode 100644 ui/src/components/Header.jsx create mode 100644 ui/src/components/KnowledgeBase.jsx create mode 100644 ui/src/components/LandingPage.jsx create mode 100644 ui/src/components/SoapNoteEditor.jsx create mode 100644 ui/src/components/StatusBadge.jsx create mode 100644 ui/src/index.css create mode 100644 ui/src/main.jsx create mode 100644 ui/tailwind.config.js create mode 100644 ui/vite.config.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7a0d35f --- /dev/null +++ b/.env.example @@ -0,0 +1,57 @@ +# ============================================================ +# MediVault AI — Environment Configuration +# ============================================================ +# Copy this file to .env and fill in your values. +# All inference is fully air-gapped — zero cloud dependencies. + +# ============================================================ +# Flowise — AI Orchestration (Docker service — auto-starts) +# ============================================================ +# First-time setup (one-time): +# 1. Open http://localhost:3001 +# 2. Complete the "Setup Account" form (any username/password) +# 3. Log in → avatar (top-right) → API Keys → Add New Key +# 4. Paste the key below and restart: docker compose restart medivault-api +FLOWISE_ENDPOINT=http://medivault-flowise:3001 +FLOWISE_API_KEY= + +# ============================================================ +# Ollama — Sole LLM + Embeddings Provider (runs on host) +# ============================================================ +# Install: https://ollama.com/download +# macOS: brew install ollama (or download the .app from ollama.com) +# Then pull the required models (one-time, ~5.0 GB total): +# ollama pull llama3.1:8b # chat model (~4.7 GB) +# ollama pull nomic-embed-text # embeddings (~274 MB) +# +# Ollama serves on host:11434 — Docker reaches it via host.docker.internal. +OLLAMA_BASE_URL=http://host.docker.internal:11434 +OLLAMA_MODEL=llama3.1:8b +OLLAMA_EMBED_MODEL=nomic-embed-text + +# ============================================================ +# ChromaDB — Vector Store (Docker service — auto-managed) +# ============================================================ +# No setup needed — starts automatically with docker compose up. +CHROMA_HOST=medivault-chromadb +CHROMA_PORT=8000 + +# ============================================================ +# Whisper — Speech-to-Text (Docker service — fully automatic) +# ============================================================ +# No installation needed — model downloads automatically on first run. +# Model sizes: tiny (75MB) | base (145MB) | small (460MB) | medium (1.5GB) + +WHISPER_ENDPOINT=http://medivault-whisper:9000 +WHISPER_MODEL=small + +# ============================================================ +# File Size Limits +# ============================================================ +MAX_AUDIO_SIZE=26214400 +MAX_FILE_SIZE=10485760 + +# ============================================================ +# Server +# ============================================================ +BACKEND_PORT=5001 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..b93e50b --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,39 @@ +## Summary + + + +- + +## Type of Change + + + +- [ ] Bug fix +- [ ] New feature / enhancement +- [ ] Documentation update +- [ ] Refactor (no behavior change) +- [ ] Chore (dependencies, CI, tooling) + +## Changes Made + + + +Resolves # + +## How to Test + + + +1. + +## Checklist + +- [ ] I have read the [Contributing Guide](../CONTRIBUTING.md) +- [ ] My branch is up to date with `main` +- [ ] New environment variables (if any) are documented in `.env.example` and the README +- [ ] No secrets, API keys, or credentials are included in this PR +- [ ] I have tested my changes locally + +## Screenshots (if applicable) + + diff --git a/.github/workflows/code-scans.yaml b/.github/workflows/code-scans.yaml new file mode 100644 index 0000000..940d9b7 --- /dev/null +++ b/.github/workflows/code-scans.yaml @@ -0,0 +1,104 @@ +name: SDLE Scans + +on: + workflow_dispatch: + inputs: + PR_number: + description: 'Pull request number' + required: true + push: + branches: [ main ] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +concurrency: + group: sdle-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + +# ----------------------------- +# 1) Trivy Scan +# ----------------------------- + trivy_scan: + name: Trivy Vulnerability Scan + runs-on: ubuntu-latest + env: + TRIVY_REPORT_FORMAT: table + TRIVY_SCAN_TYPE: fs + TRIVY_SCAN_PATH: . + TRIVY_EXIT_CODE: '1' + TRIVY_VULN_TYPE: os,library + TRIVY_SEVERITY: CRITICAL,HIGH + steps: + - uses: actions/checkout@v4 + + - name: Create report directory + run: mkdir -p trivy-reports + + - name: Run Trivy FS Scan + uses: aquasecurity/trivy-action@0.35.0 + with: + scan-type: 'fs' + scan-ref: '.' + scanners: 'vuln,misconfig,secret,license' + ignore-unfixed: true + format: 'table' + exit-code: '1' + output: 'trivy-reports/trivy_scan_report.txt' + vuln-type: 'os,library' + severity: 'CRITICAL,HIGH' + + - name: Upload Trivy Report + uses: actions/upload-artifact@v4 + with: + name: trivy-report + path: trivy-reports/trivy_scan_report.txt + + - name: Show Trivy Report in Logs + if: failure() + run: | + echo "========= TRIVY FINDINGS =========" + cat trivy-reports/trivy_scan_report.txt + echo "=================================" + +# ----------------------------- +# 2) Bandit Scan +# ----------------------------- + bandit_scan: + name: Bandit security scan + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: 'recursive' + fetch-depth: 0 + + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Install Bandit + run: pip install bandit + + - name: Create Bandit configuration + shell: bash + run: | + cat > .bandit << 'EOF' + [bandit] + exclude_dirs = tests,test,venv,.venv,node_modules + skips = B101 + EOF + + - name: Run Bandit scan + run: | + bandit -r . -ll -iii -f screen + bandit -r . -ll -iii -f html -o bandit-report.html + + - name: Upload Bandit Report + uses: actions/upload-artifact@v4 + with: + name: bandit-report + path: bandit-report.html + retention-days: 30 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2fb65ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,86 @@ +# ============================================ +# ENVIRONMENT & SECRETS +# ============================================ +.env +.env.* +!.env.example + +# ============================================ +# PYTHON +# ============================================ +__pycache__/ +*.py[cod] +*$py.class +*.so + +venv/ +env/ +ENV/ +.venv/ + +.idea/ +.vscode/ +*.swp +*.swo + +.pytest_cache/ +.coverage +htmlcov/ + +.mypy_cache/ +.dmypy.json +dmypy.json + +*.egg-info/ +dist/ +build/ + +# ============================================ +# NODE.JS / REACT +# ============================================ +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +ui/dist/ +ui/build/ + +.env.development.local +.env.test.local +.env.production.local + +# ============================================ +# VECTOR DATABASE +# ============================================ +chroma/ +*.sqlite3 +*.sqlite + +# ============================================ +# TEST ASSETS & SAMPLE DATA +# ============================================ +docs/test-assets/ +*.wav +*.mp3 + +# ============================================ +# BUILD & RUNTIME ARTIFACTS +# ============================================ +*.log +logs/ + +# ============================================ +# TEMPORARY FILES +# ============================================ +tmp/ +temp/ +api/tmp/ +api/temp/ + +# ============================================ +# OS FILES +# ============================================ +.DS_Store +Thumbs.db +desktop.ini diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0751391 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,338 @@ +# Contributing to MediVault AI + +Thanks for your interest in contributing to MediVault AI. + +MediVault AI is an open-source offline clinical intelligence platform built with a FastAPI backend, a React frontend, Flowise for LLM chain orchestration, ChromaDB for vector storage, and Whisper ASR for speech-to-text — all running locally with no cloud dependencies. We welcome improvements across the codebase, documentation, bug reports, design feedback, and workflow polish. + +Before you start, read the relevant section below. It helps keep contributions focused, reviewable, and aligned with the current project setup. + +--- + +## Quick Setup Checklist + +Before you dive in, make sure you have these installed: + +```bash +# Check Python (3.11+ recommended) +python --version + +# Check Node.js (18+ recommended) +node --version + +# Check npm +npm --version + +# Check Docker +docker --version +docker compose version + +# Check Git +git --version +``` + +New to contributing? + +1. Open an issue or pick an existing one to work on. +2. Fork the repo and create a branch from `main`. +3. Follow the local setup guide below. +4. Run the app locally and verify your change before opening a PR. + +## Table of contents + +- [How do I...?](#how-do-i) + - [Get help or ask a question?](#get-help-or-ask-a-question) + - [Report a bug?](#report-a-bug) + - [Suggest a new feature?](#suggest-a-new-feature) + - [Fork and clone the repo?](#fork-and-clone-the-repo) + - [Set up MediVault AI locally?](#set-up-medivault-ai-locally) + - [Start contributing code?](#start-contributing-code) + - [Improve the documentation?](#improve-the-documentation) + - [Submit a pull request?](#submit-a-pull-request) +- [Branching model](#branching-model) +- [Commit conventions](#commit-conventions) +- [Code guidelines](#code-guidelines) +- [Pull request checklist](#pull-request-checklist) +- [Thank you](#thank-you) + +--- + +## How do I... + +### Get help or ask a question? + +- Start with the main project docs in [`README.md`](./README.md), [`TROUBLESHOOTING.md`](./TROUBLESHOOTING.md), [`SECURITY.md`](./SECURITY.md), and [`.env.example`](./.env.example). +- If something is unclear, open a GitHub issue with your question and the context you already checked. + +### Report a bug? + +1. Search existing issues first. +2. If the bug is new, open a GitHub issue. +3. Include your environment, what happened, what you expected, and exact steps to reproduce. +4. Add screenshots, logs, request details, or response payloads if relevant. + +### Suggest a new feature? + +1. Open a GitHub issue describing the feature. +2. Explain the problem, who it helps, and how it fits MediVault AI. +3. If the change is large, get alignment in the issue before writing code. + +### Fork and clone the repo? + +All contributions should come from a **fork** of the repository. This keeps the upstream repo clean and lets maintainers review changes via pull requests. + +#### Step 1: Fork the repository + +Click the **Fork** button at the top-right of the [MediVault AI repo](https://github.com/cld2labs/MediVaultAI) to create a copy under your GitHub account. + +#### Step 2: Clone your fork + +```bash +git clone https://github.com//MediVaultAI.git +cd MediVaultAI +``` + +#### Step 3: Add the upstream remote + +```bash +git remote add upstream https://github.com/cld2labs/MediVaultAI.git +``` + +This lets you pull in the latest changes from the original repo. + +#### Step 4: Create a branch + +Always branch off `main`. See [Branching model](#branching-model) for naming conventions. + +```bash +git checkout main +git pull upstream main +git checkout -b / +``` + +### Set up MediVault AI locally? + +#### Prerequisites + +- Python 3.11+ +- Node.js 18+ and npm +- Git +- Docker with Docker Compose v2 +- Ollama installed and running on the host machine with the required models: + +```bash +ollama pull llama3.1:8b +ollama pull nomic-embed-text +``` + +#### Option 1: Local development + +##### Step 1: Configure environment variables + +Create a root `.env` file from the example: + +```bash +cp .env.example .env +``` + +At minimum, confirm the Ollama and service URLs match your environment: + +```env +OLLAMA_BASE_URL=http://host.docker.internal:11434 +OLLAMA_MODEL=llama3.1:8b +OLLAMA_EMBED_MODEL=nomic-embed-text +``` + +For local backend development (outside Docker), set ChromaDB and Whisper to localhost: + +```env +CHROMA_HOST=localhost +WHISPER_ENDPOINT=http://localhost:9000 +``` + +##### Step 2: Install backend dependencies + +```bash +cd api +python -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +cd .. +``` + +##### Step 3: Install frontend dependencies + +```bash +cd ui +npm install +cd .. +``` + +##### Step 4: Start the required containers + +```bash +docker compose up medivault-chromadb medivault-whisper medivault-flowise +``` + +##### Step 5: Start the backend + +```bash +cd api +source .venv/bin/activate +uvicorn server:app --reload --port 5001 +``` + +The backend runs at `http://localhost:5001`. + +##### Step 6: Start the frontend + +Open a second terminal: + +```bash +cd ui +npm run dev +``` + +The Vite dev server runs at `http://localhost:5173`. + +##### Step 7: Access the application + +- Frontend: `http://localhost:5173` +- Backend health check: `http://localhost:5001/health` +- API docs: `http://localhost:5001/docs` + +#### Option 2: Docker + +From the repository root: + +```bash +cp .env.example .env +docker compose up --build +``` + +This starts: + +- Frontend on `http://localhost:3000` +- Backend on `http://localhost:5001` +- Flowise on `http://localhost:3001` +- ChromaDB on `http://localhost:8100` +- Whisper ASR on `http://localhost:9000` + +#### Common troubleshooting + +- If ports `3000`, `3001`, `5001`, `8100`, or `9000` are already in use, stop the conflicting process before starting MediVault AI. +- If Ollama is unreachable from containers, confirm `host.docker.internal` resolves. On Linux, add `extra_hosts: ["host.docker.internal:host-gateway"]` to the affected services in `docker-compose.yaml`. +- If the Whisper container shows `whisper_connected: false`, wait up to 5 minutes on first run for the model to download. +- If Docker fails to build, rebuild with `docker compose up --build`. +- If Python packages fail to install, confirm you are using a supported Python version. + +### Start contributing code? + +1. Open or choose an issue. +2. [Fork the repo](#fork-and-clone-the-repo) and create a feature branch from `main`. +3. Keep the change focused on a single problem. +4. Run the app locally and verify the affected workflow. +5. Update docs when behavior, setup, configuration, or architecture changes. +6. Open a pull request back to upstream `main`. + +### Improve the documentation? + +Documentation updates are welcome. Relevant files currently live in: + +- [`README.md`](./README.md) +- [`CONTRIBUTING.md`](./CONTRIBUTING.md) +- [`TROUBLESHOOTING.md`](./TROUBLESHOOTING.md) +- [`SECURITY.md`](./SECURITY.md) +- [`DISCLAIMER.md`](./DISCLAIMER.md) + +### Submit a pull request? + +1. Push your branch to your fork. +2. Go to the [MediVault AI repo](https://github.com/cld2labs/MediVaultAI) and click **Compare & pull request**. +3. Set the base branch to `main`. +4. Fill in the PR template (it loads automatically). +5. Submit the pull request. + +A maintainer will review your PR. You may be asked to make changes — push additional commits to the same branch and they will be added to the PR automatically. + +Before opening your PR, sync with upstream to avoid merge conflicts: + +```bash +git fetch upstream +git rebase upstream/main +``` + +Follow the checklist below and the [Pull request checklist](#pull-request-checklist) section. + +--- + +## Branching model + +- Fork the repo and base new work from `main`. +- Open pull requests against upstream `main`. +- Use descriptive branch names with a type prefix: + +| Prefix | Use | +|---|---| +| `feat/` | New features or enhancements | +| `fix/` | Bug fixes | +| `docs/` | Documentation changes | +| `refactor/` | Code restructuring (no behavior change) | +| `chore/` | Dependency updates, CI changes, tooling | + +Examples: `feat/add-specialty-selector`, `fix/whisper-timeout`, `docs/update-api-reference` + +--- + +## Commit conventions + +Use [Conventional Commits](https://www.conventionalcommits.org/) format: + +``` +(): +``` + +Examples: + +```bash +git commit -m "feat(api): add billing codes endpoint" +git commit -m "fix(ui): resolve diarization label alignment" +git commit -m "docs: update environment variables table" +``` + +Keep commits focused — one logical change per commit. + +--- + +## Code guidelines + +- Follow the existing project structure and patterns before introducing new abstractions. +- Keep frontend changes consistent with the React + Vite + Tailwind setup already in use. +- Keep backend changes consistent with the FastAPI service structure in [`api`](./api). +- Avoid unrelated refactors in the same pull request. +- Do not commit secrets, API keys, audio files, local `.env` files, or generated artifacts. +- Do not include real patient data in any issue, log snippet, attachment, or test asset. +- Prefer clear, small commits and descriptive pull request summaries. +- Update documentation when contributor setup, behavior, environment variables, or API usage changes. + +--- + +## Pull request checklist + +Before submitting your pull request, confirm the following: + +- You tested the affected flow locally. +- The application still starts successfully in the environment you changed. +- You removed debug code, stray logs, and commented-out experiments. +- You documented any new setup steps, environment variables, or behavior changes. +- You kept the pull request scoped to one issue or topic. +- You added screenshots for UI changes when relevant. +- You did not commit secrets, patient data, or local generated data. +- You are opening the pull request against `main`. + +If one or more of these are missing, the pull request may be sent back for changes before review. + +--- + +## Thank you + +Thanks for contributing to MediVault AI. Whether you're fixing a bug, improving the docs, or refining the product experience, your work helps make the project more useful and easier to maintain. diff --git a/DISCLAIMER.md b/DISCLAIMER.md new file mode 100644 index 0000000..5506cfd --- /dev/null +++ b/DISCLAIMER.md @@ -0,0 +1,22 @@ +# Disclaimer + +This blueprint is provided by Cloud2 Labs "as is" and "as available" for +educational and demonstration purposes only. + +The **MediVault AI — Offline Clinical Intelligence Platform** blueprint is a reference +implementation and does not constitute a production-ready system or +regulatory-compliant solution. + +This software is not designed to provide professional medical, clinical, legal, +or compliance advice. All AI-generated outputs produced by this blueprint — +including SOAP notes, ICD-10 diagnosis codes, CPT procedure codes, and clinical +Q&A responses — are drafts only and require independent review and approval by +a licensed clinician before use in any clinical context or patient record. + +Cloud2 Labs does not assume responsibility or liability for any clinical harm, +data loss, security incident, service disruption, regulatory non-compliance, or +adverse outcome resulting from the use or modification of this blueprint. + +Do not use this system with real patient data in any environment unless your +organisation has implemented all applicable compliance measures required by +HIPAA, GDPR, and any other relevant health information privacy regulations. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..ad2904b --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +© 2026 cld2labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..eb1605c --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,38 @@ +# Security Policy + +The **MediVault AI — Offline Clinical Intelligence Platform** blueprint does not include +production-grade security controls. + +This repository is not secure by default and must not be used in production +without a comprehensive security review. + +## Known Considerations + +- **Flowise API key**: `FLOWISE_API_KEY` is loaded from `.env`. + Never commit `.env` to version control. Leave blank only in local development. +- **Flowise authentication**: `FLOWISE_USERNAME` and `FLOWISE_PASSWORD` default to + `admin` / `changeme`. Change these before any non-local deployment. +- **CORS**: The FastAPI backend defaults to permissive CORS in development. Restrict + allowed origins in any non-local deployment. +- **ChromaDB exposure**: ChromaDB is exposed on host port `8100` with no authentication. + Do not expose this port outside a trusted local network. +- **Ollama exposure**: Ollama runs on the host at port `11434` with no authentication. + Restrict access at the network level in any shared or production environment. +- **Real patient data**: Never use real patient data in development, staging, or + demonstration environments without full regulatory compliance measures in place. + +## User Responsibilities + +Users are responsible for implementing appropriate: + +- Authentication and authorization mechanisms for the UI and API +- TLS termination via a reverse proxy for any non-localhost deployment +- Network-level access controls and firewall rules +- Encryption and secure data storage for any persisted clinical data +- Monitoring, logging, and auditing +- HIPAA, GDPR, and regulatory compliance safeguards relevant to their deployment environment + +## Reporting a Vulnerability + +If you discover a security vulnerability in this blueprint, please report it +privately to the Cloud2 Labs maintainers rather than opening a public issue. diff --git a/TERMS_AND_CONDITIONS.md b/TERMS_AND_CONDITIONS.md new file mode 100644 index 0000000..dee8c60 --- /dev/null +++ b/TERMS_AND_CONDITIONS.md @@ -0,0 +1,19 @@ +# Terms and Conditions + +This repository contains the **MediVault AI — Offline Clinical Intelligence Platform** blueprint +maintained by Cloud2 Labs. + +By accessing or using this blueprint, you acknowledge and agree that: + +- This blueprint is provided solely for educational and demonstration purposes +- You are solely responsible for deployment, configuration, and usage +- You are responsible for all data handling, security controls, and compliance +- Every AI-generated output — including SOAP notes, ICD-10 codes, CPT codes, and clinical Q&A + responses — must be reviewed and approved by a licensed clinician before clinical use +- You are responsible for ensuring compliance with HIPAA, GDPR, and any applicable health + information privacy regulations in your deployment environment +- Real patient data must not be used in any environment without full compliance measures in place +- Cloud2 Labs provides no warranties or guarantees of any kind + +Cloud2 Labs does not support or recommend production deployment of this blueprint +without a thorough security review and appropriate hardening. diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 0000000..1b69d42 --- /dev/null +++ b/TROUBLESHOOTING.md @@ -0,0 +1,164 @@ +# Troubleshooting Guide + +This document contains all common issues encountered during development and their solutions. + +## Table of Contents + +- [API Common Issues](#api-common-issues) +- [UI Common Issues](#ui-common-issues) + +### API Common Issues + +#### Flowise shows `connecting` or `flowise_connected: false` + +**Solution**: + +1. Wait up to 30 seconds after `docker compose up` — Flowise requires time to initialise before flows can be provisioned +2. Verify the Flowise container is running: `docker compose ps` +3. Check the API provisioning log: `docker compose logs medivault-api | grep -i "provision\|flowise\|error"` +4. If `FLOWISE_API_KEY` is set in `.env`, confirm the key matches one generated in the Flowise UI at `http://localhost:3001` — an incorrect key causes all provisioning calls to return 401 +5. Restart the API after correcting the key: `docker compose restart medivault-api` + +#### Whisper shows `whisper_connected: false` + +**Solution**: + +1. On first startup the Whisper container downloads the `small` model (~500 MB) — allow up to 5 minutes and monitor progress: `docker compose logs -f medivault-whisper` +2. Confirm the container is running: `docker compose ps medivault-whisper` +3. Verify the Whisper endpoint is reachable from the host: `curl http://localhost:9000/health` +4. If the container exited, inspect the logs and restart: `docker compose restart medivault-whisper` + +#### Transcription returns empty or fails + +**Solution**: + +1. Confirm the Whisper service is healthy before submitting audio +2. Verify the audio file plays correctly on the host machine before uploading +3. Ensure the file is WAV or MP3 and does not exceed 25 MB +4. If using a non-standard codec, convert to WAV before uploading: + ```bash + ffmpeg -i input.m4a -ar 16000 -ac 1 output.wav + ``` +5. Check the API logs for the specific error: `docker compose logs medivault-api | grep -i "transcri\|whisper\|error"` + +#### SOAP generation fails or returns malformed JSON + +**Solution**: + +1. Confirm `/health` reports `flowise_connected: true` and `flows_provisioned: true`: `curl http://localhost:5001/health` +2. Verify Ollama is running and the model is available: `ollama list` +3. Test Ollama directly: `curl http://localhost:11434/api/tags` +4. If Flowise flows are missing, restart the API to re-trigger provisioning: `docker compose restart medivault-api` +5. Inspect the Flowise SOAP Generator flow at `http://localhost:3001` and confirm the `ChatOllama` node base URL is `http://host.docker.internal:11434` + +#### No matching information in Clinical QA after approving notes + +**Solution**: + +1. Confirm `nomic-embed-text` is pulled: `ollama list` +2. Pull the model if missing: `ollama pull nomic-embed-text` +3. Verify the approve operation succeeded by checking the API logs: `docker compose logs medivault-api | grep -i "approve\|chroma\|embed\|error"` +4. Confirm ChromaDB is reachable and the collection exists: `curl http://localhost:8100/api/v1/collections` +5. Re-approve the note after confirming Ollama and ChromaDB are both healthy + +#### Billing codes not appearing + +**Solution**: + +1. Confirm a complete SOAP note with all sections populated was submitted before requesting billing codes +2. Check the API logs for the billing request: `docker compose logs medivault-api | grep -i "billing\|icd\|cpt\|error"` +3. Verify Ollama is responsive: `curl http://localhost:11434/api/tags` +4. If Ollama is slow on CPU hardware, wait for the previous LLM call to complete before submitting the billing request — concurrent requests may time out + +#### PDF ingestion fails + +**Solution**: + +1. Confirm the file is under 10 MB +2. Verify the PDF contains a text layer: `pdftotext document.pdf -` +3. If the output is empty, the PDF is image-only — run OCR before ingesting: `ocrmypdf input.pdf output.pdf` +4. Check the API logs for the rejection reason: `docker compose logs medivault-api | grep -i "ingest\|pdf\|error"` + +#### ChromaDB connection errors + +**Solution**: + +1. Confirm ChromaDB is running: `docker compose ps medivault-chromadb` +2. Verify ChromaDB responds: `curl http://localhost:8100/api/v1/heartbeat` +3. Confirm `CHROMA_HOST` is set to `medivault-chromadb` and `CHROMA_PORT` to `8000` in `.env` — the backend communicates on the internal Docker network port, not 8100 +4. Restart ChromaDB if the container exited: `docker compose restart medivault-chromadb` + +#### Ollama not responding + +**Solution**: + +1. Verify Ollama is running on the host: `ollama list` and `curl http://localhost:11434/api/tags` +2. Start Ollama if it is not running: `ollama serve` +3. Confirm the required models are pulled: + ```bash + ollama pull llama3.1:8b + ollama pull nomic-embed-text + ``` +4. On Linux, confirm `extra_hosts` is present in `docker-compose.yaml` for services that need to reach the host: + ```yaml + extra_hosts: + - "host.docker.internal:host-gateway" + ``` +5. Confirm `OLLAMA_BASE_URL` in `.env` is set to `http://host.docker.internal:11434` + +#### Import errors or server won't start + +**Solution**: + +1. Ensure all dependencies are installed: `pip install -r requirements.txt` +2. Verify you are using Python 3.11 or higher: `python --version` +3. Activate your virtual environment if using one +4. Check if port 5001 is already in use: `lsof -i :5001` (Unix) or `netstat -ano | findstr :5001` (Windows) +5. Use a different port by updating `BACKEND_PORT` in `.env` + +## UI Common Issues + +### API Connection Issues + +**Problem**: "Failed to transcribe", "Failed to generate SOAP note", or API shows offline + +**Solution**: + +1. Ensure the API server is running on `http://localhost:5001` +2. Check browser console for detailed errors +3. Verify CORS is enabled in the API +4. Test API directly: `curl http://localhost:5001/health` + +### Build Issues + +**Problem**: Build fails with dependency errors + +**Solution**: + +```bash +# Clear node_modules and reinstall +rm -rf node_modules package-lock.json +npm install +``` + +### Styling Issues + +**Problem**: Styles not applying + +**Solution**: + +```bash +# Rebuild Tailwind CSS +npm run dev +``` + +### UI Shows Blank Page + +**Problem**: Blank page on load or UI container shows errors + +**Solution**: + +1. Check the UI container logs: `docker compose logs medivault-ui` +2. Check the API container logs: `docker compose logs medivault-api` +3. Rebuild the UI container: `docker compose up --build medivault-ui` +4. Confirm the API is reachable: `curl http://localhost:5001/health` diff --git a/api/.dockerignore b/api/.dockerignore new file mode 100644 index 0000000..94f0e15 --- /dev/null +++ b/api/.dockerignore @@ -0,0 +1,13 @@ +__pycache__/ +*.pyc +*.pyo +*.pyd +.Python +.env +.env.* +!.env.example +*.egg-info/ +dist/ +build/ +.git/ +.gitignore diff --git a/api/Dockerfile b/api/Dockerfile new file mode 100644 index 0000000..2cb809b --- /dev/null +++ b/api/Dockerfile @@ -0,0 +1,17 @@ +FROM python:3.11-slim + +RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +RUN groupadd -r appuser && useradd -r -g appuser -u 1000 appuser +USER appuser + +EXPOSE 5001 + +CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "5001"] diff --git a/api/config.py b/api/config.py new file mode 100644 index 0000000..6a37662 --- /dev/null +++ b/api/config.py @@ -0,0 +1,52 @@ +import os +from dotenv import load_dotenv + +load_dotenv() + +# ── Flowise ──────────────────────────────────────────────────────────────── +FLOWISE_ENDPOINT = os.getenv("FLOWISE_ENDPOINT", "http://medivault-flowise:3001") +FLOWISE_API_KEY = os.getenv("FLOWISE_API_KEY", "") + +SOAP_FLOW_ID = os.getenv("SOAP_FLOW_ID", "") +QA_FLOW_ID = os.getenv("QA_FLOW_ID", "") +UPSERT_FLOW_ID = os.getenv("UPSERT_FLOW_ID", "") + +# ── Ollama ───────────────────────────────────────────────────────────────── +OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://host.docker.internal:11434") +OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "llama3.1:8b") +OLLAMA_EMBED_MODEL = os.getenv("OLLAMA_EMBED_MODEL", "nomic-embed-text") + +# ── ChromaDB ─────────────────────────────────────────────────────────────── +CHROMA_HOST = os.getenv("CHROMA_HOST", "host.docker.internal") +CHROMA_PORT = int(os.getenv("CHROMA_PORT", "8100")) + +# ── Whisper ──────────────────────────────────────────────────────────────── +WHISPER_ENDPOINT = os.getenv("WHISPER_ENDPOINT", "http://host.docker.internal:8080") + +# ── Limits ───────────────────────────────────────────────────────────────── +MAX_AUDIO_SIZE = int(os.getenv("MAX_AUDIO_SIZE", str(50 * 1024 * 1024))) +MAX_FILE_SIZE = int(os.getenv("MAX_FILE_SIZE", str(10 * 1024 * 1024))) +BACKEND_PORT = int(os.getenv("BACKEND_PORT", "5001")) + +# ── App ──────────────────────────────────────────────────────────────────── +CORS_ALLOW_ORIGINS = ["*"] +CORS_ALLOW_CREDENTIALS = True +CORS_ALLOW_METHODS = ["*"] +CORS_ALLOW_HEADERS = ["*"] + +APP_TITLE = "MediVault AI API" +APP_DESCRIPTION = "Offline clinical intelligence — SOAP note generation and clinical decision support" +APP_VERSION = "2.0.0" + +SUPPORTED_SPECIALTIES = [ + "general", "emergency", "cardiology", "pediatrics", + "psychiatry", "orthopedics", "dermatology", "neurology", + "oncology", "gastroenterology", +] + +ALLOWED_AUDIO_EXTENSIONS = {".wav", ".mp3", ".m4a", ".ogg", ".webm", ".flac"} +ALLOWED_PDF_EXTENSIONS = {".pdf"} + +SOAP_FLOW_NAME = "MediVault SOAP Generator" +QA_FLOW_NAME = "MediVault Clinical QA" +UPSERT_FLOW_NAME = "MediVault KB Upsert" diff --git a/api/models.py b/api/models.py new file mode 100644 index 0000000..54651e4 --- /dev/null +++ b/api/models.py @@ -0,0 +1,116 @@ +from pydantic import BaseModel, Field +from typing import Optional, Literal + + +class DiarizedSegment(BaseModel): + speaker: Literal["Doctor", "Patient"] + text: str + start_ms: int = 0 + + +class TranscribeResponse(BaseModel): + transcript: str + segments: list[DiarizedSegment] = [] + + +class SoapNote(BaseModel): + chief_complaint: str = "" + subjective: str = "" + objective: str = "" + assessment: str = "" + plan: str = "" + + +class SoapKeywords(BaseModel): + symptoms: list[str] = [] + medications: list[str] = [] + diagnoses: list[str] = [] + + +class BillingCode(BaseModel): + code: str + description: str + + +class BillingCodesResponse(BaseModel): + cpt: list[BillingCode] = [] + icd10: list[BillingCode] = [] + + +class GenerateBillingRequest(BaseModel): + soap: SoapNote + specialty: str = "general" + + +class GenerateSoapRequest(BaseModel): + transcript: str = Field(..., min_length=1) + segments: list[DiarizedSegment] = [] + specialty: str = Field(default="general") + + +class GenerateSoapResponse(BaseModel): + soap: SoapNote + keywords: SoapKeywords = Field(default_factory=SoapKeywords) + specialty: str + raw_response: Optional[str] = None + + +class ApproveNoteRequest(BaseModel): + soap: SoapNote + specialty: str = "general" + patient_ref: str = "" + billing: Optional[BillingCodesResponse] = None + keywords: Optional[SoapKeywords] = None + + +class ApproveNoteResponse(BaseModel): + message: str + document_id: str + status: str + + +class ChatRequest(BaseModel): + question: str = Field(..., min_length=1) + session_id: Optional[str] = None + + +class CitedSource(BaseModel): + document: str + chunk: str + doc_type: str = "guideline" + score: Optional[float] = None + + +class ChatResponse(BaseModel): + answer: str + sources: list[CitedSource] = [] + session_id: Optional[str] = None + + +class IngestDocumentResponse(BaseModel): + message: str + document_id: str + status: str + + +class DocumentRecord(BaseModel): + id: str + filename: str + size_bytes: int + ingested_at: str + doc_type: str = "guideline" + patient_ref: str = "" + specialty: str = "" + + +class DocumentsListResponse(BaseModel): + documents: list[DocumentRecord] + total: int + + +class HealthResponse(BaseModel): + status: str + flowise_connected: bool + whisper_connected: bool + flows_provisioned: bool + version: str diff --git a/api/requirements.txt b/api/requirements.txt new file mode 100644 index 0000000..aaa90fe --- /dev/null +++ b/api/requirements.txt @@ -0,0 +1,9 @@ +fastapi==0.115.5 +uvicorn==0.32.1 +pydantic==2.10.3 +python-multipart>=0.0.18 +httpx==0.28.1 +pypdf==6.1.1 +python-dotenv==1.0.1 +tenacity==9.0.0 +chromadb-client==1.0.9 diff --git a/api/server.py b/api/server.py new file mode 100644 index 0000000..a9b539e --- /dev/null +++ b/api/server.py @@ -0,0 +1,543 @@ +import os +import json +import uuid +import tempfile +import logging +from datetime import datetime, timezone +from contextlib import asynccontextmanager + +from fastapi import FastAPI, File, UploadFile, HTTPException, status +from fastapi.middleware.cors import CORSMiddleware + +import config +from models import ( + TranscribeResponse, + GenerateSoapRequest, GenerateSoapResponse, SoapNote, SoapKeywords, + GenerateBillingRequest, BillingCodesResponse, BillingCode, + ApproveNoteRequest, ApproveNoteResponse, + ChatRequest, ChatResponse, CitedSource, + IngestDocumentResponse, DocumentRecord, DocumentsListResponse, + HealthResponse, +) +from services import ( + get_flowise_client, get_whisper_client, + provision, is_provisioned, + extract_text_from_pdf, validate_pdf_file, + generate_soap as llm_generate_soap, + generate_billing_codes as llm_generate_billing_codes, + answer_question as llm_answer_question, + diarize_segments, + upsert_document, query_documents, + delete_document as chroma_delete_document, + chroma_is_connected, +) + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", +) +logger = logging.getLogger(__name__) + +_documents_store: list[DocumentRecord] = [] + + +@asynccontextmanager +async def lifespan(app: FastAPI): + app.state.flowise = get_flowise_client() + app.state.whisper = get_whisper_client() + logger.info("Starting Flowise flow provisioning...") + provision() + logger.info(f"Flows provisioned: {is_provisioned()} — SOAP={config.SOAP_FLOW_ID} QA={config.QA_FLOW_ID}") + yield + logger.info("MediVault AI API shutdown") + + +app = FastAPI( + title=config.APP_TITLE, + description=config.APP_DESCRIPTION, + version=config.APP_VERSION, + lifespan=lifespan, +) + +app.add_middleware( + CORSMiddleware, + allow_origins=config.CORS_ALLOW_ORIGINS, + allow_credentials=config.CORS_ALLOW_CREDENTIALS, + allow_methods=config.CORS_ALLOW_METHODS, + allow_headers=config.CORS_ALLOW_HEADERS, +) + + +@app.get("/") +def root(): + return { + "message": "MediVault AI API", + "version": config.APP_VERSION, + "status": "healthy", + } + + +@app.get("/health", response_model=HealthResponse) +def health_check(): + return HealthResponse( + status="healthy", + flowise_connected=app.state.flowise.is_connected(), + whisper_connected=app.state.whisper.is_connected(), + flows_provisioned=is_provisioned(), + version=config.APP_VERSION, + ) + + +@app.post("/transcribe", response_model=TranscribeResponse) +async def transcribe_audio(file: UploadFile = File(...)): + content = await file.read() + file_size = len(content) + + if file_size > config.MAX_AUDIO_SIZE: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Audio file too large ({file_size / 1024 / 1024:.1f} MB). Max {config.MAX_AUDIO_SIZE / 1024 / 1024:.0f} MB.", + ) + + ext = os.path.splitext(file.filename or "audio.wav")[1].lower() + if ext not in config.ALLOWED_AUDIO_EXTENSIONS: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Unsupported audio format '{ext}'.", + ) + + try: + logger.info(f"Transcribing: {file.filename} ({file_size / 1024:.1f} KB)") + transcript, segments = app.state.whisper.transcribe(content, file.filename or "audio.wav") + if not transcript.strip(): + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail="Transcription produced no output. Check audio quality.", + ) + logger.info(f"Transcribed {len(transcript)} chars, {len(segments)} segments") + return TranscribeResponse(transcript=transcript, segments=segments) + except HTTPException: + raise + except Exception as e: + logger.error(f"Transcription error: {e}", exc_info=True) + raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=str(e)) + + +@app.post("/generate-soap", response_model=GenerateSoapResponse) +def generate_soap_endpoint(request: GenerateSoapRequest): + diarized_text = "\n".join( + f"{seg.speaker}: {seg.text}" + for seg in request.segments + ) if request.segments else request.transcript + + prompt = ( + f"Specialty: {request.specialty}\n\n" + f"Consultation Transcript:\n{diarized_text}\n\n" + "Generate the SOAP note JSON now." + ) + + raw = None + + # Primary: Flowise SOAP LLMChain flow + if config.SOAP_FLOW_ID: + try: + logger.info("Generating SOAP note via Flowise — flow=%s specialty=%s", config.SOAP_FLOW_ID, request.specialty) + result = app.state.flowise.predict(config.SOAP_FLOW_ID, prompt) + # LLMChain returns {"text": "..."} + raw = result.get("text") or result.get("output") or result.get("answer") or "" + if raw: + logger.info("SOAP generated via Flowise (%d chars)", len(raw)) + except Exception as fe: + logger.warning("Flowise SOAP generation failed (%s) — falling back to direct Ollama", fe) + raw = None + + # Fallback: direct Ollama call + if not raw: + try: + logger.info("Generating SOAP note via direct Ollama — model=%s specialty=%s", config.OLLAMA_MODEL, request.specialty) + raw = llm_generate_soap(prompt) + except Exception as e: + logger.error("SOAP generation error: %s", e, exc_info=True) + raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=str(e)) + + try: + soap, keywords = _parse_soap_json(raw) + logger.info("SOAP note generated — symptoms=%d medications=%d diagnoses=%d", + len(keywords.symptoms), len(keywords.medications), len(keywords.diagnoses)) + return GenerateSoapResponse(soap=soap, keywords=keywords, specialty=request.specialty, raw_response=raw) + except Exception as e: + logger.error("SOAP parse error: %s", e, exc_info=True) + raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=str(e)) + + +def _coerce_str(value, _depth: int = 0) -> str: + """Normalise a value that may be a str, list, or dict into a readable plain string.""" + if isinstance(value, str): + return value.strip() + if isinstance(value, list): + parts = [_coerce_str(v, _depth + 1) for v in value if v not in (None, "", [], {})] + if _depth == 0: + # Top-level list → join as sentences + return " ".join(p.rstrip(".") + "." for p in parts if p).strip() + return ", ".join(p for p in parts if p) + if isinstance(value, dict): + # Try common clinical key patterns for readable output + parts = [] + # Render key: value pairs, skipping empty values + for k, v in value.items(): + coerced = _coerce_str(v, _depth + 1) + if not coerced: + continue + # Use readable key names + key_label = k.replace("_", " ").strip() + if key_label in ("description", "text", "name", "diagnosis", "finding"): + # Lead with the main value, no key prefix + parts.insert(0, coerced) + elif key_label in ("differential diagnoses", "referrals", "treatment", "follow up", + "medications", "history", "symptoms", "tests ordered"): + # Skip empty sub-lists + if coerced and coerced not in (".", ""): + parts.append(coerced) + else: + parts.append(f"{key_label}: {coerced}") + return " ".join(parts).strip() + return str(value).strip() if value else "" + + +def _strip_inline_sources(text: str) -> str: + """ + Remove inline [Source: ...] tags the LLM embeds in answers. + The UI renders sources separately as citation boxes below the answer, + so inline tags are redundant and visually messy. + Handles: [Source: X], (Source: X), [Source: X, Y], **[Source: X]** + """ + import re + # Remove markdown-bold wrapped variants first + text = re.sub(r'\*+\[Source:[^\]]*\]\*+', '', text, flags=re.IGNORECASE) + # Remove bracketed [Source: ...] + text = re.sub(r'\[Source:[^\]]*\]', '', text, flags=re.IGNORECASE) + # Remove parenthesised (Source: ...) + text = re.sub(r'\(Source:[^)]*\)', '', text, flags=re.IGNORECASE) + # Remove "Refer to ..." trailing citations + text = re.sub(r'\(Refer to [^)]*\)', '', text, flags=re.IGNORECASE) + return text.strip() + + +def _strip_fences(raw: str) -> str: + """Remove markdown code fences from LLM output.""" + text = raw.strip() + if not text.startswith("```"): + return text + # Remove opening fence + optional language tag (e.g. ```json) + lines = text.splitlines() + # Drop first line (the opening fence) + lines = lines[1:] + # Drop last line if it's a closing fence + if lines and lines[-1].strip() == "```": + lines = lines[:-1] + return "\n".join(lines).strip() + + +def _parse_soap_json(raw: str) -> tuple[SoapNote, SoapKeywords]: + """ + Parse raw LLM output into a (SoapNote, SoapKeywords) tuple. + Strips markdown fences if the model wraps its response despite instructions. + Coerces array/dict fields to strings (llama3.2 sometimes returns arrays for list-like fields). + Falls back to SoapNote(subjective=raw) with empty keywords on parse failure. + """ + try: + cleaned = _strip_fences(raw) + start = cleaned.find("{") + end = cleaned.rfind("}") + 1 + if start != -1 and end > start: + data = json.loads(cleaned[start:end]) + soap = SoapNote( + chief_complaint=_coerce_str(data.get("chief_complaint", "")), + subjective=_coerce_str(data.get("subjective", "")), + objective=_coerce_str(data.get("objective", "")), + assessment=_coerce_str(data.get("assessment", data.get("assessment", ""))), + plan=_coerce_str(data.get("plan", "")), + ) + raw_kw = data.get("keywords", {}) + if isinstance(raw_kw, dict): + keywords = SoapKeywords( + symptoms=[str(s) for s in raw_kw.get("symptoms", []) if s], + medications=[str(m) for m in raw_kw.get("medications", []) if m], + diagnoses=[str(d) for d in raw_kw.get("diagnoses", []) if d], + ) + else: + keywords = SoapKeywords() + return soap, keywords + except Exception as e: + logger.warning("SOAP JSON parse failed: %s — using raw text as subjective", e) + return SoapNote(subjective=raw), SoapKeywords() + + +def _parse_billing_json(raw: str) -> BillingCodesResponse: + """ + Parse raw LLM output into BillingCodesResponse. + Strips markdown fences and extracts the first JSON object found. + """ + try: + cleaned = _strip_fences(raw) + start = cleaned.find("{") + end = cleaned.rfind("}") + 1 + if start != -1 and end > start: + data = json.loads(cleaned[start:end]) + cpt = [BillingCode(code=c["code"], description=c["description"]) for c in data.get("cpt", []) if "code" in c] + icd10 = [BillingCode(code=c["code"], description=c["description"]) for c in data.get("icd10", []) if "code" in c] + return BillingCodesResponse(cpt=cpt, icd10=icd10) + except Exception as e: + logger.warning("Billing JSON parse failed: %s", e) + return BillingCodesResponse() + + +@app.post("/generate-billing", response_model=BillingCodesResponse) +def generate_billing_endpoint(request: GenerateBillingRequest): + """ + Generate ICD-10 and CPT billing codes from an approved SOAP note. + Called after the clinician has reviewed the SOAP note — not during generation. + Returns 1-3 CPT codes (E&M / procedures) and 1-3 ICD-10 codes (diagnoses). + All codes are AI suggestions only and must be verified by a qualified medical coder. + """ + soap_text = ( + f"Specialty: {request.specialty}\n\n" + f"Chief Complaint: {request.soap.chief_complaint}\n" + f"Subjective: {request.soap.subjective}\n" + f"Objective: {request.soap.objective}\n" + f"Assessment: {request.soap.assessment}\n" + f"Plan: {request.soap.plan}" + ) + + try: + logger.info("Generating billing codes — model=%s, specialty=%s", config.OLLAMA_MODEL, request.specialty) + raw = llm_generate_billing_codes(soap_text) + result = _parse_billing_json(raw) + logger.info("Billing codes generated — CPT=%d ICD-10=%d", len(result.cpt), len(result.icd10)) + return result + except HTTPException: + raise + except Exception as e: + logger.error("Billing code generation error: %s", e, exc_info=True) + raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=str(e)) + + +@app.post("/approve-note", response_model=ApproveNoteResponse) +def approve_note(request: ApproveNoteRequest): + doc_id = str(uuid.uuid4()) + patient_label = request.patient_ref or "Anonymous" + + soap_text = ( + f"SOAP Note\n" + f"Patient Reference: {patient_label}\n" + f"Specialty: {request.specialty}\n\n" + f"Chief Complaint:\n{request.soap.chief_complaint}\n\n" + f"Subjective:\n{request.soap.subjective}\n\n" + f"Objective:\n{request.soap.objective}\n\n" + f"Assessment:\n{request.soap.assessment}\n\n" + f"Plan:\n{request.soap.plan}" + ) + + # Append keywords if provided + if request.keywords: + kw = request.keywords + if kw.symptoms or kw.medications or kw.diagnoses: + soap_text += ( + f"\n\nKeywords:\n" + f"Symptoms: {', '.join(kw.symptoms)}\n" + f"Medications: {', '.join(kw.medications)}\n" + f"Diagnoses: {', '.join(kw.diagnoses)}" + ) + + # Append billing codes if provided — makes ICD/CPT searchable in Clinical QA + if request.billing: + icd_lines = "\n".join(f" {c.code} — {c.description}" for c in request.billing.icd10) + cpt_lines = "\n".join(f" {c.code} — {c.description}" for c in request.billing.cpt) + soap_text += ( + f"\n\nBilling Codes:\n" + f"ICD-10 Diagnoses:\n{icd_lines}\n" + f"CPT Procedures:\n{cpt_lines}" + ) + + metadata = { + "doc_type": "soap_note", + "doc_id": doc_id, + "patient_ref": request.patient_ref or "Anonymous", + "specialty": request.specialty, + "ingested_at": datetime.now(timezone.utc).isoformat(), + } + + try: + logger.info(f"Ingesting approved SOAP note — patient_ref={request.patient_ref}, specialty={request.specialty}") + + # Always use direct ChromaDB client for reliable upserts + upsert_document(soap_text, metadata) + logger.info("SOAP note upserted via direct ChromaDB client") + + _documents_store.append( + DocumentRecord( + id=doc_id, + filename=f"SOAP_{request.specialty}_{request.patient_ref or 'anon'}.txt", + size_bytes=len(soap_text.encode()), + ingested_at=datetime.now(timezone.utc).isoformat(), + doc_type="soap_note", + patient_ref=request.patient_ref or "", + specialty=request.specialty, + ) + ) + logger.info(f"SOAP note ingested into knowledge base: {doc_id}") + return ApproveNoteResponse( + message="SOAP note approved and added to knowledge base.", + document_id=doc_id, + status="success", + ) + except HTTPException: + raise + except Exception as e: + logger.error(f"Approve note error: {e}", exc_info=True) + raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=str(e)) + + +@app.post("/chat", response_model=ChatResponse) +def clinical_chat(request: ChatRequest): + """ + Clinical QA — Flowise ConversationalRetrievalQAChain (primary) reading from the + shared clinical_kb ChromaDB collection. + + All reads and writes use the direct Python chromadb client for consistent embeddings. + Fallback to direct RAG if the primary path fails. + """ + session_id = request.session_id or str(uuid.uuid4()) + + # Direct ChromaDB + Ollama RAG + # All reads and writes go through the Python chromadb client to keep embeddings consistent. + try: + logger.info("Clinical QA via direct RAG — session=%s question=%s", session_id, request.question[:80]) + retrieved = query_documents(request.question, n_results=8) + + if retrieved: + context = "\n\n---\n\n".join( + f"[Source: {r['metadata'].get('patient_ref') or r['metadata'].get('source', 'Unknown')} " + f"({r['metadata'].get('doc_type', 'document')})]\n{r['text']}" + for r in retrieved + ) + answer = _strip_inline_sources(llm_answer_question(context, request.question)) + + # If the LLM issued a refusal (non-medical or not in KB), show no citations — + # the retrieved docs were not actually used to form the answer. + _REFUSAL_PHRASES = ( + "this assistant only answers clinical", + "no matching information found", + ) + is_refusal = any(p in answer.lower() for p in _REFUSAL_PHRASES) + sources = [] if is_refusal else [ + CitedSource( + document=r["metadata"].get("patient_ref") or r["metadata"].get("source", "Unknown"), + chunk=r["text"][:300], + doc_type=r["metadata"].get("doc_type", "guideline"), + score=r.get("score"), + ) + for r in retrieved + ] + else: + logger.info("Clinical QA — knowledge base is empty or no relevant documents found") + answer = ( + "No matching information found in the knowledge base. " + "Try approving relevant SOAP notes or uploading clinical guidelines." + ) + sources = [] + + return ChatResponse(answer=answer, sources=sources, session_id=session_id) + except HTTPException: + raise + except Exception as e: + logger.error("Clinical chat error: %s", e, exc_info=True) + raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=str(e)) + + +@app.post("/ingest-document", response_model=IngestDocumentResponse) +async def ingest_document(file: UploadFile = File(...)): + content = await file.read() + file_size = len(content) + + try: + validate_pdf_file(file.filename or "doc.pdf", file_size) + except ValueError as e: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) + + tmp_path = None + try: + with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp: + tmp.write(content) + tmp_path = tmp.name + + doc_id = str(uuid.uuid4()) + logger.info(f"Ingesting document: {file.filename} ({file_size / 1024:.1f} KB)") + + extracted_text = extract_text_from_pdf(tmp_path) + if not extracted_text.strip(): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="No text could be extracted from this PDF.", + ) + + metadata = { + "doc_type": "guideline", + "doc_id": doc_id, + "source": file.filename or "document.pdf", + "ingested_at": datetime.now(timezone.utc).isoformat(), + } + + # Always use direct ChromaDB client for reliable upserts + logger.info("Ingesting document via direct ChromaDB: %s", file.filename) + upsert_document(extracted_text, metadata) + + _documents_store.append( + DocumentRecord( + id=doc_id, + filename=file.filename or "document.pdf", + size_bytes=file_size, + ingested_at=datetime.now(timezone.utc).isoformat(), + doc_type="guideline", + ) + ) + + logger.info(f"Document ingested into ChromaDB: {file.filename} → {doc_id}") + return IngestDocumentResponse( + message=f"'{file.filename}' ingested into knowledge base.", + document_id=doc_id, + status="success", + ) + except HTTPException: + raise + except Exception as e: + logger.error(f"Ingest error: {e}", exc_info=True) + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) + finally: + if tmp_path and os.path.exists(tmp_path): + try: + os.remove(tmp_path) + except Exception: + pass + + +@app.get("/documents", response_model=DocumentsListResponse) +def list_documents(): + return DocumentsListResponse(documents=_documents_store, total=len(_documents_store)) + + +@app.delete("/documents/{doc_id}") +def delete_document(doc_id: str): + global _documents_store + before = len(_documents_store) + _documents_store = [d for d in _documents_store if d.id != doc_id] + if len(_documents_store) == before: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found.") + chroma_delete_document(doc_id) + logger.info(f"Document deleted: {doc_id}") + return {"message": "Document removed.", "document_id": doc_id} + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=config.BACKEND_PORT) # nosec B104 diff --git a/api/services/__init__.py b/api/services/__init__.py new file mode 100644 index 0000000..ce9cc04 --- /dev/null +++ b/api/services/__init__.py @@ -0,0 +1,6 @@ +from .flowise_client import FlowiseClient, get_flowise_client +from .flowise_provisioner import provision, is_provisioned +from .whisper_client import WhisperClient, get_whisper_client +from .pdf_service import extract_text_from_pdf, validate_pdf_file +from .llm_client import generate_soap, generate_billing_codes, diarize_segments, answer_question +from .chroma_client import upsert_document, query_documents, delete_document, is_connected as chroma_is_connected diff --git a/api/services/chroma_client.py b/api/services/chroma_client.py new file mode 100644 index 0000000..6db95cc --- /dev/null +++ b/api/services/chroma_client.py @@ -0,0 +1,100 @@ +""" +chroma_client.py +Direct ChromaDB HTTP client for the clinical_kb collection. + +Embeddings are generated via Ollama using the configured embedding model. +""" + +import logging +import uuid + +import config +from services.llm_client import embed + +logger = logging.getLogger(__name__) + +_COLLECTION_NAME = "clinical_kb" + + +def _get_client(): + import chromadb + return chromadb.HttpClient(host=config.CHROMA_HOST, port=config.CHROMA_PORT) + + +def upsert_document(text: str, metadata: dict) -> str: + """ + Embed and upsert a document into the clinical_kb collection. + Returns the document ID. + """ + doc_id = metadata.get("doc_id") or str(uuid.uuid4()) + try: + client = _get_client() + collection = client.get_or_create_collection(name=_COLLECTION_NAME) + embeddings = embed([text]) + collection.upsert( + ids=[doc_id], + documents=[text], + embeddings=embeddings, + metadatas=[metadata], + ) + logger.info("Upserted document %s into %s", doc_id, _COLLECTION_NAME) + return doc_id + except Exception as e: + logger.error("ChromaDB upsert failed: %s", e, exc_info=True) + raise + + +def query_documents(query: str, n_results: int = 5) -> list[dict]: + """ + Query the clinical_kb collection and return matching chunks with metadata. + """ + try: + client = _get_client() + collection = client.get_or_create_collection(name=_COLLECTION_NAME) + + count = collection.count() + if count == 0: + logger.info("clinical_kb is empty — no documents to query") + return [] + + query_embeddings = embed([query]) + results = collection.query( + query_embeddings=query_embeddings, + n_results=min(n_results, count), + include=["documents", "metadatas", "distances"], + ) + docs = results.get("documents", [[]])[0] + metas = results.get("metadatas", [[]])[0] + distances = results.get("distances", [[]])[0] + return [ + { + "text": doc, + "metadata": meta, + "score": round(max(0.0, 1 - dist), 4), + } + for doc, meta, dist in zip(docs, metas, distances) + ] + except Exception as e: + logger.error("ChromaDB query failed: %s", e, exc_info=True) + return [] + + +def delete_document(doc_id: str) -> bool: + """Delete a document from the collection by ID.""" + try: + client = _get_client() + collection = client.get_or_create_collection(name=_COLLECTION_NAME) + collection.delete(ids=[doc_id]) + logger.info("Deleted document %s from %s", doc_id, _COLLECTION_NAME) + return True + except Exception as e: + logger.error("ChromaDB delete failed: %s", e, exc_info=True) + return False + + +def is_connected() -> bool: + try: + _get_client().heartbeat() + return True + except Exception: + return False diff --git a/api/services/flowise_client.py b/api/services/flowise_client.py new file mode 100644 index 0000000..68f3071 --- /dev/null +++ b/api/services/flowise_client.py @@ -0,0 +1,90 @@ +import logging +from typing import Optional +import httpx +import config + +logger = logging.getLogger(__name__) + + +class FlowiseClient: + def __init__(self): + self.endpoint = config.FLOWISE_ENDPOINT.rstrip("/") + self.api_key = config.FLOWISE_API_KEY + self._headers = {"Content-Type": "application/json"} + if self.api_key: + self._headers["Authorization"] = f"Bearer {self.api_key}" + + def predict(self, flow_id: str, question: str, overrides: Optional[dict] = None) -> dict: + """ + Send a question to a Flowise chatflow and return the full response dict. + The ConversationalRetrievalQAChain returns: + { "text": "...", "sourceDocuments": [...], "chatId": "..." } + """ + payload: dict = {"question": question} + if overrides: + payload["overrideConfig"] = overrides + + url = f"{self.endpoint}/api/v1/prediction/{flow_id}" + logger.info("Flowise predict → flow=%s question_len=%d", flow_id, len(question)) + with httpx.Client(timeout=180.0) as client: + response = client.post(url, json=payload, headers=self._headers) + if not response.is_success: + logger.error( + "Flowise predict failed: %s — %s", + response.status_code, + response.text[:500], + ) + response.raise_for_status() + return response.json() + + def upsert(self, flow_id: str, text: str, metadata: Optional[dict] = None) -> dict: + """ + Upsert a document into the Flowise QA flow's vector store (ChromaDB). + + Flowise upsert endpoint: POST /api/v1/vector/upsert/{flow_id} + - `question` : the raw text to embed and store + - `overrideConfig`: passed through to the vector store node as runtime config. + We flatten metadata keys here so Flowise passes them to + the ChromaDB document's metadata dict. + """ + url = f"{self.endpoint}/api/v1/vector/upsert/{flow_id}" + override: dict = {} + if metadata: + override.update(metadata) + + payload: dict = {"question": text} + if override: + payload["overrideConfig"] = override + + logger.info("Flowise upsert → flow=%s text_len=%d", flow_id, len(text)) + with httpx.Client(timeout=180.0) as client: + response = client.post(url, json=payload, headers=self._headers) + if not response.is_success: + logger.error( + "Flowise upsert failed: %s — %s", + response.status_code, + response.text[:500], + ) + response.raise_for_status() + return response.json() + + def is_connected(self) -> bool: + try: + with httpx.Client(timeout=5.0) as client: + response = client.get( + f"{self.endpoint}/api/v1/ping", + headers=self._headers, + ) + return response.status_code == 200 + except Exception: + return False + + +_flowise_client: Optional[FlowiseClient] = None + + +def get_flowise_client() -> FlowiseClient: + global _flowise_client + if _flowise_client is None: + _flowise_client = FlowiseClient() + return _flowise_client diff --git a/api/services/flowise_provisioner.py b/api/services/flowise_provisioner.py new file mode 100644 index 0000000..727ae22 --- /dev/null +++ b/api/services/flowise_provisioner.py @@ -0,0 +1,663 @@ +""" +flowise_provisioner.py +Auto-creates AND updates Flowise flows on every FastAPI startup. + +Ollama only (ChatOllama + OllamaEmbeddings). No cloud credentials required. + +On every restart the provisioner: + - Creates the flow if it doesn't exist + - Updates it if it does (so .env changes are always reflected) +""" + +import json +import logging +import time +import uuid + +import httpx + +import config + +logger = logging.getLogger(__name__) + +_provisioned = False + + +# ── HTTP helpers ──────────────────────────────────────────────────────────── + +def _headers() -> dict: + h = {"Content-Type": "application/json"} + if config.FLOWISE_API_KEY: + h["Authorization"] = f"Bearer {config.FLOWISE_API_KEY}" + return h + + +def _wait_for_flowise(max_attempts: int = 15, delay: float = 4.0) -> bool: + url = f"{config.FLOWISE_ENDPOINT}/api/v1/ping" + for attempt in range(1, max_attempts + 1): + try: + with httpx.Client(timeout=5.0) as client: + r = client.get(url) + if r.status_code == 200: + logger.info("Flowise is ready") + return True + except Exception: + pass + logger.info("Waiting for Flowise... attempt %d/%d", attempt, max_attempts) + time.sleep(delay) + logger.error("Flowise did not become ready after %d attempts", max_attempts) + return False + + +def _list_chatflows() -> list[dict]: + url = f"{config.FLOWISE_ENDPOINT}/api/v1/chatflows" + with httpx.Client(timeout=15.0) as client: + r = client.get(url, headers=_headers()) + r.raise_for_status() + return r.json() + + +def _create_chatflow(name: str, flow_data: dict) -> str: + url = f"{config.FLOWISE_ENDPOINT}/api/v1/chatflows" + payload = { + "name": name, + "flowData": json.dumps(flow_data), + "deployed": True, + "isPublic": False, + "type": "CHATFLOW", + } + with httpx.Client(timeout=30.0) as client: + r = client.post(url, json=payload, headers=_headers()) + r.raise_for_status() + return r.json()["id"] + + +def _update_chatflow(flow_id: str, name: str, flow_data: dict) -> None: + url = f"{config.FLOWISE_ENDPOINT}/api/v1/chatflows/{flow_id}" + payload = { + "name": name, + "flowData": json.dumps(flow_data), + "deployed": True, + "isPublic": False, + "type": "CHATFLOW", + } + with httpx.Client(timeout=30.0) as client: + r = client.put(url, json=payload, headers=_headers()) + r.raise_for_status() + + + +# ── Node ID helpers ───────────────────────────────────────────────────────── + +def _make_id(prefix: str) -> str: + return f"{prefix}_{uuid.uuid4().hex[:8]}" + + +# ── Provider-specific node builders ──────────────────────────────────────── + +def _chat_node(node_id: str, position: dict, soap_temp: float = 0.1) -> dict: + """Build a ChatOllama node for a Flowise flow.""" + input_params = [ + {"label": "Base URL", "name": "baseUrl", "type": "string", "default": "http://localhost:11434"}, + {"label": "Model Name", "name": "modelName", "type": "string", "placeholder": "llama3.1:8b"}, + {"label": "Temperature", "name": "temperature","type": "number", "optional": True}, + {"label": "Top P", "name": "topP", "type": "number", "optional": True, "additionalParams": True}, + {"label": "Top K", "name": "topK", "type": "number", "optional": True, "additionalParams": True}, + {"label": "Keep Alive", "name": "keepAlive", "type": "string", "optional": True, "additionalParams": True}, + ] + return { + "id": node_id, + "type": "genericNode", + "position": position, + "data": { + "id": node_id, + "label": "ChatOllama", + "name": "chatOllama", + "version": 2, + "type": "ChatOllama", + "baseClasses": [ + "ChatOllama", "SimpleChatModel", + "BaseChatModel", "BaseLanguageModel", "Runnable", + ], + "category": "Chat Models", + "inputs": { + "baseUrl": config.OLLAMA_BASE_URL, + "modelName": config.OLLAMA_MODEL, + "temperature": soap_temp, + }, + "inputParams": input_params, + "outputs": {}, + "selected": False, + }, + } + + +def _chat_source_handle(node_id: str) -> str: + """Return the sourceHandle string for the ChatOllama node output.""" + return ( + f"{node_id}-output-chatOllama-" + "ChatOllama|SimpleChatModel|BaseChatModel|BaseLanguageModel|Runnable" + ) + + +def _embed_node(node_id: str, position: dict) -> dict: + """Build an OllamaEmbeddings node for a Flowise flow.""" + input_params = [ + {"label": "Base URL", "name": "baseUrl", "type": "string", "default": "http://localhost:11434"}, + {"label": "Model Name", "name": "modelName", "type": "string", "placeholder": "nomic-embed-text"}, + {"label": "Num GPU", "name": "numGpu", "type": "number", "optional": True, "additionalParams": True}, + {"label": "Keep Alive", "name": "keepAlive", "type": "string", "optional": True, "additionalParams": True}, + ] + return { + "id": node_id, + "type": "genericNode", + "position": position, + "data": { + "id": node_id, + "label": "Ollama Embeddings", + "name": "ollamaEmbedding", + "version": 1, + "type": "OllamaEmbeddings", + "baseClasses": ["OllamaEmbeddings", "Embeddings"], + "category": "Embeddings", + "inputs": { + "baseUrl": config.OLLAMA_BASE_URL, + "modelName": config.OLLAMA_EMBED_MODEL, + }, + "inputParams": input_params, + "outputs": {}, + "selected": False, + }, + } + + +def _embed_source_handle(node_id: str) -> str: + """Return the sourceHandle string for the OllamaEmbeddings node output.""" + return f"{node_id}-output-ollamaEmbedding-OllamaEmbeddings|Embeddings" + + +# ── Flow data builders ────────────────────────────────────────────────────── + + +def _build_soap_flow_data() -> dict: + """ + SOAP Generator flow — LLM Chain (no vector store dependency). + + PromptTemplate ──► LLMChain ◄── ChatModel + │ + ▼ + output (SOAP JSON) + + The transcript is the full context — no retrieval needed. + Using LLMChain avoids the ChromaDB dependency at prediction time + and works even when ChromaDB is not running. + """ + chat_id = _make_id("chat") + prompt_id = _make_id("prompt") + chain_id = _make_id("chain") + + soap_system_prompt = ( + "You are a clinical documentation specialist.\n" + "You will receive a diarized doctor-patient consultation transcript " + "where speakers are labeled Doctor and Patient.\n\n" + "Generate a structured SOAP note as valid JSON with EXACTLY these keys:\n" + " chief_complaint, subjective, objective, assessment, plan, keywords\n\n" + "STRICT CONTENT RULES:\n" + "- Use ONLY information explicitly stated in the transcript. Do not invent vitals, medications, or findings not mentioned.\n" + "- If objective findings were not mentioned in the transcript, write: Not documented in this consultation.\n" + "- If a field has no information from the transcript, write: Not reported.\n" + "- Every field except keywords MUST be a plain STRING.\n" + "- keywords MUST be an object with three flat string arrays.\n\n" + "OUTPUT FORMAT — return valid JSON matching this exact structure:\n" + "{{\n" + " \"chief_complaint\": \"One sentence summarising the patient's main complaint from the transcript.\",\n" + " \"subjective\": \"Prose paragraph summarising what the patient reported: symptoms, duration, severity, history.\",\n" + " \"objective\": \"Prose paragraph of clinician findings: vitals, exam findings, test results. If none mentioned, write: Not documented in this consultation.\",\n" + " \"assessment\": \"Prose paragraph with clinical impression and differential diagnoses with ICD-10 codes where applicable.\",\n" + " \"plan\": \"Prose paragraph with treatment, tests ordered, medications, and follow-up instructions.\",\n" + " \"keywords\": {{\n" + " \"symptoms\": [\"list\", \"of\", \"symptoms\"],\n" + " \"medications\": [\"list\", \"of\", \"medications\"],\n" + " \"diagnoses\": [\"list\", \"of\", \"diagnoses\"]\n" + " }}\n" + "}}\n\n" + "Use proper medical terminology. Return ONLY valid JSON — no markdown fences, no explanation." + ) + + # Human message template — {input} is replaced by the transcript at prediction time + human_template = "{input}" + + chat_node = _chat_node(chat_id, {"x": 400, "y": 200}, soap_temp=0.1) + + prompt_input_params = [ + {"label": "System Message", "name": "systemMessagePrompt", "type": "string", "rows": 4}, + {"label": "Human Message", "name": "humanMessagePrompt", "type": "string", "rows": 4}, + {"label": "Format Prompt Values", "name": "promptValues", "type": "json", "optional": True, "acceptVariable": True, "list": True}, + ] + + prompt_node = { + "id": prompt_id, + "type": "genericNode", + "position": {"x": 100, "y": 200}, + "data": { + "id": prompt_id, + "label": "Chat Prompt Template", + "name": "chatPromptTemplate", + "version": 1, + "type": "ChatPromptTemplate", + "baseClasses": ["ChatPromptTemplate", "BaseChatPromptTemplate", "BasePromptTemplate", "Runnable"], + "category": "Prompts", + "inputs": { + "systemMessagePrompt": soap_system_prompt, + "humanMessagePrompt": human_template, + "inputVariables": "input", + }, + "inputParams": prompt_input_params, + "outputs": {}, + "selected": False, + }, + } + + llmchain_input_params = [ + {"label": "Language Model", "name": "model", "type": "BaseLanguageModel"}, + {"label": "Prompt", "name": "prompt", "type": "BasePromptTemplate"}, + {"label": "Output Key", "name": "outputKey", "type": "string", "default": "text", "optional": True, "additionalParams": True}, + {"label": "Chain Name", "name": "chainName", "type": "string", "optional": True, "additionalParams": True}, + ] + + chain_node = { + "id": chain_id, + "type": "genericNode", + "position": {"x": 700, "y": 200}, + "data": { + "id": chain_id, + "label": "LLM Chain", + "name": "llmChain", + "version": 3, + "type": "LLMChain", + "baseClasses": ["LLMChain", "BaseChain", "Runnable"], + "category": "Chains", + "inputs": { + "model": f"{{{{{chat_id}.data.instance}}}}", + "prompt": f"{{{{{prompt_id}.data.instance}}}}", + "outputKey": "text", + }, + "inputParams": llmchain_input_params, + "outputs": {"output": "llmChain"}, + "selected": False, + }, + } + + nodes = [prompt_node, chat_node, chain_node] + edges = [ + { + "id": f"e_{prompt_id}_{chain_id}", + "source": prompt_id, + "target": chain_id, + "sourceHandle": ( + f"{prompt_id}-output-chatPromptTemplate-" + "ChatPromptTemplate|BaseChatPromptTemplate|BasePromptTemplate|Runnable" + ), + "targetHandle": f"{chain_id}-input-prompt-BasePromptTemplate", + "type": "buttonedge", + }, + { + "id": f"e_{chat_id}_{chain_id}", + "source": chat_id, + "target": chain_id, + "sourceHandle": _chat_source_handle(chat_id), + "targetHandle": f"{chain_id}-input-model-BaseLanguageModel", + "type": "buttonedge", + }, + ] + + return {"nodes": nodes, "edges": edges} + + +def _build_qa_flow_data() -> dict: + """ + Clinical QA flow. + ChatModel ──────────────────────────────► ConversationalRetrievalQAChain + Embeddings ──► Chroma(clinical_kb) ──► + BufferMemory ───────────────────────────► + + returnSourceDocuments: True → citations in chat responses. + """ + chat_id = _make_id("chat") + embed_id = _make_id("embed") + chroma_id = _make_id("chroma") + memory_id = _make_id("memory") + chain_id = _make_id("chain") + + chroma_url = f"http://{config.CHROMA_HOST}:{config.CHROMA_PORT}" + + rephrase_prompt = ( + "Given the conversation history and the follow-up question, " + "rephrase the follow-up question to be a standalone search query " + "that retrieves the most relevant clinical documents.\n\n" + "Chat History: {chat_history}\n" + "Follow Up: {question}\n" + "Standalone question:" + ) + + response_prompt = ( + "You are a clinical knowledge base assistant. Answer ONLY from the retrieved context provided.\n\n" + "Rules:\n" + "- If the question is not clinical or medical, respond exactly: 'This assistant only answers clinical and medical questions from the knowledge base.'\n" + "- If the answer is not present in the context, respond exactly: 'No matching information found in the knowledge base. Try approving relevant SOAP notes or uploading clinical guidelines.'\n" + "- Never use outside knowledge. If context does not contain the answer, use the fallback above.\n" + "- Give a direct, factual answer in 1-3 sentences. No preamble, no apologies, no 'unfortunately'.\n" + "- Do not include source tags in your answer — sources are shown separately.\n\n" + "Context: {context}\n\n" + "Question: {question}\n\n" + "Answer:" + ) + + chat_node = _chat_node(chat_id, {"x": 100, "y": 100}, soap_temp=0.3) + embed_node = _embed_node(embed_id, {"x": 100, "y": 500}) + + chroma_qa_input_params = [ + {"label": "Document", "name": "document", "type": "Document", "list": True, "optional": True}, + {"label": "Embeddings", "name": "embeddings", "type": "Embeddings"}, + {"label": "Record Manager", "name": "recordManager", "type": "RecordManager", "optional": True}, + {"label": "Collection Name", "name": "collectionName", "type": "string"}, + {"label": "Chroma URL", "name": "chromaURL", "type": "string", "optional": True}, + {"label": "Chroma Metadata Filter", "name": "chromaMetadataFilter", "type": "json", "optional": True, "additionalParams": True}, + {"label": "Top K", "name": "topK", "type": "number", "optional": True, "additionalParams": True}, + ] + + chroma_node = { + "id": chroma_id, + "type": "genericNode", + "position": {"x": 500, "y": 500}, + "data": { + "id": chroma_id, + "label": "Chroma", + "name": "chroma", + "version": 1, + "type": "Chroma", + "baseClasses": ["Chroma", "VectorStoreRetriever", "BaseRetriever"], + "category": "Vector Stores", + "inputs": { + "embeddings": f"{{{{{embed_id}.data.instance}}}}", + "collectionName": "clinical_kb", + "chromaURL": chroma_url, + "topK": 8, + }, + "inputParams": chroma_qa_input_params, + "outputs": {"output": "retriever"}, + "selected": False, + }, + } + + memory_input_params = [ + {"label": "Session Id", "name": "sessionId", "type": "string", "optional": True, "description": "If not specified, a random id will be used"}, + {"label": "Memory Key", "name": "memoryKey", "type": "string", "default": "chat_history"}, + ] + + memory_node = { + "id": memory_id, + "type": "genericNode", + "position": {"x": 100, "y": 300}, + "data": { + "id": memory_id, + "label": "Buffer Memory", + "name": "bufferMemory", + "version": 2, + "type": "BufferMemory", + "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], + "category": "Memory", + "inputs": { + "sessionId": "", + "memoryKey": "chat_history", + }, + "inputParams": memory_input_params, + "outputs": {}, + "selected": False, + }, + } + + chain_input_params = [ + {"label": "Chat Model", "name": "model", "type": "BaseChatModel"}, + {"label": "Vector Store Retriever", "name": "vectorStoreRetriever", "type": "BaseRetriever"}, + {"label": "Memory", "name": "memory", "type": "BaseMemory", "optional": True}, + {"label": "Return Source Documents","name": "returnSourceDocuments", "type": "boolean", "optional": True}, + {"label": "Rephrase Prompt", "name": "rephrasePrompt", "type": "string", "optional": True, "additionalParams": True}, + {"label": "Response Prompt", "name": "responsePrompt", "type": "string", "optional": True, "additionalParams": True}, + ] + + chain_node = { + "id": chain_id, + "type": "genericNode", + "position": {"x": 500, "y": 100}, + "data": { + "id": chain_id, + "label": "Conversational Retrieval QA Chain", + "name": "conversationalRetrievalQAChain", + "version": 3, + "type": "ConversationalRetrievalQAChain", + "baseClasses": ["ConversationalRetrievalQAChain", "BaseChain", "Runnable"], + "category": "Chains", + "inputs": { + "model": f"{{{{{chat_id}.data.instance}}}}", + "vectorStoreRetriever": f"{{{{{chroma_id}.data.instance}}}}", + "memory": f"{{{{{memory_id}.data.instance}}}}", + "returnSourceDocuments": True, + "rephrasePrompt": rephrase_prompt, + "responsePrompt": response_prompt, + }, + "inputParams": chain_input_params, + "outputs": {}, + "selected": False, + }, + } + + nodes = [chat_node, embed_node, chroma_node, memory_node, chain_node] + edges = [ + { + "id": f"e_{chat_id}_{chain_id}", + "source": chat_id, + "target": chain_id, + "sourceHandle": _chat_source_handle(chat_id), + "targetHandle": f"{chain_id}-input-model-BaseChatModel", + "type": "buttonedge", + }, + { + "id": f"e_{embed_id}_{chroma_id}", + "source": embed_id, + "target": chroma_id, + "sourceHandle": _embed_source_handle(embed_id), + "targetHandle": f"{chroma_id}-input-embeddings-Embeddings", + "type": "buttonedge", + }, + { + "id": f"e_{chroma_id}_{chain_id}", + "source": chroma_id, + "target": chain_id, + "sourceHandle": f"{chroma_id}-output-retriever-Chroma|VectorStoreRetriever|BaseRetriever", + "targetHandle": f"{chain_id}-input-vectorStoreRetriever-BaseRetriever", + "type": "buttonedge", + }, + { + "id": f"e_{memory_id}_{chain_id}", + "source": memory_id, + "target": chain_id, + "sourceHandle": f"{memory_id}-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory", + "targetHandle": f"{chain_id}-input-memory-BaseMemory", + "type": "buttonedge", + }, + ] + + return {"nodes": nodes, "edges": edges} + + +def _build_upsert_flow_data() -> dict: + """ + KB Upsert flow — PlainText Document Loader → Chroma (upsert mode) ← Embeddings + + PlainText ──► Chroma ◄── Embeddings + │ + ▼ + (writes to clinical_kb collection) + """ + plaintext_id = _make_id("plaintext") + embed_id = _make_id("embed") + chroma_id = _make_id("chroma") + + chroma_url = f"http://{config.CHROMA_HOST}:{config.CHROMA_PORT}" + + embed_node = _embed_node(embed_id, {"x": 100, "y": 400}) + + # inputParams must mirror the node class's inputs array. + # The Flowise upsert handler iterates nodeData.inputs and calls + # nodeData.inputParams.find(...) — if inputParams is absent the call crashes. + # The Flowise UI populates this automatically; the provisioner must do it explicitly. + plaintext_input_params = [ + {"label": "Text", "name": "text", "type": "string", "rows": 4}, + {"label": "Text Splitter", "name": "textSplitter", "type": "TextSplitter", "optional": True}, + {"label": "Additional Metadata", "name": "metadata", "type": "json", "optional": True, "additionalParams": True}, + {"label": "Omit Metadata Keys", "name": "omitMetadataKeys", "type": "string", "optional": True, "additionalParams": True}, + ] + + plaintext_node = { + "id": plaintext_id, + "type": "genericNode", + "position": {"x": 100, "y": 100}, + "data": { + "id": plaintext_id, + "label": "Plain Text", + "name": "plainText", + "version": 2, + "type": "Document", + "baseClasses": ["Document"], + "category": "Document Loaders", + # text is overridden at upsert time via overrideConfig.text + "inputs": {"text": "", "metadata": ""}, + # inputParams mirrors the node class inputs array required by Flowise + "inputParams": plaintext_input_params, + "outputAnchors": [{"id": f"{plaintext_id}-output-document-Document", "name": "document", "label": "Document", "description": "Array of document objects", "type": "Document"}], + "outputs": {"output": "document"}, + "selected": False, + }, + } + + chroma_input_params = [ + {"label": "Document", "name": "document", "type": "Document", "list": True, "optional": True}, + {"label": "Embeddings", "name": "embeddings", "type": "Embeddings"}, + {"label": "Record Manager", "name": "recordManager", "type": "RecordManager", "optional": True}, + {"label": "Collection Name", "name": "collectionName", "type": "string"}, + {"label": "Chroma URL", "name": "chromaURL", "type": "string", "optional": True}, + {"label": "Chroma Metadata Filter", "name": "chromaMetadataFilter", "type": "json", "optional": True, "additionalParams": True}, + {"label": "Top K", "name": "topK", "type": "number", "optional": True, "additionalParams": True}, + ] + + chroma_node = { + "id": chroma_id, + "type": "genericNode", + "position": {"x": 500, "y": 200}, + "data": { + "id": chroma_id, + "label": "Chroma", + "name": "chroma", + "version": 2, + "type": "Chroma", + "baseClasses": ["Chroma", "VectorStoreRetriever", "BaseRetriever"], + "category": "Vector Stores", + "inputs": { + # Flowise resolves node-to-node connections via {{nodeId.data.instance}} syntax + "document": f"{{{{{plaintext_id}.data.instance}}}}", + "embeddings": f"{{{{{embed_id}.data.instance}}}}", + "collectionName": "clinical_kb", + "chromaURL": chroma_url, + }, + "inputParams": chroma_input_params, + "outputs": {"output": "retriever"}, + "selected": False, + }, + } + + nodes = [plaintext_node, embed_node, chroma_node] + edges = [ + { + "id": f"e_{plaintext_id}_{chroma_id}", + "source": plaintext_id, + "target": chroma_id, + "sourceHandle": f"{plaintext_id}-output-document-Document", + "targetHandle": f"{chroma_id}-input-document-Document", + "type": "buttonedge", + }, + { + "id": f"e_{embed_id}_{chroma_id}", + "source": embed_id, + "target": chroma_id, + "sourceHandle": _embed_source_handle(embed_id), + "targetHandle": f"{chroma_id}-input-embeddings-Embeddings", + "type": "buttonedge", + }, + ] + + return {"nodes": nodes, "edges": edges} + + +# ── Public API ────────────────────────────────────────────────────────────── + +def provision() -> bool: + """ + Called once at FastAPI startup. + 1. Wait for Flowise + 2. Provision API credentials for the active LLM provider + 3. Create or update both flows with current .env values + """ + global _provisioned + + if not _wait_for_flowise(): + logger.error("Flowise did not become ready — skipping flow provisioning") + return False + + logger.info("Provisioning flows — Ollama model=%s embed=%s", config.OLLAMA_MODEL, config.OLLAMA_EMBED_MODEL) + + try: + # flows + existing = _list_chatflows() + existing_by_name = {f.get("name"): f.get("id") for f in existing} + + # SOAP Generator + soap_data = _build_soap_flow_data() + if config.SOAP_FLOW_NAME in existing_by_name: + config.SOAP_FLOW_ID = existing_by_name[config.SOAP_FLOW_NAME] + _update_chatflow(config.SOAP_FLOW_ID, config.SOAP_FLOW_NAME, soap_data) + logger.info("SOAP flow updated: %s", config.SOAP_FLOW_ID) + else: + config.SOAP_FLOW_ID = _create_chatflow(config.SOAP_FLOW_NAME, soap_data) + logger.info("SOAP flow created: %s", config.SOAP_FLOW_ID) + + # Clinical QA + qa_data = _build_qa_flow_data() + if config.QA_FLOW_NAME in existing_by_name: + config.QA_FLOW_ID = existing_by_name[config.QA_FLOW_NAME] + _update_chatflow(config.QA_FLOW_ID, config.QA_FLOW_NAME, qa_data) + logger.info("QA flow updated: %s", config.QA_FLOW_ID) + else: + config.QA_FLOW_ID = _create_chatflow(config.QA_FLOW_NAME, qa_data) + logger.info("QA flow created: %s", config.QA_FLOW_ID) + + # KB Upsert (PlainText → Chroma) — separate from QA flow + upsert_data = _build_upsert_flow_data() + if config.UPSERT_FLOW_NAME in existing_by_name: + config.UPSERT_FLOW_ID = existing_by_name[config.UPSERT_FLOW_NAME] + _update_chatflow(config.UPSERT_FLOW_ID, config.UPSERT_FLOW_NAME, upsert_data) + logger.info("Upsert flow updated: %s", config.UPSERT_FLOW_ID) + else: + config.UPSERT_FLOW_ID = _create_chatflow(config.UPSERT_FLOW_NAME, upsert_data) + logger.info("Upsert flow created: %s", config.UPSERT_FLOW_ID) + + _provisioned = True + return True + + except Exception as e: + logger.error("Flow provisioning failed: %s", e, exc_info=True) + return False + + +def is_provisioned() -> bool: + return _provisioned and bool(config.SOAP_FLOW_ID) and bool(config.QA_FLOW_ID) and bool(config.UPSERT_FLOW_ID) diff --git a/api/services/llm_client.py b/api/services/llm_client.py new file mode 100644 index 0000000..f325955 --- /dev/null +++ b/api/services/llm_client.py @@ -0,0 +1,179 @@ +""" +llm_client.py +Ollama client for SOAP note generation, speaker diarization, billing code suggestion, +clinical QA, and text embedding. + +Ollama runs on the host machine and is reachable from Docker containers +via host.docker.internal. +""" + +import json +import logging + +import httpx + +import config + +logger = logging.getLogger(__name__) + +# ── Timeouts ──────────────────────────────────────────────────────────────── +_CHAT_TIMEOUT = 600.0 # seconds — 10 min budget for llama3.1:8b on CPU (2-3 min per call) +_EMBED_TIMEOUT = 30.0 + +# ── System prompts ────────────────────────────────────────────────────────── + +SOAP_SYSTEM_PROMPT = """\ +You are a clinical documentation specialist. +You will receive a diarized doctor-patient consultation transcript +where speakers are labeled Doctor and Patient. + +Generate a structured SOAP note as valid JSON with EXACTLY these keys: + chief_complaint, subjective, objective, assessment, plan, keywords + +STRICT OUTPUT RULES — read carefully: +- Every field except keywords MUST be a plain STRING (not an object, not an array). +- Write each field as flowing prose sentences, NOT as JSON sub-objects or lists. +- keywords MUST be an object with three flat string arrays. + +STRICT CONTENT RULES: +- Use ONLY information explicitly stated in the transcript. Do not invent vitals, medications, or findings not mentioned. +- If objective findings (BP, exam results) were not mentioned in the transcript, write "Not documented in this consultation." +- If a field has no information from the transcript, write "Not reported." + +OUTPUT FORMAT — return valid JSON matching this exact structure: +{ + "chief_complaint": "One sentence summarising the patient's main complaint from the transcript.", + "subjective": "Prose paragraph summarising what the patient reported: symptoms, duration, severity, history.", + "objective": "Prose paragraph of clinician findings from the transcript: vitals, exam findings, test results. If none mentioned, write: Not documented in this consultation.", + "assessment": "Prose paragraph with clinical impression and differential diagnoses with ICD-10 codes where applicable.", + "plan": "Prose paragraph with treatment, tests ordered, medications, and follow-up instructions.", + "keywords": { + "symptoms": ["list", "of", "symptoms"], + "medications": ["list", "of", "medications"], + "diagnoses": ["list", "of", "diagnoses"] + } +} + +Use proper medical terminology. Return ONLY valid JSON — no markdown fences, no explanation.""" + + +BILLING_SYSTEM_PROMPT = """\ +You are an expert medical coder. Review the provided SOAP note and suggest +1-3 appropriate CPT codes (procedures/E&M visits) and 1-3 ICD-10 codes (diagnoses). + +Return ONLY valid JSON matching this exact schema — no markdown, no explanation: +{ + "cpt": [{"code": "string", "description": "string"}], + "icd10": [{"code": "string", "description": "string"}] +}""" + + +QA_SYSTEM_PROMPT = """\ +You are a clinical knowledge base assistant. You answer ONLY from the retrieved context provided. + +Rules: +- If the question is not clinical or medical, respond exactly: "This assistant only answers clinical and medical questions from the knowledge base." +- If the answer is not present in the context, respond exactly: "No matching information found in the knowledge base. Try approving relevant SOAP notes or uploading clinical guidelines." +- Never use outside knowledge. If context does not contain the answer, use the fallback above. +- Give a direct, factual answer in 1-3 sentences. No preamble, no apologies, no "unfortunately". +- Do not include source tags in your answer — sources are shown separately.""" + + +DIARIZATION_SYSTEM_PROMPT = """\ +You are a medical AI assistant. You are given a transcript of a clinical visit broken into numbered audio segments. + +Reconstruct the dialogue: determine if each segment is spoken by the "Doctor" or "Patient" based on context. +Combine adjacent segments from the same speaker into a single utterance. + +Rules: +- The Doctor always speaks first (greeting/opening). +- Doctor: greetings, clinical questions, exam findings (BP, heart sounds), diagnoses, test orders, instructions. +- Patient: symptom descriptions, answering questions, personal/family history, own medications, concern/worry. +- Combine consecutive segments from the same speaker into one utterance with combined text. +- Use the start time of the first segment in each combined group as the utterance start. + +You MUST respond with ONLY a valid JSON array — no markdown, no explanation: +[{"speaker": "Doctor"|"Patient", "text": "combined text of all segments in this turn", "start": 0.0}]""" + + +# ── Public API ────────────────────────────────────────────────────────────── + +def generate_soap(transcript: str) -> str: + """ + Generate a SOAP note JSON string from a diarized transcript. + Returns raw JSON string. Raises on failure. + """ + logger.info("Generating SOAP note via Ollama model=%s", config.OLLAMA_MODEL) + return _chat(transcript, SOAP_SYSTEM_PROMPT) + + +def generate_billing_codes(soap_json: str) -> str: + """ + Generate ICD-10 and CPT codes from a SOAP note JSON string. + Returns raw JSON string. Raises on failure. + """ + logger.info("Generating billing codes via Ollama model=%s", config.OLLAMA_MODEL) + return _chat(soap_json, BILLING_SYSTEM_PROMPT) + + +def answer_question(context: str, question: str) -> str: + """ + Answer a clinical question using retrieved context from the knowledge base. + Returns the answer string. Raises on failure. + """ + logger.info("Answering clinical question via Ollama model=%s", config.OLLAMA_MODEL) + user_message = f"Context from knowledge base:\n\n{context}\n\nQuestion: {question}" + return _chat(user_message, QA_SYSTEM_PROMPT) + + +def diarize_segments(segments_text: str) -> str: + """ + Classify Whisper segments into Doctor/Patient turns. + segments_text: formatted string of numbered segments. + Returns raw JSON array string. Raises on failure. + Uses num_predict=1024 to prevent truncation of the JSON array output. + """ + logger.info("Diarizing segments via Ollama model=%s", config.OLLAMA_MODEL) + return _chat(segments_text, DIARIZATION_SYSTEM_PROMPT, num_predict=1024) + + +def embed(texts: list[str]) -> list[list[float]]: + """ + Generate embeddings via Ollama embeddings endpoint. + Returns list of float vectors, one per input text. + """ + url = f"{config.OLLAMA_BASE_URL}/api/embed" + payload = {"model": config.OLLAMA_EMBED_MODEL, "input": texts} + with httpx.Client(timeout=_EMBED_TIMEOUT) as client: + r = client.post(url, json=payload) + r.raise_for_status() + data = r.json() + # Ollama /api/embed returns {"embeddings": [[...]]} + return data["embeddings"] + + +# ── Internal ──────────────────────────────────────────────────────────────── + +def _chat(user_message: str, system_prompt: str, num_predict: int = -1) -> str: + """ + Call Ollama /api/chat (non-streaming) and return the assistant content string. + num_predict: max tokens to generate. -1 = model default (unlimited). + """ + url = f"{config.OLLAMA_BASE_URL}/api/chat" + payload = { + "model": config.OLLAMA_MODEL, + "stream": False, + "options": {"temperature": 0.1, "num_predict": num_predict}, + "messages": [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_message}, + ], + } + with httpx.Client(timeout=_CHAT_TIMEOUT) as client: + r = client.post(url, json=payload) + r.raise_for_status() + data = r.json() + content = data.get("message", {}).get("content", "") + if not content: + raise ValueError("Ollama returned empty content") + return content diff --git a/api/services/pdf_service.py b/api/services/pdf_service.py new file mode 100644 index 0000000..feed806 --- /dev/null +++ b/api/services/pdf_service.py @@ -0,0 +1,26 @@ +import os +import config + + +def validate_pdf_file(filename: str, file_size: int, max_size: int = None) -> None: + if max_size is None: + max_size = config.MAX_FILE_SIZE + ext = os.path.splitext(filename)[1].lower() + if ext not in config.ALLOWED_PDF_EXTENSIONS: + raise ValueError(f"Invalid file type '{ext}'. Only PDF files are accepted.") + if file_size > max_size: + raise ValueError( + f"File too large ({file_size / 1024 / 1024:.1f} MB). " + f"Maximum allowed size is {max_size / 1024 / 1024:.0f} MB." + ) + + +def extract_text_from_pdf(file_path: str) -> str: + from pypdf import PdfReader + reader = PdfReader(file_path) + pages = [] + for page in reader.pages: + text = page.extract_text() + if text: + pages.append(text.strip()) + return "\n\n".join(pages) diff --git a/api/services/whisper_client.py b/api/services/whisper_client.py new file mode 100644 index 0000000..3786185 --- /dev/null +++ b/api/services/whisper_client.py @@ -0,0 +1,251 @@ +import json +import logging +from typing import Optional +import httpx +import config +from models import DiarizedSegment + +logger = logging.getLogger(__name__) + + +class WhisperClient: + def __init__(self): + self.endpoint = config.WHISPER_ENDPOINT.rstrip("/") + # Detect which server type based on endpoint + # onerahmet/openai-whisper-asr-webservice uses /asr?output=json + # whisper.cpp binary uses /inference + self._is_asr_webservice = ( + "9000" in self.endpoint + or "medivault-whisper" in self.endpoint + ) + + def transcribe(self, audio_bytes: bytes, filename: str) -> tuple[str, list[DiarizedSegment]]: + if self._is_asr_webservice: + return self._transcribe_asr_webservice(audio_bytes, filename) + else: + return self._transcribe_whisper_cpp(audio_bytes, filename) + + def _transcribe_asr_webservice(self, audio_bytes: bytes, filename: str) -> tuple[str, list[DiarizedSegment]]: + """onerahmet/openai-whisper-asr-webservice — /asr endpoint with output=json.""" + url = f"{self.endpoint}/asr" + ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else "wav" + mime_map = { + "wav": "audio/wav", "mp3": "audio/mpeg", "m4a": "audio/mp4", + "ogg": "audio/ogg", "webm": "audio/webm", "flac": "audio/flac", + } + mime = mime_map.get(ext, "audio/wav") + + # output=json returns segments with start/end times — needed for LLM diarization + files = {"audio_file": (filename, audio_bytes, mime)} + params = {"output": "json", "encode": "true", "task": "transcribe", "language": "en"} + + logger.info("Transcribing via whisper-asr-webservice: %s (%d bytes)", filename, len(audio_bytes)) + with httpx.Client(timeout=600.0) as client: + response = client.post(url, files=files, params=params) + response.raise_for_status() + result = response.json() + + raw_segments = result.get("segments", []) + full_text = result.get("text", "").strip() + + if raw_segments: + segments = self._diarize_with_llm(raw_segments) + else: + segments = self._diarize_flat(full_text) + + return full_text, segments + + def _transcribe_whisper_cpp(self, audio_bytes: bytes, filename: str) -> tuple[str, list[DiarizedSegment]]: + """whisper.cpp server binary — /inference endpoint.""" + url = f"{self.endpoint}/inference" + files = {"file": (filename, audio_bytes, "audio/wav")} + data = {"response_format": "verbose_json"} + + logger.info("Transcribing via whisper.cpp: %s (%d bytes)", filename, len(audio_bytes)) + with httpx.Client(timeout=600.0) as client: + response = client.post(url, files=files, data=data) + response.raise_for_status() + result = response.json() + + raw_segments = result.get("segments", []) + full_text = result.get("text", "").strip() + + if raw_segments: + segments = self._diarize_with_llm(raw_segments) + else: + segments = self._diarize_flat(full_text) + + return full_text, segments + + def _diarize_with_llm(self, raw_segments: list[dict]) -> list[DiarizedSegment]: + """ + Format segments as numbered timestamped entries for LLM diarization + and send all at once to the LLM. + + The LLM returns utterances with speaker, combined text, and start timestamp. + Falls back to gap-heuristic if LLM fails. + """ + valid_segs = [s for s in raw_segments if s.get("text", "").strip()] + if not valid_segs: + full_text = " ".join(s.get("text", "") for s in raw_segments) + return self._diarize_flat(full_text) + + from services.llm_client import diarize_segments as llm_diarize + + lines = [] + for idx, seg in enumerate(valid_segs): + start = float(seg.get("start", 0.0)) + text = seg["text"].strip() + lines.append(f"[ID: {idx}] (Start: {start:.1f}s): {text}") + + segments_text = "\n".join(lines) + logger.info("LLM diarization: %d segments", len(valid_segs)) + + try: + raw_json = llm_diarize(segments_text) + + # Strip markdown fences if model wraps output + cleaned = raw_json.strip() + if cleaned.startswith("```"): + cleaned = cleaned.split("```", 2)[-1] if cleaned.count("```") >= 2 else cleaned + if "\n" in cleaned: + cleaned = cleaned.split("\n", 1)[1] + cleaned = cleaned.rstrip("`").strip() + + # Extract JSON array + start_idx = cleaned.find("[") + end_idx = cleaned.rfind("]") + if start_idx != -1 and end_idx != -1 and end_idx > start_idx: + cleaned = cleaned[start_idx: end_idx + 1] + + utterances = json.loads(cleaned) + if not isinstance(utterances, list): + raise ValueError("Expected JSON array") + + all_turns: list[DiarizedSegment] = [] + for utt in utterances: + speaker = utt.get("speaker", "") + text = utt.get("text", "").strip() + start_s = float(utt.get("start", 0.0)) + if speaker in ("Doctor", "Patient") and text: + all_turns.append(DiarizedSegment( + speaker=speaker, + text=text, + start_ms=int(start_s * 1000), + )) + + if all_turns: + all_turns = self._correct_diarization(all_turns) + logger.info("LLM diarization complete: %d turns", len(all_turns)) + return all_turns + + raise ValueError("LLM returned empty utterances list") + + except Exception as exc: + logger.warning("LLM diarization failed (%s) — falling back to gap-heuristic", exc) + return self._diarize_gap(raw_segments) + + def _correct_diarization(self, turns: list[DiarizedSegment]) -> list[DiarizedSegment]: + """ + Post-processing: only enforce that Doctor speaks first. + If the LLM assigned Patient to turn 0, swap ALL speakers globally. + No run-length correction — consecutive same-speaker turns are valid + (e.g. doctor narrating exam findings across multiple turns). + """ + if not turns: + return turns + + if turns[0].speaker == "Patient": + logger.info("Diarization correction: turn 0 was Patient — swapping all speakers") + opposite = {"Doctor": "Patient", "Patient": "Doctor"} + turns = [ + DiarizedSegment(speaker=opposite[t.speaker], text=t.text, start_ms=t.start_ms) + for t in turns + ] + + return turns + + def _diarize_gap(self, raw_segments: list[dict]) -> list[DiarizedSegment]: + """ + Gap-heuristic fallback diarization. + Assigns speakers based on silence gaps (>1200 ms = speaker change). + Used only when LLM diarization fails. + """ + diarized = [] + current_speaker: Optional[str] = None + last_end_ms = 0 + + for seg in raw_segments: + start_ms = int(seg.get("start", 0) * 1000) + end_ms = int(seg.get("end", 0) * 1000) + text = seg.get("text", "").strip() + + if not text: + last_end_ms = end_ms + continue + + text_lower = text.lower() + if text_lower.startswith("doctor") or text_lower.startswith("(speaker ?) doctor"): + current_speaker = "Doctor" + for prefix in ["(speaker ?) doctor,", "(speaker ?) doctor:", "doctor,", "doctor:"]: + if text_lower.startswith(prefix): + text = text[len(prefix):].strip() + break + elif text_lower.startswith("patient") or text_lower.startswith("(speaker ?) patient"): + current_speaker = "Patient" + for prefix in ["(speaker ?) patient,", "(speaker ?) patient:", "patient,", "patient:"]: + if text_lower.startswith(prefix): + text = text[len(prefix):].strip() + break + else: + gap_ms = start_ms - last_end_ms + if current_speaker is None: + current_speaker = "Doctor" + elif gap_ms > 1200: + current_speaker = "Patient" if current_speaker == "Doctor" else "Doctor" + + if text: + diarized.append(DiarizedSegment( + speaker=current_speaker, + text=text, + start_ms=start_ms, + )) + last_end_ms = end_ms + + return diarized if diarized else self._diarize_flat( + " ".join(s.get("text", "") for s in raw_segments) + ) + + def _diarize_flat(self, text: str) -> list[DiarizedSegment]: + """Last-resort fallback: split full transcript into alternating speaker turns by sentence.""" + if not text.strip(): + return [] + sentences = [s.strip() for s in text.replace("?", "?.").replace("!", "!.").split(".") if s.strip()] + segments = [] + speaker = "Doctor" + for i, sentence in enumerate(sentences): + segments.append(DiarizedSegment(speaker=speaker, text=sentence, start_ms=i * 3000)) + if i % 2 == 1: + speaker = "Patient" if speaker == "Doctor" else "Doctor" + return segments + + def is_connected(self) -> bool: + try: + with httpx.Client(timeout=5.0) as client: + # asr-webservice: GET / returns 200 with API info + # whisper.cpp: GET /health returns 200 + check_url = self.endpoint + ("/" if self._is_asr_webservice else "/health") + response = client.get(check_url, follow_redirects=True) + return response.status_code in (200, 404) # 404 on /health is still "server up" + except Exception: + return False + + +_whisper_client: Optional[WhisperClient] = None + + +def get_whisper_client() -> WhisperClient: + global _whisper_client + if _whisper_client is None: + _whisper_client = WhisperClient() + return _whisper_client diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..a45d7a9 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,142 @@ +services: + medivault-api: + build: + context: ./api + dockerfile: Dockerfile + container_name: medivault-api + ports: + - "5001:5001" + env_file: + - .env + environment: + # Flowise + - FLOWISE_ENDPOINT=${FLOWISE_ENDPOINT:-http://medivault-flowise:3001} + - FLOWISE_API_KEY=${FLOWISE_API_KEY:-} + - SOAP_FLOW_ID=${SOAP_FLOW_ID:-} + - QA_FLOW_ID=${QA_FLOW_ID:-} + # Ollama — sole LLM provider (fully air-gapped, runs on host for GPU acceleration) + - OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://host.docker.internal:11434} + - OLLAMA_MODEL=${OLLAMA_MODEL:-llama3.1:8b} + - OLLAMA_EMBED_MODEL=${OLLAMA_EMBED_MODEL:-nomic-embed-text} + # ChromaDB (Docker service — auto-managed) + - CHROMA_HOST=${CHROMA_HOST:-medivault-chromadb} + - CHROMA_PORT=${CHROMA_PORT:-8000} + # Whisper — Docker service (medivault-whisper) or host fallback + - WHISPER_ENDPOINT=${WHISPER_ENDPOINT:-http://host.docker.internal:8080} + # Limits + - MAX_AUDIO_SIZE=${MAX_AUDIO_SIZE:-52428800} + - MAX_FILE_SIZE=${MAX_FILE_SIZE:-10485760} + networks: + - medivault-network + extra_hosts: + - "host.docker.internal:host-gateway" + depends_on: + - medivault-flowise + - medivault-whisper + - medivault-chromadb + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5001/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + + medivault-ui: + build: + context: ./ui + dockerfile: Dockerfile + container_name: medivault-ui + ports: + - "3000:8080" + depends_on: + - medivault-api + networks: + - medivault-network + restart: unless-stopped + + medivault-chromadb: + image: chromadb/chroma:latest + container_name: medivault-chromadb + ports: + - "8100:8000" + volumes: + - chroma-data:/chroma/chroma + environment: + - IS_PERSISTENT=TRUE + - ANONYMIZED_TELEMETRY=FALSE + networks: + - medivault-network + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "bash -c 'exec 3<>/dev/tcp/localhost/8000 && echo -e \"GET /api/v2/heartbeat HTTP/1.0\\r\\nHost: localhost\\r\\n\\r\\n\" >&3 && grep -q nanosecond <&3'"] + interval: 15s + timeout: 5s + retries: 5 + start_period: 10s + + medivault-flowise: + image: flowiseai/flowise:latest + container_name: medivault-flowise + ports: + - "3001:3001" + volumes: + - flowise-data:/root/.flowise + environment: + - PORT=3001 + networks: + - medivault-network + extra_hosts: + - "host.docker.internal:host-gateway" + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://localhost:3001/api/v1/ping | grep -q pong"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 60s + + medivault-whisper: + # Whisper.cpp-based ASR — 4-8x faster than Python Whisper on CPU. + # Same /asr endpoint and request format — zero code changes needed. + # Model downloaded automatically on first run to the whisper-models volume. + # + # GPU (NVIDIA): change tag to :latest-gpu and add nvidia device reservations. + image: onerahmet/openai-whisper-asr-webservice:latest + container_name: medivault-whisper + ports: + - "9000:9000" + volumes: + - whisper-models:/root/.cache/whisper + environment: + # Model size: tiny | base | small | medium | large-v3 + # Recommended: small (good accuracy, ~460 MB, ~30-45s for 2-min audio on CPU) + - ASR_MODEL=${WHISPER_MODEL:-small} + - ASR_ENGINE=faster_whisper + networks: + - medivault-network + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "curl -sf -o /dev/null -w '%{http_code}' http://localhost:9000/docs | grep -q 200 || exit 1"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 300s + + # ── Host services (NOT in Docker) ────────────────────────────────────────── + # + # Ollama :11434 — LLM inference (runs on host for GPU acceleration) + # Install: https://ollama.com/download + # Models: ollama pull llama3.1:8b && ollama pull nomic-embed-text + +networks: + medivault-network: + driver: bridge + +volumes: + flowise-data: + chroma-data: + whisper-models: + # Persists downloaded Whisper model across container restarts. + # First run downloads ~460 MB (small) to ~3 GB (large-v3). + # Subsequent starts are instant. diff --git a/docs/assets/InnovationHub-HeaderImage.png b/docs/assets/InnovationHub-HeaderImage.png new file mode 100644 index 0000000000000000000000000000000000000000..0558e3792690bbcfa699cf0002d31ebd1df5b904 GIT binary patch literal 184925 zcmeFYby!qw*Ep&Vub`xYl%%3Gj11i(B@GfoBQZ1#-SH>_(j7yJLpKbaN;?eQFo1M- z4|T>z-uL^y-#LGr>zsc-F1XnD-s@iXeXm|?uN|VQEK7j<4ENr>djxXtq~71V_W*V8 z-u>f;e_`(AuX%W5{ylPdr{jF@9%0AtzxxSngjDzL{j1kXL)%4LNm0lYV#jH01~CD1 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ú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 literal 0 HcmV?d00001 diff --git a/ui/.dockerignore b/ui/.dockerignore new file mode 100644 index 0000000..25e0123 --- /dev/null +++ b/ui/.dockerignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +.git/ +.gitignore +*.log diff --git a/ui/Dockerfile b/ui/Dockerfile new file mode 100644 index 0000000..09e201d --- /dev/null +++ b/ui/Dockerfile @@ -0,0 +1,27 @@ +FROM node:18-alpine AS builder + +WORKDIR /app + +COPY package.json . +RUN npm install + +COPY . . +RUN npm run build + +FROM nginx:alpine + +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf + +RUN addgroup -S appuser && adduser -S -G appuser -u 1000 appuser \ + && chown -R appuser:appuser /usr/share/nginx/html \ + && chown -R appuser:appuser /var/cache/nginx \ + && chown -R appuser:appuser /var/log/nginx \ + && touch /var/run/nginx.pid \ + && chown appuser:appuser /var/run/nginx.pid + +USER appuser + +EXPOSE 8080 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/ui/index.html b/ui/index.html new file mode 100644 index 0000000..f5d6577 --- /dev/null +++ b/ui/index.html @@ -0,0 +1,17 @@ + + + + + + + + MediVault AI + + + + + +
+ + + diff --git a/ui/nginx.conf b/ui/nginx.conf new file mode 100644 index 0000000..ee28ffb --- /dev/null +++ b/ui/nginx.conf @@ -0,0 +1,28 @@ +server { + listen 8080; + server_name _; + root /usr/share/nginx/html; + index index.html; + + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + location /api/ { + rewrite ^/api/(.*) /$1 break; + proxy_pass http://medivault-api:5001; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_read_timeout 600s; + proxy_send_timeout 600s; + proxy_connect_timeout 60s; + client_max_body_size 55m; + # Stream large file uploads directly — don't buffer in nginx temp files + proxy_request_buffering off; + proxy_buffering off; + } + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/ui/package-lock.json b/ui/package-lock.json new file mode 100644 index 0000000..ed77bf6 --- /dev/null +++ b/ui/package-lock.json @@ -0,0 +1,3251 @@ +{ + "name": "medivault-ai-ui", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "medivault-ai-ui", + "version": "1.0.0", + "dependencies": { + "@xyflow/react": "^12.3.6", + "axios": "^1.7.9", + "lucide-react": "^0.468.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.16", + "vite": "^6.0.3" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@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.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@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.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "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.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "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.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "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/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "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.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "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/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "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.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "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/@xyflow/react": { + "version": "12.10.2", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.2.tgz", + "integrity": "sha512-CgIi6HwlcHXwlkTpr0fxLv/0sRVNZ8IdwKLzzeCscaYBwpvfcH1QFOCeaTCuEn1FQEs/B8CjnTSjhs8udgmBgQ==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.76", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.76", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.76.tgz", + "integrity": "sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axios": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz", + "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.13", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.13.tgz", + "integrity": "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "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.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001784", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001784.tgz", + "integrity": "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==", + "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/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "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/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "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/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "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/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.331", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz", + "integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "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/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "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/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "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/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "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": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "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.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "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/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.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/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/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/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/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "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/vite/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/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + } + } +} diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 0000000..d7f5f10 --- /dev/null +++ b/ui/package.json @@ -0,0 +1,27 @@ +{ + "name": "medivault-ai-ui", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@xyflow/react": "^12.3.6", + "axios": "^1.7.9", + "lucide-react": "^0.468.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.16", + "vite": "^6.0.3" + } +} diff --git a/ui/postcss.config.js b/ui/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/ui/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/ui/public/cloud2labs-logo.png b/ui/public/cloud2labs-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2a0ef60222e8d01b0d287003bc0960c5e49b7cfe GIT binary patch literal 16782 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 S(53Q!!{})lYc{DvqyG=xOb|i< literal 0 HcmV?d00001 diff --git a/ui/src/App.jsx b/ui/src/App.jsx new file mode 100644 index 0000000..ee8633a --- /dev/null +++ b/ui/src/App.jsx @@ -0,0 +1,67 @@ +import { useState, useEffect } from 'react' +import Header from './components/Header' +import LandingPage from './components/LandingPage' +import ConsultationRecorder from './components/ConsultationRecorder' +import ClinicalChat from './components/ClinicalChat' +import KnowledgeBase from './components/KnowledgeBase' +import { Mic, MessageSquare, Database } from 'lucide-react' + +const TABS = [ + { id: 'record', label: 'SOAP Notes', icon: Mic }, + { id: 'chat', label: 'Clinical QA', icon: MessageSquare }, + { id: 'knowledge', label: 'Knowledge Base', icon: Database }, +] + +function App() { + const [darkMode, setDarkMode] = useState(() => { + const saved = localStorage.getItem('darkMode') + return saved !== null ? JSON.parse(saved) : true + }) + const [launched, setLaunched] = useState(false) + const [activeTab, setActiveTab] = useState('record') + + useEffect(() => { + localStorage.setItem('darkMode', JSON.stringify(darkMode)) + if (darkMode) { + document.documentElement.classList.add('dark') + } else { + document.documentElement.classList.remove('dark') + } + }, [darkMode]) + + if (!launched) { + return setLaunched(true)} /> + } + + return ( +
+
setDarkMode(d => !d)} onHome={() => setLaunched(false)} /> + +
+
+ {TABS.map(({ id, label, icon: Icon }) => ( + + ))} +
+ + {/* Keep all tabs mounted to preserve state when switching — only hide inactive ones */} +
+
+
+
+
+ ) +} + +export default App diff --git a/ui/src/components/AudioRecorder.jsx b/ui/src/components/AudioRecorder.jsx new file mode 100644 index 0000000..756aa0c --- /dev/null +++ b/ui/src/components/AudioRecorder.jsx @@ -0,0 +1,175 @@ +import { useState, useRef } from 'react' +import axios from 'axios' +import { Mic, MicOff, Upload, Square, Loader2 } from 'lucide-react' +import SoapNoteEditor from './SoapNoteEditor' +import StatusBadge from './StatusBadge' + +const API_URL = import.meta.env.VITE_API_URL || '/api' + +const SPECIALTIES = ['general', 'emergency', 'cardiology', 'pediatrics', 'psychiatry', 'orthopedics', 'dermatology'] + +export default function AudioRecorder() { + const [mode, setMode] = useState('upload') + const [specialty, setSpecialty] = useState('general') + const [recording, setRecording] = useState(false) + const [status, setStatus] = useState('idle') + const [transcript, setTranscript] = useState('') + const [soap, setSoap] = useState(null) + const [error, setError] = useState('') + + const mediaRef = useRef(null) + const chunksRef = useRef([]) + const fileInputRef = useRef(null) + + const startRecording = async () => { + try { + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }) + const recorder = new MediaRecorder(stream) + chunksRef.current = [] + recorder.ondataavailable = e => chunksRef.current.push(e.data) + recorder.onstop = () => processAudioBlob(new Blob(chunksRef.current, { type: 'audio/webm' }), 'recording.webm') + recorder.start() + mediaRef.current = recorder + setRecording(true) + setError('') + } catch (e) { + setError('Microphone access denied.') + } + } + + const stopRecording = () => { + mediaRef.current?.stop() + mediaRef.current?.stream?.getTracks().forEach(t => t.stop()) + setRecording(false) + } + + const handleFileUpload = (e) => { + const file = e.target.files?.[0] + if (file) processAudioBlob(file, file.name) + } + + const handleDrop = (e) => { + e.preventDefault() + const file = e.dataTransfer.files?.[0] + if (file) processAudioBlob(file, file.name) + } + + const processAudioBlob = async (blob, filename) => { + setStatus('loading') + setTranscript('') + setSoap(null) + setError('') + + try { + const formData = new FormData() + formData.append('file', blob, filename) + + const transcribeRes = await axios.post(`${API_URL}/transcribe`, formData) + const text = transcribeRes.data.transcript + setTranscript(text) + + const soapRes = await axios.post(`${API_URL}/generate-soap`, { + transcript: text, + specialty, + }) + setSoap(soapRes.data.soap) + setStatus('success') + } catch (e) { + setError(e.response?.data?.detail || 'Processing failed.') + setStatus('error') + } + } + + return ( +
+
+
+

Audio → SOAP Note

+ +
+ +
+
+ {['upload', 'record'].map(m => ( + + ))} +
+ + +
+ + {mode === 'upload' ? ( +
e.preventDefault()} + onClick={() => fileInputRef.current?.click()} + className="border-2 border-dashed rounded-xl p-10 text-center cursor-pointer transition-colors dark:border-slate-700/50 dark:hover:border-purple-500/50 border-gray-200 hover:border-purple-400" + > + +

Drop audio file or click to browse

+

WAV · MP3 · M4A · OGG · WEBM · FLAC — max 50 MB

+ +
+ ) : ( +
+ +

+ {recording ? 'Recording… click to stop' : 'Click to start recording'} +

+
+ )} + + {error && ( +

{error}

+ )} +
+ + {status === 'loading' && ( +
+ +

Transcribing audio and generating SOAP note…

+
+ )} + + {transcript && soap && ( +
+
+

Transcript

+

{transcript}

+
+ +
+ )} +
+ ) +} diff --git a/ui/src/components/ClinicalChat.jsx b/ui/src/components/ClinicalChat.jsx new file mode 100644 index 0000000..32bc31f --- /dev/null +++ b/ui/src/components/ClinicalChat.jsx @@ -0,0 +1,163 @@ +import { useState, useRef, useEffect } from 'react' +import axios from 'axios' +import { Send, Loader2, BookOpen, FileText, ClipboardList, AlertTriangle } from 'lucide-react' + +const API_URL = import.meta.env.VITE_API_URL || '/api' + +function generateSessionId() { + return crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2) +} + +const DocTypeBadge = ({ type }) => { + const isSoap = type === 'soap_note' + return ( + + {isSoap ? : } + {isSoap ? 'SOAP Note' : 'Guideline'} + + ) +} + +export default function ClinicalChat() { + const [messages, setMessages] = useState([]) + const [input, setInput] = useState('') + const [loading, setLoading] = useState(false) + const [sessionId] = useState(generateSessionId) + const bottomRef = useRef(null) + + useEffect(() => { + bottomRef.current?.scrollIntoView({ behavior: 'smooth' }) + }, [messages]) + + const send = async () => { + if (!input.trim() || loading) return + const question = input.trim() + setInput('') + setMessages(prev => [...prev, { role: 'user', content: question, sources: [] }]) + setLoading(true) + try { + const res = await axios.post(`${API_URL}/chat`, { question, session_id: sessionId }) + setMessages(prev => [...prev, { + role: 'assistant', + content: res.data.answer, + sources: res.data.sources || [], + }]) + } catch (e) { + const status = e.response?.status + let userMessage = 'Something went wrong. Please try again.' + if (status === 503) { + userMessage = 'The AI service is temporarily unavailable. Please check that Flowise and the LLM provider are running.' + } else if (status === 500) { + userMessage = 'The AI could not process your question. Please try rephrasing or check the knowledge base has documents.' + } else if (status === 422) { + userMessage = 'Your question could not be processed. Please try again with a different phrasing.' + } else if (!e.response) { + userMessage = 'Cannot reach the MediVault API. Please check your connection.' + } + setMessages(prev => [...prev, { + role: 'assistant', + content: userMessage, + sources: [], + error: true, + }]) + } finally { + setLoading(false) + } + } + + return ( +
+
+

Clinical QA

+

+ RAG over clinical guidelines and approved SOAP notes — all inference runs offline +

+
+ +
+ {messages.length === 0 && ( +
+ +

Ask a clinical question about guidelines or past consultations

+
+ + +
+
+ )} + + {messages.map((msg, i) => ( +
+
+
+ {msg.content} +
+ + {msg.sources?.length > 0 && ( +
+ {msg.sources.map((src, j) => ( +
+ + + {src.document} + {src.chunk && — {src.chunk.slice(0, 100)}…} + +
+ ))} +
+ )} +
+
+ ))} + + {loading && ( +
+
+ +
+
+ )} +
+
+ +
+
+