NeuMax commited on
Commit
6645e8f
·
verified ·
1 Parent(s): 7b163c4

Upload 11 files

Browse files
Files changed (11) hide show
  1. .env.example +31 -0
  2. .gitignore +10 -0
  3. README.md +106 -0
  4. UPLOAD_GUIDE.txt +139 -0
  5. app.py +227 -0
  6. brain.py +161 -0
  7. config.py +33 -0
  8. memory.py +101 -0
  9. requirements.txt +23 -0
  10. thinking.py +110 -0
  11. 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