diff --git a/.gitignore b/.gitignore index 371f084..385bc14 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ android/.idea/ *.aab *.keystore *.jks +public/models/ diff --git a/BROWSER_WHISPER_SETUP.md b/BROWSER_WHISPER_SETUP.md new file mode 100644 index 0000000..fdf2050 --- /dev/null +++ b/BROWSER_WHISPER_SETUP.md @@ -0,0 +1,174 @@ +# Browser Whisper Self-Hosted Setup + +## Overview + +As of v18, Browser Whisper is **fully self-hosted** with **zero CDN dependencies**. All models and libraries are bundled with the application. + +## What Changed + +**Before (v17 and earlier):** +- Loaded transformers.js from `cdn.jsdelivr.net` +- Downloaded models from `cdn-lfs.huggingface.co` +- Failed in corporate/clinical networks with firewall restrictions + +**Now (v18+):** +- Transformers.js library bundled at `/models/transformers.min.js` +- Whisper model bundled at `/models/Xenova/whisper-tiny.en/` +- Everything served from your own server +- **Works in any network environment** + +## Files Included + +``` +public/models/ +├── transformers.min.js (~876KB) - Transformers.js library +└── Xenova/ + └── whisper-tiny.en/ (~42MB total) + ├── config.json + ├── tokenizer.json + ├── preprocessor_config.json + ├── generation_config.json + └── onnx/ + ├── encoder_model_quantized.onnx + └── decoder_model_merged_quantized.onnx +``` + +## How It Works + +1. **Worker loads transformers.js locally:** + ```javascript + importScripts('/models/transformers.min.js'); + ``` + +2. **Transformers.js configured for local models:** + ```javascript + T.env.localModelPath = '/models/'; + T.env.allowRemoteModels = false; + ``` + +3. **Models load from your server:** + - Browser requests: `GET /models/Xenova/whisper-tiny.en/config.json` + - Served by Express static middleware + - No external network calls + +## Docker Build + +Models are downloaded **during Docker build** (not runtime): + +```dockerfile +RUN curl -sL -o onnx/encoder_model_quantized.onnx \ + https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/onnx/encoder_model_quantized.onnx +``` + +This means: +- Docker image is ~200MB larger (one-time cost) +- Runtime has zero dependencies +- Works in air-gapped environments (after image is pulled) + +## Development Setup + +If you're running locally (not Docker), download models: + +```bash +cd public/models +mkdir -p Xenova/whisper-tiny.en/onnx + +# Download transformers.js +curl -L -o transformers.min.js \ + https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2/dist/transformers.min.js + +# Download model files +cd Xenova/whisper-tiny.en +curl -L -o config.json \ + https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/config.json +curl -L -o tokenizer.json \ + https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/tokenizer.json +curl -L -o preprocessor_config.json \ + https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/preprocessor_config.json +curl -L -o generation_config.json \ + https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/generation_config.json +curl -L -o onnx/encoder_model_quantized.onnx \ + https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/onnx/encoder_model_quantized.onnx +curl -L -o onnx/decoder_model_merged_quantized.onnx \ + https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/onnx/decoder_model_merged_quantized.onnx +``` + +Or use the helper script: + +```bash +./scripts/download-whisper-models.sh +``` + +## Adding More Models + +To add base or small models: + +1. **Create directory:** + ```bash + mkdir -p public/models/Xenova/whisper-base.en/onnx + ``` + +2. **Download from HuggingFace:** + - https://huggingface.co/Xenova/whisper-base.en + - https://huggingface.co/Xenova/whisper-small.en + +3. **Update UI in `settings.html`:** + ```html + + ``` + +4. **Update Dockerfile** to download during build + +## Benefits + +✅ **Works everywhere** - No firewall/CDN issues +✅ **Privacy-first** - Audio never leaves browser +✅ **Offline capable** - After initial page load +✅ **No API costs** - Zero transcription expenses +✅ **Predictable** - Same model, same results +✅ **Fast** - Local processing, no network latency + +## Limitations + +- Docker image is larger (~200MB vs ~150MB) +- Only tiny model included by default (base/small optional) +- Slower than cloud APIs for long recordings +- Requires modern browser with WebAssembly support + +## Testing + +```bash +# 1. Start server +docker-compose up -d + +# 2. Open browser DevTools → Network tab +# 3. Go to Settings → Browser Transcription +# 4. Click "Pre-download model" +# 5. Watch for requests to /models/* (should all be 200 OK from your server) +# 6. NO requests to cdn.jsdelivr.net or huggingface.co +``` + +## Troubleshooting + +**Issue: "Failed to load transformers library"** +- Check: `GET /models/transformers.min.js` returns 200 OK +- Verify file exists: `ls public/models/transformers.min.js` + +**Issue: "Model load failed"** +- Check: `GET /models/Xenova/whisper-tiny.en/config.json` returns 200 OK +- Verify files exist: `ls public/models/Xenova/whisper-tiny.en/` + +**Issue: Still seeing CDN requests** +- Clear browser cache (Ctrl+Shift+R) +- Check you're running v18+ (`/api/health` should show version) + +## Migration from v17 + +If upgrading from v17: + +1. Pull new Docker image: `docker-compose pull` +2. Restart: `docker-compose up -d` +3. Clear browser cache +4. Test: Settings → Browser Transcription → Pre-download + +No configuration changes needed - it just works! diff --git a/Dockerfile b/Dockerfile index 75d7c78..57969cd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,8 @@ FROM node:20-alpine WORKDIR /app # ffmpeg: audio conversion for AWS Transcribe (WebM → PCM) -RUN apk add --no-cache ffmpeg +# curl: download Whisper models for browser-based transcription +RUN apk add --no-cache ffmpeg curl COPY package.json ./ RUN npm install --omit=dev @@ -12,6 +13,20 @@ COPY . . RUN mkdir -p /app/data/logs +# Download Browser Whisper models (self-hosted, no CDN dependency) +RUN mkdir -p /app/public/models/Xenova/whisper-tiny.en/onnx && \ + cd /app/public/models/Xenova/whisper-tiny.en && \ + echo "Downloading Whisper model files..." && \ + curl -sL -o config.json https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/config.json && \ + curl -sL -o tokenizer.json https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/tokenizer.json && \ + curl -sL -o preprocessor_config.json https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/preprocessor_config.json && \ + curl -sL -o generation_config.json https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/generation_config.json && \ + curl -sL -o onnx/encoder_model_quantized.onnx https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/onnx/encoder_model_quantized.onnx && \ + curl -sL -o onnx/decoder_model_merged_quantized.onnx https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/onnx/decoder_model_merged_quantized.onnx && \ + cd /app/public/models && \ + curl -sL -o transformers.min.js https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2/dist/transformers.min.js && \ + echo "Whisper models downloaded successfully" + EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=5s --start-period=20s \ diff --git a/docker-compose.yml b/docker-compose.yml index 6c205cd..cc4fd0f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: pediatric-scribe: - image: danielonyejesi/pediatric-ai-scribe-v3:v17 + image: danielonyejesi/pediatric-ai-scribe-v3:v18 ports: - "3552:3000" env_file: diff --git a/package.json b/package.json index 396d7bb..2523340 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pediatric-ai-scribe", - "version": "17.0.0", + "version": "18.0.0", "description": "AI-powered pediatric clinical documentation platform", "main": "server.js", "scripts": { diff --git a/public/js/whisperWorker.js b/public/js/whisperWorker.js index 79e33fe..79d8dcd 100644 --- a/public/js/whisperWorker.js +++ b/public/js/whisperWorker.js @@ -4,16 +4,17 @@ // ============================================================ console.log('[WhisperWorker] Starting worker initialization'); -console.log('[WhisperWorker] Attempting to load transformers from CDN...'); +console.log('[WhisperWorker] Loading transformers from local server...'); try { - importScripts('https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2'); + // Load transformers.js from our own server (no CDN dependency!) + importScripts('/models/transformers.min.js'); console.log('[WhisperWorker] Transformers library loaded successfully'); } catch (err) { console.error('[WhisperWorker] Failed to load transformers library:', err); self.postMessage({ type: 'error', - message: 'Failed to load transformers library. Check network/firewall: ' + err.message + message: 'Failed to load transformers library: ' + err.message }); throw err; } @@ -23,9 +24,13 @@ if (!T) { console.error('[WhisperWorker] Transformers object not found after import'); throw new Error('Transformers library did not initialize'); } + +// Configure transformers.js to load models from our local server T.env.allowLocalModels = false; T.env.useBrowserCache = true; -console.log('[WhisperWorker] Transformers configured'); +T.env.allowRemoteModels = false; // Force local-only +T.env.localModelPath = '/models/'; // Our bundled models +console.log('[WhisperWorker] Transformers configured for local models at /models/'); var _pipe = null; var _loadedModel = null; diff --git a/scripts/download-whisper-models.sh b/scripts/download-whisper-models.sh new file mode 100755 index 0000000..42d55bd --- /dev/null +++ b/scripts/download-whisper-models.sh @@ -0,0 +1,69 @@ +#!/bin/bash +# Download Browser Whisper models for local development +# These are bundled during Docker build, but need manual download for dev + +set -e + +cd "$(dirname "$0")/.." +MODELS_DIR="public/models" + +echo "🎙️ Downloading Browser Whisper models..." +echo "" + +# Create directories +mkdir -p "$MODELS_DIR/Xenova/whisper-tiny.en/onnx" + +# Download transformers.js +echo "📦 Downloading transformers.js..." +curl -L --progress-bar -o "$MODELS_DIR/transformers.min.js" \ + "https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2/dist/transformers.min.js" +echo "✅ transformers.min.js ($(du -h $MODELS_DIR/transformers.min.js | cut -f1))" +echo "" + +# Download model config files +echo "📝 Downloading model configs..." +cd "$MODELS_DIR/Xenova/whisper-tiny.en" + +curl -sL -o config.json \ + "https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/config.json" +echo "✅ config.json ($(du -h config.json | cut -f1))" + +curl -sL -o tokenizer.json \ + "https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/tokenizer.json" +echo "✅ tokenizer.json ($(du -h tokenizer.json | cut -f1))" + +curl -sL -o preprocessor_config.json \ + "https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/preprocessor_config.json" +echo "✅ preprocessor_config.json ($(du -h preprocessor_config.json | cut -f1))" + +curl -sL -o generation_config.json \ + "https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/generation_config.json" +echo "✅ generation_config.json ($(du -h generation_config.json | cut -f1))" +echo "" + +# Download ONNX models (large files) +echo "🧠 Downloading encoder model..." +curl -L --progress-bar -o onnx/encoder_model_quantized.onnx \ + "https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/onnx/encoder_model_quantized.onnx" +echo "✅ encoder_model_quantized.onnx ($(du -h onnx/encoder_model_quantized.onnx | cut -f1))" +echo "" + +echo "🧠 Downloading decoder model..." +curl -L --progress-bar -o onnx/decoder_model_merged_quantized.onnx \ + "https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/onnx/decoder_model_merged_quantized.onnx" +echo "✅ decoder_model_merged_quantized.onnx ($(du -h onnx/decoder_model_merged_quantized.onnx | cut -f1))" +echo "" + +# Show summary +cd - > /dev/null +echo "════════════════════════════════════════" +echo "✅ Browser Whisper models downloaded!" +echo "════════════════════════════════════════" +echo "Total size: $(du -sh $MODELS_DIR | cut -f1)" +echo "" +echo "Files:" +ls -lh "$MODELS_DIR/Xenova/whisper-tiny.en/" | tail -n +2 +echo "" +ls -lh "$MODELS_DIR/Xenova/whisper-tiny.en/onnx/" | tail -n +2 +echo "" +echo "Models are ready for use. Start server with: npm start" diff --git a/server.js b/server.js index b1618c7..bd0612e 100644 --- a/server.js +++ b/server.js @@ -152,7 +152,7 @@ app.get('/api/models', async (req, res) => { const { activeProvider } = require('./src/utils/ai'); app.get('/api/health', (req, res) => { res.json({ - status: 'running', version: '17.0.0', provider: activeProvider, + status: 'running', version: '18.0.0', provider: activeProvider, timestamp: new Date().toISOString(), openrouter: process.env.OPENROUTER_API_KEY ? 'configured' : 'missing', bedrock: process.env.AWS_BEDROCK_REGION ? 'configured' : 'not configured', @@ -220,7 +220,7 @@ const PORT = process.env.PORT || 3000; server.listen(PORT, () => { console.log(''); console.log('=========================================='); - console.log('🏥 PEDIATRIC AI SCRIBE v13.0'); + console.log('🏥 PEDIATRIC AI SCRIBE v18.0'); console.log('=========================================='); console.log('🌐 http://localhost:' + PORT); console.log('🤖 Provider: ' + activeProvider);