GitHub Actions
Deploy screen-companion from 02f91bc
8c6ce69
Raw
History Blame Contribute Delete
3.34 kB
"""Screen Companion — Gradio Server frontend.
Serves the custom HTML landing page and proxies /api/analyze requests to
the Modal backend. Run with SCREEN_COMPANION_MOCK=1 for local testing.
"""
import os
from pathlib import Path
import httpx
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from gradio import Server
from pydantic import BaseModel
app = Server()
BACKEND_URL = os.environ.get(
"SCREEN_COMPANION_API_URL",
"https://rafalbogusdxc--screen-companion-api.modal.run",
).rstrip("/")
MOCK = os.environ.get("SCREEN_COMPANION_MOCK", "").strip().lower() in ("1", "true", "yes")
STATIC_DIR = Path(__file__).parent / "static"
http = httpx.AsyncClient(base_url=BACKEND_URL, timeout=120, follow_redirects=True)
class AnalyzeRequest(BaseModel):
image_b64: str
memories: str = ""
@app.get("/", response_class=HTMLResponse)
async def homepage():
return (Path(__file__).parent / "index.html").read_text()
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
@app.post("/api/analyze")
async def proxy_analyze(req: AnalyzeRequest):
if MOCK:
import random
observations = [
"user editing code in IDE",
"browser with documentation open",
"terminal with test output",
"same as before",
"user reading article",
]
msgs = [
("Nice progress! That code looks clean.", "happy"),
("Hmm, looks like you're deep in thought...", "neutral"),
("You've been at this a while - remember to stretch!", "happy"),
("Ooh, interesting problem you're working on!", "excited"),
("Errors happen to everyone. You'll figure it out!", "sad"),
("That's a great approach, keep going!", "excited"),
("I see you're reading docs - smart move!", "happy"),
("Take a deep breath, you've got this!", "neutral"),
(None, "neutral"),
(None, "happy"),
(None, "neutral"),
]
msg, emotion = random.choice(msgs)
obs = random.choice(observations)
return {"message": msg, "emotion": emotion, "observation": obs}
try:
r = await http.post("/analyze-screen", json={
"image_b64": req.image_b64,
"memories": req.memories,
})
r.raise_for_status()
return r.json()
except httpx.ConnectError:
return {"message": "Waking up... one moment.", "emotion": "neutral", "observation": "backend cold starting"}
except httpx.ReadTimeout:
return {"message": "Hmm, lost my train of thought.", "emotion": "neutral", "observation": "response timed out"}
except Exception as e:
print(f"[screen-companion] proxy error: {type(e).__name__}: {e}")
return {"message": None, "emotion": "neutral", "observation": f"error: {type(e).__name__}"}
@app.get("/api/health")
async def proxy_health():
if MOCK:
return {"status": "mock", "model": "SmolVLM-2B (mock mode)"}
try:
r = await http.get("/health", timeout=10)
r.raise_for_status()
return r.json()
except Exception:
return {"status": "unreachable", "url": BACKEND_URL}
if __name__ == "__main__":
app.launch(server_name="0.0.0.0", server_port=7860)