Spaces:
Running
Running
deploy(S4): Blender headless pipeline + WebAR client + backend fixes
Browse files- backend/main.py +73 -9
- backend/supervisor.py +23 -2
backend/main.py
CHANGED
|
@@ -88,7 +88,7 @@ from backend.services.pc_mic_service import start_pc_mic_loop
|
|
| 88 |
from backend.voice.audio_ws import run_in_thread as start_audio_ws
|
| 89 |
APP_VERSION = "1.0.0"
|
| 90 |
|
| 91 |
-
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends
|
| 92 |
from backend.security.auth import verify_token
|
| 93 |
try:
|
| 94 |
from slowapi import Limiter, _rate_limit_exceeded_handler
|
|
@@ -174,26 +174,90 @@ for _webar_candidate in (
|
|
| 174 |
logging.info(f"[S4] WebAR client mounted at /webar from {_webar_candidate}")
|
| 175 |
break
|
| 176 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
@app.get("/")
|
| 178 |
-
async def root_health_check():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
return {"status": "JARVIS / FRIDAY Cloud OS Online", "message": "All systems nominal."}
|
| 180 |
|
| 181 |
@app.get("/wake")
|
| 182 |
@app.post("/wake")
|
| 183 |
async def wake_server():
|
| 184 |
"""Public endpoint — anyone can hit this from anywhere in the world to wake the cloud server.
|
| 185 |
-
HF Spaces sleep after inactivity; this wakes them up instantly.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
import time
|
| 187 |
return {
|
| 188 |
"status": "online",
|
| 189 |
"message": "JARVIS / FRIDAY Cloud Brain is awake and operational.",
|
| 190 |
"timestamp": time.time(),
|
| 191 |
-
"servers": {
|
| 192 |
-
"hf_space": "online",
|
| 193 |
-
"sqlite": "online",
|
| 194 |
-
"chroma": "online",
|
| 195 |
-
"mongodb_atlas": "online"
|
| 196 |
-
}
|
| 197 |
}
|
| 198 |
|
| 199 |
@app.get("/ping")
|
|
|
|
| 88 |
from backend.voice.audio_ws import run_in_thread as start_audio_ws
|
| 89 |
APP_VERSION = "1.0.0"
|
| 90 |
|
| 91 |
+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, Request
|
| 92 |
from backend.security.auth import verify_token
|
| 93 |
try:
|
| 94 |
from slowapi import Limiter, _rate_limit_exceeded_handler
|
|
|
|
| 174 |
logging.info(f"[S4] WebAR client mounted at /webar from {_webar_candidate}")
|
| 175 |
break
|
| 176 |
|
| 177 |
+
# Public front door of the Space. Everything on it is either static or rendered
|
| 178 |
+
# server-side from real state (uptime) — nothing fabricated, nothing token-gated
|
| 179 |
+
# leaks here.
|
| 180 |
+
_ROOT_STATUS_PAGE = """<!doctype html>
|
| 181 |
+
<html lang="en"><head><meta charset="utf-8">
|
| 182 |
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 183 |
+
<title>JARVIS Cloud OS</title>
|
| 184 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 185 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 186 |
+
<link href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@400;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
| 187 |
+
<style>
|
| 188 |
+
:root { --accent:#35e6ff; --amber:#ffb454; --text:#dff3fb; --dim:rgba(223,243,251,.55); }
|
| 189 |
+
* { box-sizing:border-box; margin:0; }
|
| 190 |
+
body { min-height:100vh; display:flex; align-items:center; justify-content:center;
|
| 191 |
+
background:radial-gradient(circle at 50% 30%, #0a1620 0%, #05090e 65%, #030509 100%);
|
| 192 |
+
color:var(--text); font-family:'Chakra Petch',sans-serif; padding:24px; }
|
| 193 |
+
.card { width:min(560px,94vw); padding:36px 32px; border:1px solid rgba(120,220,255,.22);
|
| 194 |
+
border-radius:20px; background:rgba(10,22,30,.55); backdrop-filter:blur(24px) saturate(1.4);
|
| 195 |
+
box-shadow:0 18px 50px rgba(0,0,0,.55), inset 0 1px 0 rgba(180,240,255,.12); }
|
| 196 |
+
.eyebrow { font:600 11px/1 'Chakra Petch'; letter-spacing:.34em; color:var(--dim);
|
| 197 |
+
text-transform:uppercase; margin-bottom:14px; }
|
| 198 |
+
h1 { font:700 30px/1.2 'Chakra Petch'; letter-spacing:.02em; margin-bottom:6px; }
|
| 199 |
+
h1 em { font-style:normal; color:var(--accent); }
|
| 200 |
+
.sub { color:var(--dim); font-size:14px; margin-bottom:26px; }
|
| 201 |
+
.grid { display:grid; grid-template-columns:1fr 1fr; gap:10px; margin-bottom:26px; }
|
| 202 |
+
.stat { border:1px solid rgba(120,220,255,.14); border-radius:12px; padding:12px 14px; }
|
| 203 |
+
.stat span { display:block; font:400 10px/1 'Chakra Petch'; letter-spacing:.22em;
|
| 204 |
+
color:var(--dim); text-transform:uppercase; margin-bottom:7px; }
|
| 205 |
+
.stat strong { font:500 14px/1.3 'JetBrains Mono',monospace; color:var(--accent);
|
| 206 |
+
text-transform:uppercase; }
|
| 207 |
+
.stat strong.amber { color:var(--amber); }
|
| 208 |
+
.pulse { display:inline-block; width:9px; height:9px; border-radius:50%; background:var(--accent);
|
| 209 |
+
margin-right:8px; box-shadow:0 0 12px var(--accent); animation:pulse 2s ease-in-out infinite; }
|
| 210 |
+
@keyframes pulse { 50% { opacity:.35; box-shadow:0 0 4px var(--accent); } }
|
| 211 |
+
.links { display:flex; gap:10px; flex-wrap:wrap; }
|
| 212 |
+
.links a { flex:1; min-width:140px; text-align:center; padding:13px 18px; border-radius:12px;
|
| 213 |
+
text-decoration:none; font:600 13px/1 'Chakra Petch'; letter-spacing:.08em; text-transform:uppercase;
|
| 214 |
+
color:var(--text); border:1px solid rgba(120,220,255,.3); transition:background .15s, box-shadow .15s; }
|
| 215 |
+
.links a:hover { background:rgba(53,230,255,.12); box-shadow:0 0 22px rgba(53,230,255,.25); }
|
| 216 |
+
.foot { margin-top:24px; color:var(--dim); font-size:11px; letter-spacing:.06em; }
|
| 217 |
+
</style></head><body>
|
| 218 |
+
<main class="card">
|
| 219 |
+
<p class="eyebrow">Stark Industries · Cloud Node</p>
|
| 220 |
+
<h1><span class="pulse"></span>JARVIS <em>/</em> FRIDAY</h1>
|
| 221 |
+
<p class="sub">OMEGA Cloud OS — all API surfaces are token-gated.</p>
|
| 222 |
+
<div class="grid">
|
| 223 |
+
<div class="stat"><span>Status</span><strong>Online</strong></div>
|
| 224 |
+
<div class="stat"><span>Uptime</span><strong>{{UPTIME}}</strong></div>
|
| 225 |
+
<div class="stat"><span>Mode</span><strong class="amber">Cloud</strong></div>
|
| 226 |
+
<div class="stat"><span>Assistant</span><strong>JARVIS</strong></div>
|
| 227 |
+
</div>
|
| 228 |
+
<div class="links">
|
| 229 |
+
<a href="/webar/">Enter WebAR</a>
|
| 230 |
+
<a href="/ping">Ping</a>
|
| 231 |
+
</div>
|
| 232 |
+
<p class="foot">Authorized clients: JARVIS desktop, Guardian mobile, WebAR.</p>
|
| 233 |
+
</main>
|
| 234 |
+
</body></html>"""
|
| 235 |
+
|
| 236 |
+
|
| 237 |
@app.get("/")
|
| 238 |
+
async def root_health_check(request: Request):
|
| 239 |
+
# Browsers (the Space's public front door) get a real status page;
|
| 240 |
+
# programmatic callers keep the JSON contract unchanged.
|
| 241 |
+
if "text/html" in (request.headers.get("accept") or ""):
|
| 242 |
+
from backend.routes.mobile_bridge_routes import _uptime_str
|
| 243 |
+
from fastapi.responses import HTMLResponse
|
| 244 |
+
return HTMLResponse(_ROOT_STATUS_PAGE.replace("{{UPTIME}}", _uptime_str()))
|
| 245 |
return {"status": "JARVIS / FRIDAY Cloud OS Online", "message": "All systems nominal."}
|
| 246 |
|
| 247 |
@app.get("/wake")
|
| 248 |
@app.post("/wake")
|
| 249 |
async def wake_server():
|
| 250 |
"""Public endpoint — anyone can hit this from anywhere in the world to wake the cloud server.
|
| 251 |
+
HF Spaces sleep after inactivity; this wakes them up instantly.
|
| 252 |
+
|
| 253 |
+
Only claims what is actually true: the fact this handler is running means the
|
| 254 |
+
Space process is awake. Per-database claims were previously hardcoded
|
| 255 |
+
"online" without any check — that lie is gone."""
|
| 256 |
import time
|
| 257 |
return {
|
| 258 |
"status": "online",
|
| 259 |
"message": "JARVIS / FRIDAY Cloud Brain is awake and operational.",
|
| 260 |
"timestamp": time.time(),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
}
|
| 262 |
|
| 263 |
@app.get("/ping")
|
backend/supervisor.py
CHANGED
|
@@ -117,8 +117,23 @@ Return ONLY the raw python code. Do not include markdown formatting or backticks
|
|
| 117 |
|
| 118 |
if fixed_code.endswith("```"):
|
| 119 |
fixed_code = fixed_code[:-3]
|
| 120 |
-
|
| 121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
except Exception as e:
|
| 123 |
print(f"[SUPERVISOR] Auto-healing failed: {e}")
|
| 124 |
return None
|
|
@@ -187,6 +202,12 @@ def start_server():
|
|
| 187 |
fixed_code = heal_code(crashed_file, stderr_data)
|
| 188 |
|
| 189 |
if fixed_code:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
with open(crashed_file, "w", encoding="utf-8") as f:
|
| 191 |
f.write(fixed_code)
|
| 192 |
print("[SUPERVISOR] Code healed successfully. Restarting in 3 seconds...")
|
|
|
|
| 117 |
|
| 118 |
if fixed_code.endswith("```"):
|
| 119 |
fixed_code = fixed_code[:-3]
|
| 120 |
+
|
| 121 |
+
fixed_code = fixed_code.strip()
|
| 122 |
+
|
| 123 |
+
# An unvalidated LLM reply must never replace server code: reject
|
| 124 |
+
# anything that isn't syntactically valid Python or that shrank the
|
| 125 |
+
# file so much it's likely a truncated/apology reply.
|
| 126 |
+
import ast as _ast
|
| 127 |
+
try:
|
| 128 |
+
_ast.parse(fixed_code)
|
| 129 |
+
except SyntaxError as syn_err:
|
| 130 |
+
print(f"[SUPERVISOR] Heal rejected — generated code does not parse: {syn_err}")
|
| 131 |
+
return None
|
| 132 |
+
if len(fixed_code) < max(64, len(broken_code) // 3):
|
| 133 |
+
print("[SUPERVISOR] Heal rejected — generated code suspiciously short vs original.")
|
| 134 |
+
return None
|
| 135 |
+
|
| 136 |
+
return fixed_code
|
| 137 |
except Exception as e:
|
| 138 |
print(f"[SUPERVISOR] Auto-healing failed: {e}")
|
| 139 |
return None
|
|
|
|
| 202 |
fixed_code = heal_code(crashed_file, stderr_data)
|
| 203 |
|
| 204 |
if fixed_code:
|
| 205 |
+
# Keep the pre-heal original so a bad patch is always recoverable.
|
| 206 |
+
try:
|
| 207 |
+
import shutil
|
| 208 |
+
shutil.copy2(crashed_file, crashed_file + ".pre_heal.bak")
|
| 209 |
+
except Exception as bak_err:
|
| 210 |
+
print(f"[SUPERVISOR] Warning: could not back up original before heal: {bak_err}")
|
| 211 |
with open(crashed_file, "w", encoding="utf-8") as f:
|
| 212 |
f.write(fixed_code)
|
| 213 |
print("[SUPERVISOR] Code healed successfully. Restarting in 3 seconds...")
|