import gradio as gr
import threading, time, json
from datetime import datetime, UTC
from collections import deque
from pathlib import Path
from neural_network import LivingNetwork, CATEGORIES as TEXT_CATS, KNOWLEDGE_FILE
from data_fetcher import DataFetcher
from image_fetcher import ImageFetcher, IMAGE_DIR, CATEGORIES as IMG_CATS
from image_model import LivingImageNetwork
from chatbot import RAGChatbot
# ── globals ──────────────────────────────────────────────────────────────────
network = LivingNetwork()
fetcher = DataFetcher()
img_fetcher = ImageFetcher()
img_network = LivingImageNetwork()
chatbot = RAGChatbot()
app_log = deque(maxlen=300)
is_alive = False
img_alive = False
def log(msg):
app_log.appendleft(f"[{datetime.now(UTC).strftime('%H:%M:%S')}] {msg}")
log("🧠 Text network ready")
log("👁️ Image CNN ready")
kf = network.get_knowledge_file_stats()
if kf['exists']: log(f"📚 Restored {kf['lines']} articles from knowledge.jsonl")
if network.epoch: log(f"✅ Text model: epoch {network.epoch:,}")
if img_network.epoch: log(f"✅ Image CNN: epoch {img_network.epoch:,}")
# ── background loops ─────────────────────────────────────────────────────────
def text_loop():
global is_alive
lf = ls = 0
log("🚀 Text network LIVE")
while is_alive:
now = time.time()
if now - lf >= 40:
try:
items = fetcher.fetch_round()
for it in items:
network.ingest(it['text'], it['category'], source=it.get('source','web'))
kf2 = network.get_knowledge_file_stats()
log(f"📡 +{len(items)} articles | knowledge: {kf2['lines']}")
lf = now
except Exception as e:
log(f"⚠ fetch: {str(e)[:50]}")
if network.stats['buffer_size'] >= 32 and network.vocab.is_built:
r = network.train_n_steps(80)
if r['steps']:
s = network.stats
log(f"📝 Epoch {s['epoch']:,} loss {s['loss']} acc {s['accuracy']}%")
else:
log(f"⏳ need {max(0,32-network.stats['buffer_size'])} more items")
if now - ls >= 180:
network.save_checkpoint(); log("💾 saved"); ls = now
time.sleep(2)
log("⏹ text stopped")
def image_loop():
global img_alive
lf = ls = 0
log("🚀 Image CNN LIVE")
while img_alive:
now = time.time()
if now - lf >= 60:
try:
r = img_fetcher.fetch_round()
log(f"🖼️ +{r['downloaded']} images | total {r['total']}")
lf = now
except Exception as e:
log(f"⚠ img fetch: {str(e)[:50]}")
if img_fetcher.get_stats()['total'] >= 16:
r = img_network.train_n_steps(IMAGE_DIR, 20)
if r['steps']:
s = img_network.stats
log(f"👁️ CNN epoch {s['epoch']:,} loss {s['loss']} acc {s['accuracy']}%")
else:
log(f"⏳ img CNN needs {max(0,16-img_fetcher.get_stats()['total'])} more images")
if now - ls >= 180:
img_network._save_checkpoint(); log("💾 img saved"); ls = now
time.sleep(3)
log("⏹ image stopped")
# ── controls ──────────────────────────────────────────────────────────────────
def start_text():
global is_alive
if is_alive: return "⚠ already running"
is_alive = True
threading.Thread(target=text_loop, daemon=True).start()
return "🟢 text network LIVE"
def stop_text():
global is_alive; is_alive = False
network.save_checkpoint(); return "🔴 stopped + saved"
def start_img():
global img_alive
if img_alive: return "⚠ already running"
img_alive = True
threading.Thread(target=image_loop, daemon=True).start()
return "🟢 image CNN LIVE"
def stop_img():
global img_alive; img_alive = False
img_network._save_checkpoint(); return "🔴 stopped + saved"
def do_fetch_text():
items = fetcher.fetch_round()
for it in items: network.ingest(it['text'], it['category'], source=it.get('source','web'))
return f"✅ +{len(items)} articles"
def do_fetch_img():
r = img_fetcher.fetch_round()
return f"✅ +{r['downloaded']} images"
def do_train_text(n):
r = network.train_n_steps(int(n))
return f"✅ {r['steps']} steps · loss {r['avg_loss']}" if r['steps'] else "⚠ need more data"
def do_train_img(n):
r = img_network.train_n_steps(IMAGE_DIR, int(n))
return f"✅ {r['steps']} steps · loss {r['avg_loss']}" if r['steps'] else "⚠ need more images"
# ── stats fns ─────────────────────────────────────────────────────────────────
def status_bar():
s = network.stats; si = img_network.stats
kf2 = network.get_knowledge_file_stats(); ig = img_fetcher.get_stats()
return (f"{'🟢' if is_alive else '🔴'} TEXT ep{s['epoch']:,} loss{s['loss']} acc{s['accuracy']}% | "
f"{'🟢' if img_alive else '🔴'} CNN ep{si['epoch']:,} loss{si['loss']} acc{si['accuracy']}% | "
f"📚{kf2['lines']} articles 🖼️{ig['total']} images")
def text_stats():
s=network.stats; fs=fetcher.get_stats(); cc=network.category_counts
hist=network.loss_history[-30:]
spark=''.join(' ▁▂▃▄▅▆▇█'[min(8,int(((v-min(hist))/(max(hist)-min(hist)+1e-9))*8))] for v in hist) if len(hist)>=2 else '…'
rows='\n'.join(f"|{c}|{cc.get(c,0)}|" for c in TEXT_CATS)
return f"""### 📝 Text Network
|Metric|Value|
|---|---|
|Epoch|{s['epoch']:,}|Loss|{s['loss']}|
|Accuracy|{s['accuracy']}%|Samples|{s['total_samples']:,}|
|Knowledge|{s['knowledge_count']} articles|Vocab|{s['vocab_size']:,}|
|LR|{s['lr']}|Buffer|{s['buffer_size']}|
Loss `{spark}`
### Sources — {fs['total_fetched']} total
RSS·{fs['sources']['rss']} Reddit·{fs['sources']['reddit']} Wiki·{fs['sources']['wikipedia']} HN·{fs['sources']['hackernews']}
### Categories
|Cat|Count|
|---|---|
{rows}
> _{s['last_text']}_"""
def img_stats():
s=img_network.stats; ifs=img_fetcher.get_stats()
hist=img_network.loss_history[-30:]
spark=''.join(' ▁▂▃▄▅▆▇█'[min(8,int(((v-min(hist))/(max(hist)-min(hist)+1e-9))*8))] for v in hist) if len(hist)>=2 else '…'
rows='\n'.join(f"|{c}|{ifs['by_category'].get(c,0)}|" for c in IMG_CATS)
return f"""### 👁️ Image CNN
|Metric|Value|
|---|---|
|Epoch|{s['epoch']:,}|Loss|{s['loss']}|
|Accuracy|{s['accuracy']}%|Images|{s['total_images']:,}|
|On Disk|{ifs['total']}|Disk|{ifs['disk_mb']} MB|
Loss `{spark}`
### By Category
|Cat|Count|
|---|---|
{rows}
**What each block learns:**
Block 1 → edges & colours · Block 2 → shapes & textures · Block 3 → objects & parts"""
def get_log():
lines = list(app_log)[:80]
def color(line):
if '✅' in line or '🟢' in line: c = '#4ade80'
elif '⚠' in line or '🔴' in line or 'error' in line.lower(): c = '#f87171'
elif '🚀' in line or '🤖' in line: c = '#818cf8'
elif '📰' in line or '🦆' in line or '🟠' in line: c = '#38bdf8'
elif '💻' in line or '📖' in line: c = '#fb923c'
elif '🔥' in line or 'loss' in line.lower(): c = '#facc15'
else: c = '#94a3b8'
return f'
{line}
'
rows = ''.join(color(l) for l in lines) or 'No logs yet...
'
return f'{rows}
'
def get_feed():
items=fetcher.get_recent_items(10)
if not items: return "_No data yet_"
return '\n\n---\n\n'.join(f"**[{i['source'].upper()}]** `{i['category'].upper()}`\n{i['text'][:130]}…" for i in items)
def get_knowledge():
kf2=network.get_knowledge_file_stats(); items=network.get_recent_knowledge(20)
if not kf2['exists'] or not items: return "### 📭 Empty — start text network"
rows=[]
for it in items:
rows.append(f"**`{it.get('category','?').upper()}`** `{it.get('source','?')}` `{it.get('timestamp','')[:10]}`\n> {it.get('text','')[:130]}…")
return f"### 📚 {kf2['lines']} articles · {kf2['size_kb']} KB\n\n---\n\n"+'\n\n---\n\n'.join(rows)
def predict_text(txt):
if not txt.strip(): return "Enter text."
r=network.predict(txt)
if 'error' in r: return f"⚠ {r['error']}"
bars=''.join(f"`{c:<15}` {'█'*int(p/5)}{'░'*(20-int(p/5))} **{p:.1f}%**\n\n" for c,p in sorted(r['all_probs'].items(),key=lambda x:-x[1]))
return f"## → {r['prediction'].upper()}\n**{r['confidence']}% confidence**\n\n{bars}"
def predict_img(path):
if not path: return "Upload image."
r=img_network.predict_image(path)
if 'error' in r: return f"⚠ {r['error']}"
bars=''.join(f"`{c:<15}` {'█'*int(p/5)}{'░'*(20-int(p/5))} **{p:.1f}%**\n\n" for c,p in sorted(r['all_probs'].items(),key=lambda x:-x[1]))
return f"## → {r['prediction'].upper()}\n**{r['confidence']}% confidence**\n\n{bars}"
def chat_fn(msg, history):
new_history = chatbot.chat(msg, history or [])
return new_history, "" # also clear the input box
# ── 3D VIZ — fully animated with signal pulses ────────────────────────────────
def build_viz(state_json: str) -> str:
return """
drag to rotate · scroll to zoom · signals travel left→right every 800ms
"""
def get_text_viz():
s = network.get_viz_state(); s['type']='text'
h = build_viz(json.dumps(s))
e = h.replace('"','"').replace('\n','
')
return f''
def get_img_viz():
s = img_network.get_viz_state()
h = build_viz(json.dumps(s))
e = h.replace('"','"').replace('\n','
')
return f''
# ── UI ────────────────────────────────────────────────────────────────────────
with gr.Blocks(title="Living Neural Network") as demo:
gr.Markdown("# 🧠 Living Neural Network\n*Two AIs training on live internet data — one reads, one looks*")
sbar = gr.Textbox(label="", value=status_bar(), interactive=False, lines=2)
with gr.Tabs():
# CONTROL
with gr.Tab("🎛️ Control"):
gr.Markdown("### 📝 Text Network")
with gr.Row():
gr.Button("▶ Start Text", variant="primary").click(start_text, outputs=gr.Textbox(label="",lines=1,interactive=False))
gr.Button("⏹ Stop Text", variant="stop" ).click(stop_text, outputs=gr.Textbox(label="",lines=1,interactive=False))
gr.Button("📡 Fetch Text" ).click(do_fetch_text, outputs=gr.Textbox(label="",lines=1,interactive=False))
with gr.Row():
ts=gr.Slider(10,500,100,step=10,label="Steps")
gr.Button("🔥 Train Text").click(do_train_text,inputs=ts,outputs=gr.Textbox(label="",lines=1,interactive=False))
gr.Markdown("---\n### 👁️ Image CNN")
with gr.Row():
gr.Button("▶ Start Images",variant="primary").click(start_img, outputs=gr.Textbox(label="",lines=1,interactive=False))
gr.Button("⏹ Stop Images",variant="stop" ).click(stop_img, outputs=gr.Textbox(label="",lines=1,interactive=False))
gr.Button("🖼️ Fetch Images" ).click(do_fetch_img, outputs=gr.Textbox(label="",lines=1,interactive=False))
with gr.Row():
is_=gr.Slider(5,100,20,step=5,label="Steps")
gr.Button("🔥 Train CNN").click(do_train_img,inputs=is_,outputs=gr.Textbox(label="",lines=1,interactive=False))
# STATS
with gr.Tab("📊 Stats"):
with gr.Row():
tmd=gr.Markdown(value=text_stats())
imd=gr.Markdown(value=img_stats())
lbox=gr.HTML(value=get_log(),label="Live Logs")
# 3D TEXT VIZ
with gr.Tab("🔮 Text Network 3D"):
gr.Markdown("**Neurons fire & signals travel left→right in real time.** Drag=rotate Scroll=zoom")
tvb=gr.Button("🔄 Refresh",variant="primary")
tvo=gr.HTML('Click 🔄 Refresh to load the 3D network
')
tvb.click(get_text_viz,outputs=tvo)
# 3D IMAGE VIZ
with gr.Tab("👁️ Image CNN 3D"):
gr.Markdown("**Conv layers visualised in 3D.** Each block learns progressively deeper features.")
ivb=gr.Button("🔄 Refresh",variant="primary")
ivo=gr.HTML('Click 🔄 Refresh to load CNN visualization
')
ivb.click(get_img_viz,outputs=ivo)
# CHAT
with gr.Tab("💬 Chat with my AI"):
gr.Markdown(
"### Talk to your AI — it answers from what it has actually learned\n"
"The more articles it collects, the smarter the answers.\n"
"Type `stats` to see what it knows. Type `help` for tips."
)
chatbox = gr.Chatbot(label="", height=480)
with gr.Row():
msg_in = gr.Textbox(label="", placeholder="Ask anything — e.g. 'What's happening in AI?'", scale=5)
send_b = gr.Button("Send", variant="primary", scale=1)
send_b.click(chat_fn, inputs=[msg_in, chatbox], outputs=[chatbox, msg_in])
msg_in.submit(chat_fn, inputs=[msg_in, chatbox], outputs=[chatbox, msg_in])
gr.Examples(
[["What's happening in AI and technology?"],
["Tell me about recent science discoveries"],
["What's in the news about sports?"],
["stats"],["help"]],
inputs=msg_in
)
# KNOWLEDGE
with gr.Tab("📚 Knowledge Base"):
gr.Markdown("Everything the AI has read — saved to `knowledge.jsonl` instantly")
kmd=gr.Markdown(value=get_knowledge())
# DATA FEED
with gr.Tab("📰 Data Feed"):
fmd=gr.Markdown(value=get_feed())
# PREDICT TEXT
with gr.Tab("🔍 Classify Text"):
gr.Markdown("Test your trained text model")
pt=gr.Textbox(label="Text",lines=3,placeholder="Scientists discover...")
pb=gr.Button("Classify",variant="primary")
po=gr.Markdown()
pb.click(predict_text,inputs=pt,outputs=po)
# PREDICT IMAGE
with gr.Tab("🖼️ Classify Image"):
gr.Markdown("Test your trained image CNN")
pi=gr.Image(label="Upload image",type="filepath")
pib=gr.Button("Classify",variant="primary")
pio=gr.Markdown()
pib.click(predict_img,inputs=pi,outputs=pio)
# timers
t3=gr.Timer(3); t5=gr.Timer(5); t8=gr.Timer(8); t12=gr.Timer(12)
t3.tick(status_bar,outputs=sbar)
t3.tick(get_log,outputs=lbox)
t5.tick(text_stats,outputs=tmd)
t5.tick(img_stats,outputs=imd)
t8.tick(get_knowledge,outputs=kmd)
t8.tick(get_feed,outputs=fmd)
t12.tick(get_text_viz,outputs=tvo)
t12.tick(get_img_viz,outputs=ivo)
# ── AUTO-START: both networks begin training immediately on startup ──────────
def _auto_start():
import time as _t
_t.sleep(3) # give Gradio time to finish starting
try:
start_text()
log("🤖 Auto-started TEXT network")
except Exception as e:
log(f"⚠ Auto-start text error: {e}")
try:
start_img()
log("🤖 Auto-started IMAGE network")
except Exception as e:
log(f"⚠ Auto-start image error: {e}")
threading.Thread(target=_auto_start, daemon=True).start()
if __name__=="__main__":
demo.launch(server_name="0.0.0.0", server_port=7860, share=True, ssr_mode=False)