A completely private, offline-first AI writing assistant that runs entirely in your browser using on-device LLM inference. Generate emails, essays, creative content, and summaries without sending any data to the cloud.
Key Principle: Your writing stays on your device. Period.
Most AI writing tools (ChatGPT, Grammarly, etc.) send your writing to their servers. This means:
- Your personal/confidential writing is exposed to third parties
- You need constant internet connectivity
- You pay subscription fees per month
- Your prompts train their models
A professional-grade writing assistant that:
- ✅ Runs 100% locally – Model loads once, then works offline forever
- ✅ AI-powered writing – LFM2 350M language model (Liquid AI)
- ✅ 5+ writing modes – Email, Essay, Creative, Summary, Tone Rewriter
- ✅ PDF support – Extract and rewrite text from PDFs
- ✅ Export-ready – Save as .txt or .md files
- ✅ Privacy-first – No cloud calls, no tracking, no data collection
- ✅ WebGPU acceleration – Runs faster on devices with GPU support
┌─────────────────────────────────────────────────────────────┐
│ OfflineScribe UI │
│ (React 19 + TypeScript + Vite 6.4.1) │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ RunAnywhere Web SDK │
│ ├── ModelManager (download/cache/load models) │
│ ├── ModelCategory (Language, Vision, STT, TTS) │
│ ├── EventBus (download progress, model events) │
│ └── TextGeneration + VLMWorkerBridge │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ WebAssembly Inference Engines │
│ ├── llama.cpp (LLM inference on CPU/GPU) │
│ ├── sherpa-onnx (STT/TTS/VAD) │
│ └── WebGPU runtime (GPU acceleration fallback) │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Browser Storage (OPFS) — Model Cache │
│ └── LFM2-350M-Q4_K_M.gguf (~500MB) │
│ Downloaded once, reused forever │
└─────────────────────────────────────────────────────────────┘
User Input (prompt)
↓
PromptEditor Component → Captures mode + text
↓
useRunAnywhere Hook → Validates model is ready
↓
TextGeneration.generateStream() → Streams tokens from LLM
↓
for await (token of stream) → Receives tokens one-by-one
↓
setOutput(accumulated) → React updates output in real-time
↓
OutputPanel Component → Renders streaming text + controls
↓
User can: Copy / Export as .txt / Export as .md
Each mode uses a custom system prompt to specialize the LLM's behavior:
System Prompt: "You are an expert email writer..."
Example: "Request for meeting reschedule"
Output: Professional, concise email draft
System Prompt: "You are an academic essay writer..."
Example: "Discuss artificial intelligence ethics"
Output: Structured essay with intro/body/conclusion
System Prompt: "You are a creative writing expert..."
Example: "A cyberpunk detective story"
Output: Engaging fictional narrative
System Prompt: "You are a summarization expert..."
Input: Long article or document
Output: Concise summary of key points
Input: Any text
Tone Options: Formal / Casual / Persuasive
Output: Same text, rewritten in selected tone
- Drag-drop interface – Upload PDFs directly
- Client-side processing – pdfjs-dist library extracts text in browser
- No server upload – Files never leave your device
- Auto-summary – PDF text automatically switches to Summary mode
- Statistics – Shows character count, line count, page count
- LLM generates tokens one at a time
- UI updates appear as text streams (like ChatGPT)
- Responsive batching – Updates grouped for smooth rendering
- No waiting for full response – see output immediately
- Copy to Clipboard – One-click copy of entire output
- Download as .txt – Plain text file for any editor
- Download as .md – Markdown file for GitHub/Docs
Shows real-time state:
- ⬇️ Downloading... X% – Model download in progress
- ⏳ Loading Model... – Loading model into memory
- 🟢 Running Locally · No Cloud – Ready to use, 100% offline
- ❌ Load Failed - Retrying... – Automatic retry if load fails
src/
├── main.tsx # React entry point
├── App.tsx # Tab navigation (Chat/Vision/Voice/Tools)
├── runanywhere.ts # SDK initialization + model catalog
│
├── hooks/
│ ├── useRunAnywhere.js # Custom hook for LLM text generation
│ │ # - Model loading
│ │ # - Streaming token handling
│ │ # - Error/timeout management
│ ├── useModelLoader.ts # Model download + load lifecycle
│ │ # - Download tracking
│ │ # - Cache management
│ │ # - State machine
│ └── useRunAnywhere.js # (existing) Model loader reference
│
├── components/
│ ├── ChatTab.tsx # Main OfflineScribe UI component
│ │ # - Mode selector
│ │ # - Editor + Output layout
│ │ # - Feature toggles
│ ├── PromptEditor.jsx # Left panel
│ │ # - Mode dropdown
│ │ # - Textarea for input
│ │ # - Generate button
│ ├── OutputPanel.jsx # Right panel
│ │ # - Displays generated text
│ │ # - Copy/Export buttons
│ │ # - Loading spinner + error display
│ ├── LocalBadge.jsx # Top-right status indicator
│ │ # - Shows download progress
│ │ # - Loading state
│ │ # - Ready/error states
│ ├── ToneRewriter.jsx # Collapsible tone feature
│ │ # - Paste text to rewrite
│ │ # - Select tone (Formal/Casual/Persuasive)
│ │ # - Live rewrite output
│ ├── PDFDropzone.jsx # Drag-drop PDF upload
│ │ # - Client-side extraction
│ │ # - Text preview + stats
│ │ # - Auto-switch to Summary mode
│ ├── VisionTab.tsx # (unchanged) Vision tab
│ ├── VoiceTab.tsx # (unchanged) Voice tab
│ ├── ToolsTab.tsx # (unchanged) Tools tab
│ ├── ModelBanner.tsx # (existing) Model download banner
│ └── LocalBadge.jsx # (existing) Local status badge
│
├── prompts/
│ └── systemPrompts.js # System prompts for all 5 modes
│ # - Email, Essay, Creative, Summary
│ # - Tone variants (Formal/Casual/Persuasive)
│ # - Placeholder examples for each mode
│
├── styles/
│ └── index.css # Complete styling (~900 lines)
│ # - Dark theme (matches RunAnywhere)
│ # - Responsive layout (1fr 1fr grid)
│ # - Component styles for all UI elements
│ # - Mobile breakpoints
│
├── workers/
│ └── vlm-worker.ts # (unchanged) Vision worker
tests/
├── web-starter-app-bugs.md
└── web-starter-app-test-suite.md
- Source: Liquid AI (LiquidAI/LFM2-350M-GGUF)
- Format: GGUF (GPU-friendly format)
- Quantization: Q4_K_M (4-bit quantized)
- Size: ~500MB (compressed)
- Loaded Size: ~350MB in memory
- Speed: ~50-100 tokens/sec on CPU (faster with GPU)
- Capabilities: Text generation, instruction following, few-shot learning
- Cache Type: OPFS (Origin Private File System)
- Persistence: Survives browser restart
- Max Size: Usually 50GB+ per origin (browser-dependent)
- Download Resume: Yes, partial downloads resume
- This App Uses: ~500MB after first download
- Input: Stays in browser memory only
- Processing: Runs on-device LLM, no network calls
- Output: Stays in browser; copied locally or downloaded
- Storage: Model cached in browser OPFS (not sent anywhere)
- Deletion: Clear browser cache = all data deleted
- ✅ No API calls to OpenAI, Anthropic, etc.
- ✅ No telemetry or tracking
- ✅ No model training on your data
- ✅ No account required
- ✅ No authentication needed
- Runs in a sandboxed browser context
- Model code in WebAssembly (cannot access OS)
- Local to your machine only
- OPFS storage isolated per website origin
This project is a PS-1 Hackathon submission built on the RunAnywhere Web Starter template. See LICENSE for details.
Built with:
- RunAnywhere Web SDK – For on-device AI inference
- LFM2 Model – By Liquid AI
- Vite – For build tooling
- React 19 – For UI framework