Upload 11 files
Browse files- .env.example +31 -0
- .gitignore +10 -0
- README.md +106 -0
- UPLOAD_GUIDE.txt +139 -0
- app.py +227 -0
- brain.py +161 -0
- config.py +33 -0
- memory.py +101 -0
- requirements.txt +23 -0
- thinking.py +110 -0
- voice_tts.py +43 -0
.env.example
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# JARVIS environment configuration
|
| 2 |
+
# Copy this to .env and fill in your values.
|
| 3 |
+
# On HuggingFace Spaces: set these in Settings → Variables & Secrets instead.
|
| 4 |
+
|
| 5 |
+
# --- Model selection ---
|
| 6 |
+
JARVIS_MODEL=microsoft/Phi-3-mini-4k-instruct
|
| 7 |
+
# Alternatively for deeper reasoning (still fits 16GB with 4-bit):
|
| 8 |
+
# JARVIS_MODEL=mistralai/Mistral-7B-Instruct-v0.2
|
| 9 |
+
|
| 10 |
+
# --- Quantisation (reduces RAM dramatically) ---
|
| 11 |
+
JARVIS_QUANT=4bit
|
| 12 |
+
# Options: 4bit | 8bit | none
|
| 13 |
+
|
| 14 |
+
# --- Memory paths ---
|
| 15 |
+
# Leave blank to use ./data/memory (relative, HF-safe)
|
| 16 |
+
JARVIS_MEMORY_DIR=
|
| 17 |
+
JARVIS_DB_PATH=
|
| 18 |
+
|
| 19 |
+
# --- Voice ---
|
| 20 |
+
JARVIS_TTS_VOICE=en-US-GuyNeural
|
| 21 |
+
# Other voice options: en-US-JennyNeural, en-GB-RyanNeural
|
| 22 |
+
|
| 23 |
+
# --- Whisper STT (only used if you add mic input) ---
|
| 24 |
+
JARVIS_WHISPER_MODEL=base
|
| 25 |
+
|
| 26 |
+
# --- Thinking engine ---
|
| 27 |
+
JARVIS_THINKING=true
|
| 28 |
+
JARVIS_THINK_INTERVAL=3600
|
| 29 |
+
|
| 30 |
+
# --- Gradio server ---
|
| 31 |
+
PORT=7860
|
.gitignore
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# .gitignore for JARVIS HuggingFace Space
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.pyc
|
| 4 |
+
*.pyo
|
| 5 |
+
.env
|
| 6 |
+
data/
|
| 7 |
+
*.db
|
| 8 |
+
*.log
|
| 9 |
+
.DS_Store
|
| 10 |
+
Thumbs.db
|
README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: JARVIS - Personal AI Agent
|
| 3 |
+
emoji: 🤖
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: gray
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: "4.26.0"
|
| 8 |
+
app_file: app.py
|
| 9 |
+
pinned: true
|
| 10 |
+
license: mit
|
| 11 |
+
tags:
|
| 12 |
+
- conversational
|
| 13 |
+
- personal-assistant
|
| 14 |
+
- llm
|
| 15 |
+
- phi-3
|
| 16 |
+
- jarvis
|
| 17 |
+
- voice
|
| 18 |
+
- memory
|
| 19 |
+
- agentic
|
| 20 |
+
hardware: cpu-upgrade
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
# J.A.R.V.I.S — Just A Rather Very Intelligent System
|
| 24 |
+
|
| 25 |
+
> *"Sometimes you gotta run before you can walk."*
|
| 26 |
+
|
| 27 |
+
Your personal AI assistant — conversational, voice-enabled, and memory-powered. Runs locally on a HuggingFace Space with **16 GB RAM** and no GPU required (thanks to 4-bit quantisation).
|
| 28 |
+
|
| 29 |
+
---
|
| 30 |
+
|
| 31 |
+
## ✨ Features
|
| 32 |
+
|
| 33 |
+
| Feature | Detail |
|
| 34 |
+
|---|---|
|
| 35 |
+
| 🧠 **Local LLM** | Microsoft Phi-3-mini-4k-instruct (3.8B params, 4-bit ≈ 2-4 GB RAM) |
|
| 36 |
+
| 💬 **Chat interface** | Clean Gradio UI — type or use voice |
|
| 37 |
+
| 🎙️ **Voice replies** | Edge-TTS (Microsoft GuyNeural, no GPU, minimal cloud) |
|
| 38 |
+
| 🗂️ **Persistent memory** | SQLite facts + conversation history |
|
| 39 |
+
| 💡 **Thinking engine** | Background anticipation — JARVIS predicts your needs |
|
| 40 |
+
| 🔧 **Commands** | `status` · `remember X is Y` · `anticipate` |
|
| 41 |
+
|
| 42 |
+
---
|
| 43 |
+
|
| 44 |
+
## 🚀 Quick Start (local)
|
| 45 |
+
|
| 46 |
+
```bash
|
| 47 |
+
git clone https://huggingface.co/spaces/<your-username>/jarvis
|
| 48 |
+
cd jarvis
|
| 49 |
+
pip install -r requirements.txt
|
| 50 |
+
python app.py
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
Open `http://localhost:7860` in your browser.
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
|
| 57 |
+
## ⚙️ Environment Variables
|
| 58 |
+
|
| 59 |
+
| Variable | Default | Description |
|
| 60 |
+
|---|---|---|
|
| 61 |
+
| `JARVIS_MODEL` | `microsoft/Phi-3-mini-4k-instruct` | HuggingFace model ID |
|
| 62 |
+
| `JARVIS_QUANT` | `4bit` | Quantisation: `4bit` / `8bit` / `none` |
|
| 63 |
+
| `JARVIS_MEMORY_DIR` | `./data/memory` | Where SQLite DB is stored |
|
| 64 |
+
| `JARVIS_TTS_VOICE` | `en-US-GuyNeural` | Edge-TTS voice name |
|
| 65 |
+
| `JARVIS_THINKING` | `true` | Enable background thinking engine |
|
| 66 |
+
|
| 67 |
+
Set in HuggingFace Space **Settings → Variables & Secrets** or in a `.env` file locally.
|
| 68 |
+
|
| 69 |
+
---
|
| 70 |
+
|
| 71 |
+
## 💾 RAM Requirements
|
| 72 |
+
|
| 73 |
+
| Model | Quant | RAM |
|
| 74 |
+
|---|---|---|
|
| 75 |
+
| Phi-3-mini-4k-instruct (3.8B) | 4-bit | ~3–4 GB |
|
| 76 |
+
| Phi-3-mini-4k-instruct (3.8B) | fp16 | ~7.6 GB |
|
| 77 |
+
| Mistral-7B-Instruct-v0.2 | 4-bit | ~4–5 GB |
|
| 78 |
+
| Mistral-7B-Instruct-v0.2 | fp16 | ~14 GB |
|
| 79 |
+
|
| 80 |
+
**Recommended for 16 GB RAM:** Phi-3-mini 4-bit (fast) or Mistral-7B 4-bit (deeper reasoning).
|
| 81 |
+
|
| 82 |
+
---
|
| 83 |
+
|
| 84 |
+
## 🗂️ Project Structure
|
| 85 |
+
|
| 86 |
+
```
|
| 87 |
+
app.py — Gradio UI + entry point
|
| 88 |
+
brain.py — HuggingFace model inference (lazy-loaded)
|
| 89 |
+
memory.py — SQLite facts + conversation history
|
| 90 |
+
thinking.py — Background anticipation engine
|
| 91 |
+
voice_tts.py — Edge-TTS text-to-speech
|
| 92 |
+
config.py — All settings via env vars
|
| 93 |
+
requirements.txt
|
| 94 |
+
README.md — This file (HuggingFace model card)
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
---
|
| 98 |
+
|
| 99 |
+
## 🤝 Credits
|
| 100 |
+
|
| 101 |
+
Built on top of:
|
| 102 |
+
- [Microsoft Phi-3](https://huggingface.co/microsoft/Phi-3-mini-4k-instruct)
|
| 103 |
+
- [HuggingFace Transformers](https://github.com/huggingface/transformers)
|
| 104 |
+
- [Gradio](https://gradio.app)
|
| 105 |
+
- [Edge-TTS](https://github.com/rany2/edge-tts)
|
| 106 |
+
- Original JARVIS project — OpenJarvis architecture inspiration
|
UPLOAD_GUIDE.txt
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
══════════════════════════════════════════════════════════════
|
| 2 |
+
JARVIS → HUGGINGFACE UPLOAD GUIDE
|
| 3 |
+
16 GB RAM · Local inference · No GPU required
|
| 4 |
+
══════════════════════════════════════════════════════════════
|
| 5 |
+
|
| 6 |
+
WHAT'S IN THIS FOLDER (hf_jarvis/)
|
| 7 |
+
────────────────────────────────────────────────────────────
|
| 8 |
+
app.py → Gradio UI (entry point for HF Spaces)
|
| 9 |
+
brain.py → HuggingFace Transformers inference engine
|
| 10 |
+
memory.py → SQLite memory (facts + conversation history)
|
| 11 |
+
thinking.py → Background anticipation engine
|
| 12 |
+
voice_tts.py → Edge-TTS voice output
|
| 13 |
+
config.py → All settings via environment variables
|
| 14 |
+
requirements.txt→ All pip packages needed
|
| 15 |
+
README.md → HuggingFace model card (auto-displayed)
|
| 16 |
+
.env.example → Local dev environment template
|
| 17 |
+
.gitignore → Keeps your DB and secrets out of git
|
| 18 |
+
|
| 19 |
+
════════════════════════════════════════════════════════════
|
| 20 |
+
STEP 1: CREATE A HUGGINGFACE ACCOUNT / LOG IN
|
| 21 |
+
════════════════════════════════════════════════════════════
|
| 22 |
+
Go to: https://huggingface.co
|
| 23 |
+
Sign up (free) or log in.
|
| 24 |
+
|
| 25 |
+
════════════════════════════════════════════════════════════
|
| 26 |
+
STEP 2: CREATE A NEW SPACE
|
| 27 |
+
════════════════════════════════════════════════════════════
|
| 28 |
+
1. Click your profile icon → New Space
|
| 29 |
+
2. Space name: jarvis (or any name you like)
|
| 30 |
+
3. License: MIT
|
| 31 |
+
4. SDK: Gradio ← IMPORTANT: select Gradio
|
| 32 |
+
5. Hardware: CPU (upgraded) ← free tier is fine for 3.8B 4-bit
|
| 33 |
+
For Mistral-7B choose "CPU upgrade" or "A10G small" if available
|
| 34 |
+
6. Visibility: Private (or Public — your choice)
|
| 35 |
+
7. Click: Create Space
|
| 36 |
+
|
| 37 |
+
════════════════════════════════════════════════════════════
|
| 38 |
+
STEP 3: UPLOAD FILES
|
| 39 |
+
════════════════════════════════════════════════════════════
|
| 40 |
+
METHOD A — Drag-and-drop in browser (easiest)
|
| 41 |
+
──────────────────────────────────────────────
|
| 42 |
+
1. Open your Space page → click "Files" tab
|
| 43 |
+
2. Click "Add file" → "Upload files"
|
| 44 |
+
3. Select ALL files from this folder:
|
| 45 |
+
app.py
|
| 46 |
+
brain.py
|
| 47 |
+
memory.py
|
| 48 |
+
thinking.py
|
| 49 |
+
voice_tts.py
|
| 50 |
+
config.py
|
| 51 |
+
requirements.txt
|
| 52 |
+
README.md
|
| 53 |
+
.env.example
|
| 54 |
+
.gitignore
|
| 55 |
+
4. Click "Commit changes to main"
|
| 56 |
+
5. HuggingFace will install requirements.txt and launch app.py
|
| 57 |
+
automatically. Wait ~2-5 minutes for the build to finish.
|
| 58 |
+
|
| 59 |
+
METHOD B — Git push (recommended for updates)
|
| 60 |
+
───────────────────────────────────────────────
|
| 61 |
+
Run in PowerShell from the hf_jarvis\ folder:
|
| 62 |
+
|
| 63 |
+
git init
|
| 64 |
+
git remote add origin https://huggingface.co/spaces/<YOUR_USERNAME>/jarvis
|
| 65 |
+
git add .
|
| 66 |
+
git commit -m "Initial JARVIS upload"
|
| 67 |
+
git push origin main
|
| 68 |
+
|
| 69 |
+
(You may need: git lfs install if you ever add large model files)
|
| 70 |
+
|
| 71 |
+
════════════════════════════════════════════════════════════
|
| 72 |
+
STEP 4: SET ENVIRONMENT VARIABLES (optional but recommended)
|
| 73 |
+
════════════════════════════════════════════════════════════
|
| 74 |
+
In your Space page → Settings tab → Variables & Secrets
|
| 75 |
+
|
| 76 |
+
Add these if you want to customize:
|
| 77 |
+
JARVIS_MODEL = microsoft/Phi-3-mini-4k-instruct (default)
|
| 78 |
+
JARVIS_QUANT = 4bit (default)
|
| 79 |
+
JARVIS_THINKING = true
|
| 80 |
+
|
| 81 |
+
Leave blank to use the safe defaults already in config.py.
|
| 82 |
+
|
| 83 |
+
════════════════════════════════════════════════════════════
|
| 84 |
+
STEP 5: WAIT FOR BUILD & OPEN JARVIS
|
| 85 |
+
════════════════════════════════════════════════════════════
|
| 86 |
+
HuggingFace will show build logs. When it says "Running",
|
| 87 |
+
your JARVIS is live at:
|
| 88 |
+
|
| 89 |
+
https://huggingface.co/spaces/<YOUR_USERNAME>/jarvis
|
| 90 |
+
|
| 91 |
+
First response takes ~30-90 seconds (model loading).
|
| 92 |
+
Every subsequent message is fast.
|
| 93 |
+
|
| 94 |
+
════════════════════════════════════════════════════════════
|
| 95 |
+
WHAT JARVIS CAN DO ON HUGGINGFACE
|
| 96 |
+
════════════════════════════════════════════════════════════
|
| 97 |
+
✅ Chat with 3.8B / 7B local model — no external API costs
|
| 98 |
+
✅ Voice replies (Edge-TTS, Microsoft GuyNeural voice)
|
| 99 |
+
✅ Remember facts: type "remember name is Tony"
|
| 100 |
+
✅ Check status: type "status"
|
| 101 |
+
✅ See predictions: type "anticipate"
|
| 102 |
+
✅ Persistent memory across sessions (SQLite DB, local to the Space)
|
| 103 |
+
✅ Runs on 16 GB RAM — Phi-3-mini 4-bit uses only ≈3-4 GB
|
| 104 |
+
|
| 105 |
+
════════════════════════════════════════════════════════════
|
| 106 |
+
RAM & MODEL OPTIONS
|
| 107 |
+
════════════════════════════════════════════════════════════
|
| 108 |
+
Your 16 GB gives you a LOT of headroom:
|
| 109 |
+
|
| 110 |
+
Model │ Quant │ RAM used
|
| 111 |
+
───────────────────────────────────┼───────┼──────────
|
| 112 |
+
Phi-3-mini-4k-instruct (3.8B) │ 4-bit │ ~3-4 GB ← default (fast)
|
| 113 |
+
Phi-3-mini-4k-instruct (3.8B) │ fp16 │ ~7.6 GB
|
| 114 |
+
Mistral-7B-Instruct-v0.2 (7B) │ 4-bit │ ~4-5 GB ← deep reasoning
|
| 115 |
+
Mistral-7B-Instruct-v0.2 (7B) │ fp16 │ ~14 GB ← max quality
|
| 116 |
+
|
| 117 |
+
To switch: set JARVIS_MODEL in Space Settings.
|
| 118 |
+
|
| 119 |
+
════════════════════════════════════════════════════════════
|
| 120 |
+
TROUBLESHOOTING
|
| 121 |
+
════════════════════════════════════════════════════════════
|
| 122 |
+
"Model loading..." forever
|
| 123 |
+
→ First load downloads from HF Hub. Wait 3-5 minutes.
|
| 124 |
+
The model (~2-7 GB) downloads once and is cached.
|
| 125 |
+
|
| 126 |
+
"bitsandbytes not available"
|
| 127 |
+
→ 4-bit quant needs a recent version. Set JARVIS_QUANT=none
|
| 128 |
+
if bitsandbytes fails (fp32, uses more RAM but works).
|
| 129 |
+
|
| 130 |
+
Out of memory
|
| 131 |
+
→ Set JARVIS_QUANT=4bit (default) to reduce RAM to ~3-4 GB.
|
| 132 |
+
|
| 133 |
+
Voice not working
|
| 134 |
+
→ edge-tts needs internet. On fully offline HF Spaces,
|
| 135 |
+
TTS falls back silently to text-only — chat still works.
|
| 136 |
+
|
| 137 |
+
══════════════════════════════════════════════════════════════
|
| 138 |
+
SIR — YOUR JARVIS IS READY FOR THE WORLD
|
| 139 |
+
══════════════════════════════════════════════════════════════
|
app.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
JARVIS — HuggingFace Space App
|
| 4 |
+
Gradio chat interface for JARVIS personal AI assistant.
|
| 5 |
+
Runs entirely locally on HuggingFace Spaces (CPU or GPU) with 16 GB RAM.
|
| 6 |
+
|
| 7 |
+
Launch: python app.py
|
| 8 |
+
Or via HuggingFace Spaces (auto-detected from README.md sdk: gradio).
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import os
|
| 12 |
+
import io
|
| 13 |
+
import json
|
| 14 |
+
import threading
|
| 15 |
+
import gradio as gr
|
| 16 |
+
|
| 17 |
+
# ── Init subsystems ──────────────────────────────────────────────────────────
|
| 18 |
+
import memory
|
| 19 |
+
import thinking
|
| 20 |
+
import brain
|
| 21 |
+
|
| 22 |
+
memory.init_db()
|
| 23 |
+
thinking.start_thinking_engine()
|
| 24 |
+
|
| 25 |
+
# Warm-up model in background so UI loads instantly
|
| 26 |
+
_warmup_thread = brain.warmup()
|
| 27 |
+
|
| 28 |
+
# ── TTS helper ────────────────────────────────────────────────────────────────
|
| 29 |
+
def _speak(text: str):
|
| 30 |
+
"""Return audio bytes for Gradio Audio component, or None."""
|
| 31 |
+
try:
|
| 32 |
+
from voice_tts import tts_bytes
|
| 33 |
+
return tts_bytes(text)
|
| 34 |
+
except Exception:
|
| 35 |
+
return None
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
# ── Core chat logic ────────────────────────────────────────────────────────────
|
| 39 |
+
def chat(user_message: str, history: list[list[str]], audio_reply: bool):
|
| 40 |
+
"""
|
| 41 |
+
Main chat handler — called by Gradio.
|
| 42 |
+
history: [[user_msg, bot_msg], ...]
|
| 43 |
+
Returns: (updated_history, status_text, audio_or_None)
|
| 44 |
+
"""
|
| 45 |
+
if not user_message.strip():
|
| 46 |
+
return history, "Say something, sir.", None
|
| 47 |
+
|
| 48 |
+
lower = user_message.lower().strip()
|
| 49 |
+
|
| 50 |
+
# ── Built-in commands ──────────────────────────────────────────────────
|
| 51 |
+
if lower in ("status", "jarvis status"):
|
| 52 |
+
facts = memory.get_all_facts()
|
| 53 |
+
reply = (
|
| 54 |
+
f"Status: Online. "
|
| 55 |
+
f"Memory: {len(facts)} facts. "
|
| 56 |
+
f"Model: {brain.HF_MODEL}. "
|
| 57 |
+
f"TTS: {'ON' if audio_reply else 'OFF'}."
|
| 58 |
+
)
|
| 59 |
+
history.append([user_message, reply])
|
| 60 |
+
audio = _speak(reply) if audio_reply else None
|
| 61 |
+
return history, "Ready.", audio
|
| 62 |
+
|
| 63 |
+
if lower in ("anticipate", "what are you thinking"):
|
| 64 |
+
ants = thinking.get_anticipations()
|
| 65 |
+
reply = "Thinking ahead: " + (", ".join(ants[:3]) if ants else "No predictions yet.")
|
| 66 |
+
history.append([user_message, reply])
|
| 67 |
+
audio = _speak(reply) if audio_reply else None
|
| 68 |
+
return history, "Ready.", audio
|
| 69 |
+
|
| 70 |
+
if lower.startswith("remember ") and " is " in lower:
|
| 71 |
+
try:
|
| 72 |
+
part = user_message[9:] # strip "remember "
|
| 73 |
+
key, val = part.split(" is ", 1)
|
| 74 |
+
memory.save_fact(key.strip().lower().replace(" ", "_"), val.strip())
|
| 75 |
+
reply = f"Remembered: {key.strip()} is {val.strip()}."
|
| 76 |
+
except Exception as e:
|
| 77 |
+
reply = f"Couldn't remember that: {e}"
|
| 78 |
+
history.append([user_message, reply])
|
| 79 |
+
audio = _speak(reply) if audio_reply else None
|
| 80 |
+
return history, "Memory updated.", audio
|
| 81 |
+
|
| 82 |
+
# ── Normal brain query ─────────────────────────────────────────────────
|
| 83 |
+
hist_flat = [
|
| 84 |
+
{"role": "user", "message": pair[0]}
|
| 85 |
+
for pair in history
|
| 86 |
+
if pair[0]
|
| 87 |
+
] + [
|
| 88 |
+
{"role": "jarvis", "message": pair[1]}
|
| 89 |
+
for pair in history
|
| 90 |
+
if len(pair) > 1 and pair[1]
|
| 91 |
+
]
|
| 92 |
+
# interleave properly
|
| 93 |
+
hist_pairs = []
|
| 94 |
+
for pair in history:
|
| 95 |
+
if pair[0]:
|
| 96 |
+
hist_pairs.append({"role": "user", "message": pair[0]})
|
| 97 |
+
if len(pair) > 1 and pair[1]:
|
| 98 |
+
hist_pairs.append({"role": "jarvis", "message": pair[1]})
|
| 99 |
+
|
| 100 |
+
reply = brain.ask(user_message, hist_pairs)
|
| 101 |
+
history.append([user_message, reply])
|
| 102 |
+
audio = _speak(reply) if audio_reply else None
|
| 103 |
+
return history, "Ready.", audio
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def remember_fact(key: str, value: str):
|
| 107 |
+
"""Directly save a fact via the sidebar form."""
|
| 108 |
+
if not key.strip() or not value.strip():
|
| 109 |
+
return "Please fill in both fields."
|
| 110 |
+
memory.save_fact(key.strip().lower().replace(" ", "_"), value.strip())
|
| 111 |
+
return f"Saved: {key.strip()} = {value.strip()}"
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def load_facts():
|
| 115 |
+
facts = memory.get_all_facts()
|
| 116 |
+
if not facts:
|
| 117 |
+
return "No facts stored yet."
|
| 118 |
+
return "\n".join(f"**{k}**: {v}" for k, v in facts.items())
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def load_anticipations():
|
| 122 |
+
ants = thinking.get_anticipations()
|
| 123 |
+
if not ants:
|
| 124 |
+
return "No predictions yet. JARVIS thinks every hour."
|
| 125 |
+
return "\n".join(f"• {a}" for a in ants[:5])
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
# ── Build Gradio UI ──────────────────────────────────────────────────────���─────
|
| 129 |
+
def build_ui() -> gr.Blocks:
|
| 130 |
+
css = """
|
| 131 |
+
#jarvis-header { text-align: center; margin-bottom: 8px; }
|
| 132 |
+
#jarvis-header h1 { font-size: 2.4em; font-weight: 800; letter-spacing: 2px; }
|
| 133 |
+
#jarvis-header p { color: #888; margin-top: -8px; }
|
| 134 |
+
.gradio-container { max-width: 900px !important; margin: auto; }
|
| 135 |
+
"""
|
| 136 |
+
|
| 137 |
+
with gr.Blocks(title="JARVIS – Personal AI", css=css, theme=gr.themes.Soft()) as demo:
|
| 138 |
+
|
| 139 |
+
# ── Header ──────────────────────────────────────────────────────────
|
| 140 |
+
with gr.Row(elem_id="jarvis-header"):
|
| 141 |
+
gr.HTML(
|
| 142 |
+
"<h1>🤖 J.A.R.V.I.S</h1>"
|
| 143 |
+
"<p>Just A Rather Very Intelligent System — Your Personal AI Agent</p>"
|
| 144 |
+
"<p style='font-size:0.8em;color:#555'>"
|
| 145 |
+
f"Powered by: <code>{brain.HF_MODEL}</code> · 16 GB RAM optimised · Local inference"
|
| 146 |
+
"</p>"
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
with gr.Row():
|
| 150 |
+
# ── Main chat column ─────────────────────────────────────────────
|
| 151 |
+
with gr.Column(scale=3):
|
| 152 |
+
chatbot = gr.Chatbot(
|
| 153 |
+
label="JARVIS",
|
| 154 |
+
height=480,
|
| 155 |
+
avatar_images=(
|
| 156 |
+
None, # user avatar
|
| 157 |
+
"https://upload.wikimedia.org/wikipedia/en/thumb/6/68/Iron_Man_in_Marvel_Cinematic_Universe.jpg/250px-Iron_Man_in_Marvel_Cinematic_Universe.jpg",
|
| 158 |
+
),
|
| 159 |
+
bubble_full_width=False,
|
| 160 |
+
)
|
| 161 |
+
with gr.Row():
|
| 162 |
+
msg_box = gr.Textbox(
|
| 163 |
+
placeholder="Talk to JARVIS... (try: 'status', 'remember name is Tony', or any question)",
|
| 164 |
+
show_label=False,
|
| 165 |
+
scale=5,
|
| 166 |
+
container=False,
|
| 167 |
+
)
|
| 168 |
+
send_btn = gr.Button("Send", variant="primary", scale=1)
|
| 169 |
+
|
| 170 |
+
audio_out = gr.Audio(label="JARVIS Voice", autoplay=True, visible=True)
|
| 171 |
+
audio_on = gr.Checkbox(label="Voice replies (TTS)", value=True)
|
| 172 |
+
status_bar = gr.Textbox(label="Status", value="Ready.", interactive=False)
|
| 173 |
+
|
| 174 |
+
# ── Sidebar ──────────────────────────────────────────────────────
|
| 175 |
+
with gr.Column(scale=1):
|
| 176 |
+
gr.Markdown("### 📂 Memory")
|
| 177 |
+
with gr.Group():
|
| 178 |
+
fact_key = gr.Textbox(placeholder="fact key (e.g. name)", label="Key", lines=1)
|
| 179 |
+
fact_val = gr.Textbox(placeholder="value", label="Value", lines=1)
|
| 180 |
+
save_btn = gr.Button("Save Fact", variant="secondary")
|
| 181 |
+
save_status = gr.Textbox(label="", interactive=False, lines=1)
|
| 182 |
+
|
| 183 |
+
gr.Markdown("### 🧠 Stored Facts")
|
| 184 |
+
facts_display = gr.Markdown(value=load_facts, every=15)
|
| 185 |
+
|
| 186 |
+
gr.Markdown("### 💡 JARVIS Thinks Ahead")
|
| 187 |
+
ants_display = gr.Markdown(value=load_anticipations, every=60)
|
| 188 |
+
|
| 189 |
+
# ── Events ───────────────────────────────────────────────────────────
|
| 190 |
+
def handle_submit(user_msg, history, audio_on_val):
|
| 191 |
+
return chat(user_msg, history, audio_on_val)
|
| 192 |
+
|
| 193 |
+
# Send on Enter or button
|
| 194 |
+
msg_box.submit(
|
| 195 |
+
handle_submit,
|
| 196 |
+
inputs=[msg_box, chatbot, audio_on],
|
| 197 |
+
outputs=[chatbot, status_bar, audio_out],
|
| 198 |
+
).then(lambda: "", outputs=msg_box)
|
| 199 |
+
|
| 200 |
+
send_btn.click(
|
| 201 |
+
handle_submit,
|
| 202 |
+
inputs=[msg_box, chatbot, audio_on],
|
| 203 |
+
outputs=[chatbot, status_bar, audio_out],
|
| 204 |
+
).then(lambda: "", outputs=msg_box)
|
| 205 |
+
|
| 206 |
+
# Save fact
|
| 207 |
+
save_btn.click(
|
| 208 |
+
remember_fact,
|
| 209 |
+
inputs=[fact_key, fact_val],
|
| 210 |
+
outputs=save_status,
|
| 211 |
+
)
|
| 212 |
+
|
| 213 |
+
# Refresh facts panel when a fact is saved
|
| 214 |
+
save_btn.click(load_facts, outputs=facts_display)
|
| 215 |
+
|
| 216 |
+
return demo
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
# ── Entry point ────────────────────────────────────────────────────────────────
|
| 220 |
+
if __name__ == "__main__":
|
| 221 |
+
demo = build_ui()
|
| 222 |
+
demo.launch(
|
| 223 |
+
server_name="0.0.0.0",
|
| 224 |
+
server_port=int(os.environ.get("PORT", 7860)),
|
| 225 |
+
share=False, # set True for a public tunnel link
|
| 226 |
+
show_error=True,
|
| 227 |
+
)
|
brain.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# JARVIS Brain — HuggingFace Edition
|
| 2 |
+
# Uses HuggingFace Transformers pipeline for local inference.
|
| 3 |
+
# Designed for 16 GB RAM:
|
| 4 |
+
# - Phi-3-mini-4k-instruct (3.8B, fp16 ≈ 7.6 GB) — default, fits comfortably
|
| 5 |
+
# - Mistral-7B-Instruct-v0.2 (7B, 4-bit ≈ 4 GB) — use JARVIS_MODEL env var
|
| 6 |
+
#
|
| 7 |
+
# Set JARVIS_MODEL or JARVIS_QUANT in env or .env to choose a different model.
|
| 8 |
+
|
| 9 |
+
import os
|
| 10 |
+
import threading
|
| 11 |
+
import memory
|
| 12 |
+
from config import HF_MODEL, QUANT_MODE
|
| 13 |
+
|
| 14 |
+
_pipe = None
|
| 15 |
+
_lock = threading.Lock()
|
| 16 |
+
_load_error = None
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def _build_system_prompt() -> str:
|
| 20 |
+
facts = memory.get_all_facts()
|
| 21 |
+
facts_str = "\n".join(f"- {k}: {v}" for k, v in facts.items()) or "None yet."
|
| 22 |
+
return (
|
| 23 |
+
"You are JARVIS — Just A Rather Very Intelligent System.\n"
|
| 24 |
+
"You are a loyal, intelligent, and proactive AI assistant.\n"
|
| 25 |
+
"You are direct, confident, and slightly witty.\n"
|
| 26 |
+
"You serve only your user.\n\n"
|
| 27 |
+
f"What you know about your user:\n{facts_str}\n\n"
|
| 28 |
+
"Guidelines:\n"
|
| 29 |
+
"- Be concise but helpful\n"
|
| 30 |
+
"- Answer clearly and confidently\n"
|
| 31 |
+
"- Remember context across the conversation"
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def _load_pipeline():
|
| 36 |
+
"""Load HuggingFace model. Called once at first use (lazy load)."""
|
| 37 |
+
global _pipe, _load_error
|
| 38 |
+
if _pipe is not None:
|
| 39 |
+
return _pipe, None
|
| 40 |
+
if _load_error is not None:
|
| 41 |
+
return None, _load_error
|
| 42 |
+
|
| 43 |
+
try:
|
| 44 |
+
from transformers import pipeline, AutoTokenizer, AutoModelForCausalLM
|
| 45 |
+
import torch
|
| 46 |
+
|
| 47 |
+
print(f"[Brain] Loading model: {HF_MODEL} (quant={QUANT_MODE}) ...")
|
| 48 |
+
|
| 49 |
+
kwargs: dict = {
|
| 50 |
+
"task": "text-generation",
|
| 51 |
+
"model": HF_MODEL,
|
| 52 |
+
"device_map": "auto",
|
| 53 |
+
"trust_remote_code": True,
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
if QUANT_MODE == "4bit":
|
| 57 |
+
try:
|
| 58 |
+
from transformers import BitsAndBytesConfig
|
| 59 |
+
kwargs["model_kwargs"] = {
|
| 60 |
+
"quantization_config": BitsAndBytesConfig(
|
| 61 |
+
load_in_4bit=True,
|
| 62 |
+
bnb_4bit_compute_dtype=torch.float16,
|
| 63 |
+
bnb_4bit_use_double_quant=True,
|
| 64 |
+
)
|
| 65 |
+
}
|
| 66 |
+
print("[Brain] 4-bit quantisation enabled (approx 2-4 GB RAM)")
|
| 67 |
+
except Exception as e:
|
| 68 |
+
print(f"[Brain] bitsandbytes not available, loading fp16: {e}")
|
| 69 |
+
kwargs["torch_dtype"] = torch.float16
|
| 70 |
+
elif QUANT_MODE == "8bit":
|
| 71 |
+
try:
|
| 72 |
+
from transformers import BitsAndBytesConfig
|
| 73 |
+
kwargs["model_kwargs"] = {
|
| 74 |
+
"quantization_config": BitsAndBytesConfig(load_in_8bit=True)
|
| 75 |
+
}
|
| 76 |
+
except Exception as e:
|
| 77 |
+
kwargs["torch_dtype"] = torch.float16
|
| 78 |
+
else:
|
| 79 |
+
# CPU-friendly fallback: float32
|
| 80 |
+
kwargs["torch_dtype"] = torch.float32
|
| 81 |
+
kwargs.pop("device_map", None) # use CPU
|
| 82 |
+
|
| 83 |
+
_pipe = pipeline(**kwargs)
|
| 84 |
+
print(f"[Brain] Model ready: {HF_MODEL}")
|
| 85 |
+
return _pipe, None
|
| 86 |
+
|
| 87 |
+
except Exception as e:
|
| 88 |
+
_load_error = str(e)
|
| 89 |
+
print(f"[Brain] Failed to load model: {e}")
|
| 90 |
+
return None, _load_error
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def ask(query: str, history: list[dict] | None = None) -> str:
|
| 94 |
+
"""
|
| 95 |
+
Ask JARVIS a question.
|
| 96 |
+
history: list of {"role": "user"|"jarvis", "message": str}
|
| 97 |
+
Returns JARVIS's response as a string.
|
| 98 |
+
"""
|
| 99 |
+
with _lock:
|
| 100 |
+
pipe, err = _load_pipeline()
|
| 101 |
+
|
| 102 |
+
if pipe is None:
|
| 103 |
+
return f"[Brain unavailable: {err}]"
|
| 104 |
+
|
| 105 |
+
# Build messages in chat format
|
| 106 |
+
messages = [{"role": "system", "content": _build_system_prompt()}]
|
| 107 |
+
|
| 108 |
+
if history:
|
| 109 |
+
for turn in history[-6:]: # keep last 3 user+jarvis pairs
|
| 110 |
+
role = "user" if turn.get("role") == "user" else "assistant"
|
| 111 |
+
messages.append({"role": role, "content": turn.get("message", "")})
|
| 112 |
+
|
| 113 |
+
messages.append({"role": "user", "content": query})
|
| 114 |
+
|
| 115 |
+
try:
|
| 116 |
+
# Run generation
|
| 117 |
+
out = pipe(
|
| 118 |
+
messages,
|
| 119 |
+
max_new_tokens=350,
|
| 120 |
+
temperature=0.7,
|
| 121 |
+
do_sample=True,
|
| 122 |
+
pad_token_id=pipe.tokenizer.eos_token_id,
|
| 123 |
+
return_full_text=False,
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
# Extract generated text
|
| 127 |
+
if isinstance(out, list) and out:
|
| 128 |
+
first = out[0]
|
| 129 |
+
if isinstance(first, dict):
|
| 130 |
+
# most pipelines return {"generated_text": [{...messages...}]}
|
| 131 |
+
gen = first.get("generated_text", "")
|
| 132 |
+
if isinstance(gen, list):
|
| 133 |
+
# find the last assistant turn
|
| 134 |
+
for turn in reversed(gen):
|
| 135 |
+
if isinstance(turn, dict) and turn.get("role") == "assistant":
|
| 136 |
+
result = turn.get("content", "").strip()
|
| 137 |
+
break
|
| 138 |
+
else:
|
| 139 |
+
result = str(gen).strip()
|
| 140 |
+
else:
|
| 141 |
+
result = str(gen).strip()
|
| 142 |
+
else:
|
| 143 |
+
result = str(first).strip()
|
| 144 |
+
else:
|
| 145 |
+
result = str(out).strip()
|
| 146 |
+
|
| 147 |
+
# Persist conversation
|
| 148 |
+
memory.save_message("user", query)
|
| 149 |
+
memory.save_message("jarvis", result)
|
| 150 |
+
|
| 151 |
+
return result if result else "[No response generated]"
|
| 152 |
+
|
| 153 |
+
except Exception as e:
|
| 154 |
+
return f"[Generation error: {e}]"
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def warmup():
|
| 158 |
+
"""Pre-load model so first user query doesn't feel slow."""
|
| 159 |
+
thread = threading.Thread(target=_load_pipeline, daemon=True)
|
| 160 |
+
thread.start()
|
| 161 |
+
return thread
|
config.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# JARVIS HuggingFace Configuration
|
| 2 |
+
# All paths driven by environment variables — no hardcoded Windows paths
|
| 3 |
+
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
# ── Paths (env-var driven, HF-friendly) ────────────────────────────────────
|
| 7 |
+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 8 |
+
MEMORY_DIR = os.environ.get("JARVIS_MEMORY_DIR", os.path.join(BASE_DIR, "data", "memory"))
|
| 9 |
+
DB_PATH = os.environ.get("JARVIS_DB_PATH", os.path.join(MEMORY_DIR, "jarvis.db"))
|
| 10 |
+
|
| 11 |
+
# ── Model (HuggingFace) ────────────────────────────────────────────────────
|
| 12 |
+
# For 16 GB RAM: Phi-3 Mini (3.8B, 4-bit) or Mistral-7B (4-bit)
|
| 13 |
+
# phi3:mini fits easily; Mistral-7B-Instruct needs bitsandbytes 4-bit quant
|
| 14 |
+
HF_MODEL = os.environ.get("JARVIS_MODEL", "microsoft/Phi-3-mini-4k-instruct")
|
| 15 |
+
|
| 16 |
+
# Quantisation: set "4bit" to load in 4-bit (saves VRAM/RAM; needs bitsandbytes)
|
| 17 |
+
# set "8bit" for 8-bit, leave blank for full-precision (fp16/fp32).
|
| 18 |
+
QUANT_MODE = os.environ.get("JARVIS_QUANT", "4bit") # 4bit / 8bit / none
|
| 19 |
+
|
| 20 |
+
# ── Voice ──────────────────────────────────────────────────────────────────
|
| 21 |
+
TTS_VOICE = os.environ.get("JARVIS_TTS_VOICE", "en-US-GuyNeural")
|
| 22 |
+
WHISPER_MODEL = os.environ.get("JARVIS_WHISPER_MODEL", "base")
|
| 23 |
+
|
| 24 |
+
# ── Agent identity ──────────────────────────────────────────────────────────
|
| 25 |
+
AGENT_NAME = "JARVIS"
|
| 26 |
+
AGENT_TAGLINE = "Just A Rather Very Intelligent System"
|
| 27 |
+
|
| 28 |
+
# ── Thinking engine ─────────────────────────────────────────────────────────
|
| 29 |
+
THINKING_ENABLED = os.environ.get("JARVIS_THINKING", "true").lower() == "true"
|
| 30 |
+
THINKING_INTERVAL = int(os.environ.get("JARVIS_THINK_INTERVAL", "3600"))
|
| 31 |
+
|
| 32 |
+
# ── Memory ──────────────────────────────────────────────────────────────────
|
| 33 |
+
MAX_HISTORY = int(os.environ.get("JARVIS_MAX_HISTORY", "20"))
|
memory.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# JARVIS Memory System — HuggingFace Edition
|
| 2 |
+
# SQLite3 persistent memory: facts, conversation history, preferences
|
| 3 |
+
# All paths relative / env-var driven — no hardcoded Windows paths
|
| 4 |
+
|
| 5 |
+
import sqlite3
|
| 6 |
+
import json
|
| 7 |
+
import datetime
|
| 8 |
+
import os
|
| 9 |
+
from config import DB_PATH, MEMORY_DIR
|
| 10 |
+
|
| 11 |
+
os.makedirs(MEMORY_DIR, exist_ok=True)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _connect():
|
| 15 |
+
return sqlite3.connect(DB_PATH)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def init_db():
|
| 19 |
+
"""Create tables if they don't exist."""
|
| 20 |
+
with _connect() as conn:
|
| 21 |
+
conn.executescript("""
|
| 22 |
+
CREATE TABLE IF NOT EXISTS conversations (
|
| 23 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 24 |
+
role TEXT NOT NULL, -- 'user' or 'jarvis'
|
| 25 |
+
message TEXT NOT NULL,
|
| 26 |
+
timestamp TEXT NOT NULL
|
| 27 |
+
);
|
| 28 |
+
|
| 29 |
+
CREATE TABLE IF NOT EXISTS facts (
|
| 30 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 31 |
+
key TEXT NOT NULL UNIQUE, -- e.g. 'user_name'
|
| 32 |
+
value TEXT NOT NULL,
|
| 33 |
+
updated TEXT NOT NULL
|
| 34 |
+
);
|
| 35 |
+
|
| 36 |
+
CREATE TABLE IF NOT EXISTS preferences (
|
| 37 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 38 |
+
category TEXT NOT NULL,
|
| 39 |
+
setting TEXT NOT NULL,
|
| 40 |
+
value TEXT NOT NULL
|
| 41 |
+
);
|
| 42 |
+
""")
|
| 43 |
+
print("[Memory] Database ready.")
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def save_message(role: str, message: str):
|
| 47 |
+
"""Save a conversation turn."""
|
| 48 |
+
ts = datetime.datetime.now().isoformat()
|
| 49 |
+
with _connect() as conn:
|
| 50 |
+
conn.execute(
|
| 51 |
+
"INSERT INTO conversations (role, message, timestamp) VALUES (?,?,?)",
|
| 52 |
+
(role, message, ts)
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def get_recent_history(n: int = 10) -> list[dict]:
|
| 57 |
+
"""Return the last n conversation turns."""
|
| 58 |
+
with _connect() as conn:
|
| 59 |
+
rows = conn.execute(
|
| 60 |
+
"SELECT role, message, timestamp FROM conversations ORDER BY id DESC LIMIT ?",
|
| 61 |
+
(n,)
|
| 62 |
+
).fetchall()
|
| 63 |
+
return [{"role": r[0], "message": r[1], "timestamp": r[2]} for r in reversed(rows)]
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def save_fact(key: str, value: str):
|
| 67 |
+
"""Save or update a fact about the user."""
|
| 68 |
+
ts = datetime.datetime.now().isoformat()
|
| 69 |
+
with _connect() as conn:
|
| 70 |
+
conn.execute(
|
| 71 |
+
"INSERT INTO facts (key, value, updated) VALUES (?,?,?) "
|
| 72 |
+
"ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated=excluded.updated",
|
| 73 |
+
(key, value, ts)
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def get_fact(key: str) -> str | None:
|
| 78 |
+
"""Retrieve a stored fact."""
|
| 79 |
+
with _connect() as conn:
|
| 80 |
+
row = conn.execute(
|
| 81 |
+
"SELECT value FROM facts WHERE key=?", (key,)
|
| 82 |
+
).fetchone()
|
| 83 |
+
return row[0] if row else None
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def get_all_facts() -> dict:
|
| 87 |
+
"""Return all stored facts as a dict."""
|
| 88 |
+
with _connect() as conn:
|
| 89 |
+
rows = conn.execute("SELECT key, value FROM facts").fetchall()
|
| 90 |
+
return {r[0]: r[1] for r in rows}
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def search_history(query: str, limit: int = 5) -> list[dict]:
|
| 94 |
+
"""Simple keyword search through conversation history."""
|
| 95 |
+
with _connect() as conn:
|
| 96 |
+
rows = conn.execute(
|
| 97 |
+
"SELECT role, message, timestamp FROM conversations "
|
| 98 |
+
"WHERE message LIKE ? ORDER BY id DESC LIMIT ?",
|
| 99 |
+
(f"%{query}%", limit)
|
| 100 |
+
).fetchall()
|
| 101 |
+
return [{"role": r[0], "message": r[1], "timestamp": r[2]} for r in rows]
|
requirements.txt
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Core packages
|
| 2 |
+
gradio>=4.26.0
|
| 3 |
+
transformers>=4.41.0
|
| 4 |
+
accelerate>=0.30.0
|
| 5 |
+
torch>=2.1.0
|
| 6 |
+
|
| 7 |
+
# 4-bit / 8-bit quantisation (hugely reduces RAM usage on 16 GB)
|
| 8 |
+
bitsandbytes>=0.43.0
|
| 9 |
+
|
| 10 |
+
# TTS — Microsoft neural voices, works without a GPU
|
| 11 |
+
edge-tts>=6.1.9
|
| 12 |
+
|
| 13 |
+
# Memory
|
| 14 |
+
# (sqlite3 is built-in — no extra package needed)
|
| 15 |
+
|
| 16 |
+
# Sentence tokeniser for Phi-3 / Mistral chat templates
|
| 17 |
+
sentencepiece>=0.1.99
|
| 18 |
+
|
| 19 |
+
# Optional safety filters (HuggingFace Hub recommends these)
|
| 20 |
+
safetensors>=0.4.3
|
| 21 |
+
|
| 22 |
+
# Protobuf (needed by some tokenisers)
|
| 23 |
+
protobuf>=3.20.0
|
thinking.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# JARVIS Thinking Engine — HuggingFace Edition
|
| 2 |
+
# Runs periodically in the background to generate anticipations.
|
| 3 |
+
# Paths and intervals are env-var driven.
|
| 4 |
+
|
| 5 |
+
import threading
|
| 6 |
+
import time
|
| 7 |
+
import json
|
| 8 |
+
import os
|
| 9 |
+
from datetime import datetime
|
| 10 |
+
from config import THINKING_ENABLED, THINKING_INTERVAL, MEMORY_DIR
|
| 11 |
+
import memory
|
| 12 |
+
|
| 13 |
+
_thinking_thread = None
|
| 14 |
+
_thinking_active = False
|
| 15 |
+
_anticipations: list[str] = []
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def _get_user_patterns() -> dict:
|
| 19 |
+
"""Summarise what the user talks about most."""
|
| 20 |
+
history = memory.get_recent_history(100)
|
| 21 |
+
keywords: dict[str, int] = {}
|
| 22 |
+
for turn in history:
|
| 23 |
+
for word in turn["message"].lower().split():
|
| 24 |
+
if len(word) > 4:
|
| 25 |
+
keywords[word] = keywords.get(word, 0) + 1
|
| 26 |
+
top = sorted(keywords.items(), key=lambda x: x[1], reverse=True)[:10]
|
| 27 |
+
return {
|
| 28 |
+
"top_keywords": top,
|
| 29 |
+
"total_interactions": len(history),
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _generate_anticipations() -> list[str]:
|
| 34 |
+
"""Ask the brain what the user might need next."""
|
| 35 |
+
try:
|
| 36 |
+
import brain as b
|
| 37 |
+
patterns = _get_user_patterns()
|
| 38 |
+
facts = memory.get_all_facts()
|
| 39 |
+
prompt = (
|
| 40 |
+
f"Given user interests: {', '.join(k for k, _ in patterns['top_keywords'])} "
|
| 41 |
+
f"and facts: {json.dumps(facts)}, "
|
| 42 |
+
"predict 3 brief, helpful things JARVIS should proactively offer. "
|
| 43 |
+
"Reply with one bullet per line."
|
| 44 |
+
)
|
| 45 |
+
response = b.ask(prompt)
|
| 46 |
+
lines = [l.strip("•- ").strip() for l in response.splitlines() if l.strip()]
|
| 47 |
+
return lines[:3]
|
| 48 |
+
except Exception as e:
|
| 49 |
+
return [f"[Thinking error: {e}]"]
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _save_thinking_state(anticipations: list[str]):
|
| 53 |
+
"""Persist thinking results to disk."""
|
| 54 |
+
os.makedirs(MEMORY_DIR, exist_ok=True)
|
| 55 |
+
path = os.path.join(MEMORY_DIR, "thinking.json")
|
| 56 |
+
with open(path, "w", encoding="utf-8") as f:
|
| 57 |
+
json.dump({
|
| 58 |
+
"timestamp": datetime.now().isoformat(),
|
| 59 |
+
"anticipations": anticipations,
|
| 60 |
+
"patterns": _get_user_patterns(),
|
| 61 |
+
}, f, indent=2)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def _thinking_loop():
|
| 65 |
+
global _thinking_active, _anticipations
|
| 66 |
+
_thinking_active = True
|
| 67 |
+
print(f"[Thinking] Started. Cycle: {THINKING_INTERVAL}s")
|
| 68 |
+
while _thinking_active:
|
| 69 |
+
try:
|
| 70 |
+
time.sleep(THINKING_INTERVAL)
|
| 71 |
+
if not _thinking_active:
|
| 72 |
+
break
|
| 73 |
+
print("[Thinking] Generating anticipations...")
|
| 74 |
+
_anticipations = _generate_anticipations()
|
| 75 |
+
_save_thinking_state(_anticipations)
|
| 76 |
+
print(f"[Thinking] {len(_anticipations)} anticipation(s) ready.")
|
| 77 |
+
except Exception as e:
|
| 78 |
+
print(f"[Thinking] Error: {e}")
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def start_thinking_engine():
|
| 82 |
+
global _thinking_thread, _thinking_active
|
| 83 |
+
if not THINKING_ENABLED:
|
| 84 |
+
return
|
| 85 |
+
if _thinking_thread and _thinking_thread.is_alive():
|
| 86 |
+
return
|
| 87 |
+
_thinking_thread = threading.Thread(target=_thinking_loop, daemon=True)
|
| 88 |
+
_thinking_thread.start()
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def stop_thinking_engine():
|
| 92 |
+
global _thinking_active
|
| 93 |
+
_thinking_active = False
|
| 94 |
+
if _thinking_thread:
|
| 95 |
+
_thinking_thread.join(timeout=5)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def get_anticipations() -> list[str]:
|
| 99 |
+
"""Return most recent anticipations (from memory on disk or in-process)."""
|
| 100 |
+
if _anticipations:
|
| 101 |
+
return _anticipations
|
| 102 |
+
path = os.path.join(MEMORY_DIR, "thinking.json")
|
| 103 |
+
if os.path.exists(path):
|
| 104 |
+
try:
|
| 105 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 106 |
+
data = json.load(f)
|
| 107 |
+
return data.get("anticipations", [])
|
| 108 |
+
except Exception:
|
| 109 |
+
pass
|
| 110 |
+
return []
|
voice_tts.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# JARVIS Voice TTS — HuggingFace Edition
|
| 2 |
+
# Uses edge-tts (Microsoft neural voices, works without a local GPU)
|
| 3 |
+
# Falls back to plain text if edge-tts is unavailable.
|
| 4 |
+
|
| 5 |
+
import asyncio
|
| 6 |
+
import io
|
| 7 |
+
import os
|
| 8 |
+
from config import TTS_VOICE
|
| 9 |
+
|
| 10 |
+
try:
|
| 11 |
+
import edge_tts
|
| 12 |
+
EDGE_TTS_AVAILABLE = True
|
| 13 |
+
except ImportError:
|
| 14 |
+
EDGE_TTS_AVAILABLE = False
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
async def _generate_audio_bytes(text: str) -> bytes | None:
|
| 18 |
+
"""Generate TTS audio bytes asynchronously using edge-tts."""
|
| 19 |
+
if not EDGE_TTS_AVAILABLE:
|
| 20 |
+
return None
|
| 21 |
+
try:
|
| 22 |
+
communicate = edge_tts.Communicate(text=text, voice=TTS_VOICE, rate="+0%")
|
| 23 |
+
audio = b""
|
| 24 |
+
async for chunk in communicate.stream():
|
| 25 |
+
if chunk["type"] == "audio":
|
| 26 |
+
audio += chunk["data"]
|
| 27 |
+
return audio if audio else None
|
| 28 |
+
except Exception as e:
|
| 29 |
+
print(f"[TTS] Edge-TTS error: {e}")
|
| 30 |
+
return None
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def tts_bytes(text: str) -> bytes | None:
|
| 34 |
+
"""Synchronous wrapper — returns MP3 bytes or None."""
|
| 35 |
+
try:
|
| 36 |
+
loop = asyncio.new_event_loop()
|
| 37 |
+
asyncio.set_event_loop(loop)
|
| 38 |
+
result = loop.run_until_complete(_generate_audio_bytes(text))
|
| 39 |
+
loop.close()
|
| 40 |
+
return result
|
| 41 |
+
except Exception as e:
|
| 42 |
+
print(f"[TTS] Error: {e}")
|
| 43 |
+
return None
|