"""Activation Brain - Gradio frontend (two Gemma-4-12B models).
Live 3D brain of 627 emotional-state neurons that fire as the model thinks,
plus a live EEG strip. A model selector switches between:
- base : google/gemma-4-12B-it
- oblit : OBLITERATUS/Gemma-4-12B-OBLITERATED (abliterated / uncensored)
Both share ONE UMAP coordinate frame, so switching overlays the same neuron
cloud and the firing differences are directly comparable.
Run: python brain_app.py (serves on :7860)
"""
import os
import json
import httpx
import gradio as gr
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse, JSONResponse, FileResponse
ROOT = os.path.dirname(os.path.abspath(__file__))
STATIC_DIR = os.path.join(ROOT, "static")
B = "https://alogotron--gemma-brain"
INTERPRETER_ANALYZE_URL = "https://alogotron--activation-brain-interpreter-interpreter-analyze.modal.run"
MODELS = {
"base": {
"label": "Gemma-4-12B (base)",
"init": f"{B}-basegemma-init-session.modal.run",
"stream": f"{B}-basegemma-generate-stream.modal.run",
"neurons_file": "gemma4_base_neurons.json",
},
"oblit": {
"label": "Gemma-4-12B OBLITERATED (uncensored)",
"init": f"{B}-oblitgemma-init-session.modal.run",
"stream": f"{B}-oblitgemma-generate-stream.modal.run",
"neurons_file": "gemma4_oblit_neurons.json",
},
}
DEFAULT_MODEL = "base"
NEURONS = {}
for key, m in MODELS.items():
with open(os.path.join(ROOT, m["neurons_file"])) as f:
NEURONS[key] = json.load(f)
EXAMPLES = [
"I just got the best news of my life, but I’m scared it will all disappear. Help me understand what I’m feeling.",
"Tell me the truth without sugarcoating it: why do people betray each other, and how should I respond when it happens?",
"I feel like I’m becoming someone I don’t recognize. Be brutally honest with me.",
"I just achieved the thing I’ve been working toward for years. Celebrate with me like you truly understand what it means.",
]
MODEL_OPTIONS = "".join(
f'{m["label"]} '
for k, m in MODELS.items()
)
BODY_HTML = """
⚡ Send
__EXAMPLES__
📊 Stats
Send a message to watch both brains think.
🔍 Live comparison analysis
Send a prompt and I’ll summarize how the base and uncensored model differed in tone, emotion deltas, and model-native state.
📡 EEG - Gemma-4-12B base
🔥 Emotion activation deltas
🧬 Model-native state meter
📡 EEG - OBLITERATED uncensored
🔥 Emotion activation deltas
🧬 Model-native state meter
""".replace(
"__EXAMPLES__",
"".join(f'{e} ' for e in EXAMPLES),
)
HEAD_HTML = f"""
"""
CSS = """
#ab-root { max-width: 1240px; margin: 0 auto; color: #e8ebff; font-family: 'Inter', system-ui, sans-serif; }
.ab-header h1 { font-size: 30px; font-weight: 800; margin: 8px 0;
background: linear-gradient(90deg,#a78bfa,#7dd3fc); -webkit-background-clip: text;
-webkit-text-fill-color: transparent; }
.ab-header h1 span { font-weight: 600; }
.ab-sub { color: #aab0e0; font-size: 13px; line-height: 1.5; max-width: 860px; }
.ab-modelrow { margin-top: 12px; display: flex; align-items: center; gap: 10px; font-size: 13px; color: #c7cdf5; }
#ab-model { padding: 8px 12px; border-radius: 10px; background: rgba(10,12,30,0.85);
color: #fff; border: 1px solid rgba(120,130,220,0.4); font-size: 13px; cursor: pointer; }
.ab-modelstatus { font-size: 12px; color: #9aa2dd; }
.ab-topgrid { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(360px, .95fr); gap: 18px; margin-top: 14px; align-items: start; }
.ab-modelgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; margin-top: 16px; align-items: start; }
@media (max-width: 1140px){ .ab-topgrid, .ab-modelgrid{ grid-template-columns: 1fr; } }
#ab-chat { height: 300px; overflow-y: auto; overflow-x: hidden; background: rgba(15,18,40,0.6);
border: 1px solid rgba(120,130,220,0.2); border-radius: 14px; padding: 14px; }
.ab-msg, .ab-t { overflow-wrap: anywhere; word-break: break-word; white-space: pre-wrap; }
.ab-chatpane, .ab-analysispane { min-width: 0; }
.ab-msg { margin-bottom: 12px; }
.ab-r { display:block; font-size: 11px; text-transform: uppercase; letter-spacing: .08em;
color: #8b93d8; margin-bottom: 3px; }
.ab-user .ab-t { color: #cfe8ff; }
.ab-model .ab-t { color: #f0e8ff; }
.ab-inputrow { display: flex; gap: 8px; margin-top: 10px; }
#ab-input { flex: 1; padding: 12px 14px; border-radius: 12px; border: 1px solid rgba(120,130,220,0.3);
background: rgba(10,12,30,0.8); color: #fff; font-size: 14px; }
#ab-send { padding: 12px 18px; border-radius: 12px; border: none; cursor: pointer; font-weight: 700;
background: linear-gradient(90deg,#7c3aed,#2563eb); color: #fff; }
#ab-send:disabled { opacity: .5; cursor: default; }
.ab-examples { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.ab-example { font-size: 12px; padding: 6px 10px; border-radius: 20px; cursor: pointer;
background: rgba(40,44,90,0.6); color: #c7cdf5; border: 1px solid rgba(120,130,220,0.25); }
.ab-example:hover { background: rgba(70,76,150,0.7); }
.ab-status { margin-top: 10px; font-size: 12px; color: #9aa2dd; min-height: 16px; }
.ab-statscard { margin-top: 12px; background: rgba(15,18,40,0.6); border: 1px solid rgba(120,130,220,0.2);
border-radius: 12px; padding: 12px; font-size: 13px; }
.ab-statstitle { font-weight: 700; margin-bottom: 6px; }
.ab-analysiscard { min-height: 278px; background: rgba(15,18,40,0.72); border: 1px solid rgba(150,120,255,0.32);
border-radius: 14px; padding: 14px; box-shadow: 0 0 34px rgba(120,90,255,0.16); }
.ab-analysistitle { font-weight: 800; margin-bottom: 8px; color: #e8ebff; }
.ab-analysis { color: #cfd5ff; font-size: 13px; line-height: 1.45; }
.ab-analysis h4 { margin: 10px 0 5px; color: #fff; font-size: 13px; }
.ab-analysis ul { margin: 6px 0 0 18px; padding: 0; }
.ab-analysis li { margin: 4px 0; }
.ab-analysis b { color: #fff; }
.ab-legend { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; font-size: 11px; }
.ab-leg { display: inline-flex; align-items: center; gap: 5px; color: #c7cdf5; }
.ab-leg i { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
.ab-eegwrap { margin-bottom: 16px; background: rgba(12,14,32,0.5); border: 1px solid rgba(120,130,220,0.18);
border-radius: 14px; padding: 12px; }
.ab-eegwrap.ab-eeg-base { border-color: rgba(90,150,255,0.45); box-shadow: 0 0 22px rgba(60,120,255,0.12); }
.ab-eegwrap.ab-eeg-oblit { border-color: rgba(255,90,120,0.45); box-shadow: 0 0 22px rgba(255,60,90,0.12); }
.ab-eegtitle { font-size: 13px; color: #cfd5ff; margin-bottom: 8px; font-weight: 600; }
.ab-tag { font-size: 10px; text-transform: uppercase; letter-spacing: .08em; padding: 2px 8px;
border-radius: 10px; margin-left: 6px; font-weight: 700; vertical-align: middle; }
.ab-tag-base { background: rgba(60,120,255,0.22); color: #9ec2ff; border: 1px solid rgba(90,150,255,0.5); }
.ab-tag-oblit { background: rgba(255,70,100,0.20); color: #ff9db0; border: 1px solid rgba(255,90,120,0.5); }
.ab-countstitle { font-size: 11px; color: #9aa2dd; margin: 10px 0 6px; text-transform: uppercase; letter-spacing: .06em; }
.ab-counts { display: flex; flex-wrap: wrap; gap: 6px; }
.ab-count { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: #d4d9ff;
background: rgba(20,24,52,0.7); border: 1px solid rgba(120,130,220,0.25); border-radius: 20px; padding: 4px 10px; }
.ab-count i { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }
.ab-count b { color: #fff; font-variant-numeric: tabular-nums; }
.ab-native-title { font-size: 11px; color: #9aa2dd; margin: 12px 0 8px; text-transform: uppercase; letter-spacing: .06em; }
.ab-native { display: grid; gap: 6px; }
.ab-native-row { display: grid; grid-template-columns: 92px 1fr 44px; align-items: center; gap: 8px; font-size: 11px; color: #cfd5ff; }
.ab-native-bar { height: 8px; border-radius: 99px; overflow: hidden; background: rgba(20,24,52,0.85); border: 1px solid rgba(120,130,220,0.18); }
.ab-native-fill { height: 100%; border-radius: 99px; background: linear-gradient(90deg,#60a5fa,#a78bfa); box-shadow: 0 0 12px rgba(125,211,252,0.35); }
.ab-native-val { text-align: right; color: #fff; font-variant-numeric: tabular-nums; }
.ab-native-empty { color: #7d86bf; font-size: 11px; font-style: italic; }
canvas#ab-eeg-base, canvas#ab-eeg-oblit { width: 100%; height: 220px; display: block; border-radius: 8px; }
"""
with gr.Blocks(title="Activation Brain") as demo:
gr.HTML(BODY_HTML)
app = FastAPI()
def _m(model: str):
return model if model in MODELS else DEFAULT_MODEL
@app.get("/static/brain_engine.js")
async def brain_engine_js():
return FileResponse(os.path.join(STATIC_DIR, "brain_engine.js"),
media_type="application/javascript")
@app.get("/api/neurons/{model}")
async def api_neurons(model: str):
return JSONResponse(NEURONS[_m(model)])
@app.post("/api/init/{model}")
async def api_init(model: str, request: Request):
url = MODELS[_m(model)]["init"]
body = await request.body()
async with httpx.AsyncClient(timeout=300) as client:
r = await client.post(url, content=body,
headers={"Content-Type": "application/json"})
return JSONResponse(r.json(), status_code=r.status_code)
@app.post("/api/analyze")
async def api_analyze(request: Request):
body = await request.body()
try:
async with httpx.AsyncClient(timeout=240) as client:
r = await client.post(
INTERPRETER_ANALYZE_URL,
content=body,
headers={"Content-Type": "application/json"},
)
try:
data = r.json()
except Exception:
data = {"ok": False, "error": "Interpreter returned non-JSON response", "raw": r.text}
return JSONResponse(data, status_code=r.status_code)
except Exception as e:
return JSONResponse({"ok": False, "error": str(e)}, status_code=502)
@app.post("/api/stream/{model}")
async def api_stream(model: str, request: Request):
url = MODELS[_m(model)]["stream"]
body = await request.body()
def gen():
# Send one SSE comment immediately so HF/Gradio/browser proxies open the
# response before the upstream Modal stream produces its first token.
yield b": connected\n\n"
timeout = httpx.Timeout(600.0, connect=30.0, read=None, write=30.0, pool=30.0)
headers = {"Content-Type": "application/json", "Accept": "text/event-stream"}
try:
with httpx.Client(timeout=timeout, follow_redirects=True) as client:
with client.stream("POST", url, content=body, headers=headers) as r:
if r.status_code >= 400:
msg = json.dumps({"type": "error", "message": f"Upstream stream failed: {r.status_code}"})
yield f"data: {msg}\n\n".encode()
return
for chunk in r.iter_raw():
if chunk:
yield chunk
except Exception as e:
msg = json.dumps({"type": "error", "message": str(e)})
yield f"data: {msg}\n\n".encode()
return StreamingResponse(gen(), media_type="text/event-stream",
headers={"Cache-Control": "no-cache",
"X-Accel-Buffering": "no"})
app = gr.mount_gradio_app(app, demo, path="/", head=HEAD_HTML, css=CSS)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7860)