Spaces:
Paused
Paused
| import qrcode | |
| import io | |
| import base64 | |
| from datetime import datetime | |
| from fastapi import Request | |
| from fastapi.templating import Jinja2Templates | |
| from fastapi.responses import HTMLResponse | |
| from app.config import Settings | |
| from app.session_store import create_session, get_session | |
| from app.security import create_worker_join_token | |
| # Use a simple in-memory template for v1; Jinja2 templates directory if present | |
| templates = Jinja2Templates(directory="app/templates") | |
| def render_home(request: Request) -> HTMLResponse: | |
| return HTMLResponse(content=_home_html(), status_code=200) | |
| def render_worker_page(request: Request, session_id: str, token: str) -> HTMLResponse: | |
| return HTMLResponse(content=_worker_html(session_id, token), status_code=200) | |
| def render_session_dashboard(request: Request, session_id: str) -> HTMLResponse: | |
| session = get_session(session_id) | |
| if not session: | |
| return HTMLResponse(content="<h1>Session not found</h1>", status_code=404) | |
| public_url = Settings.get_public_url() | |
| worker_url = f"{public_url}/worker?session_id={session_id}&token={session.secret}" | |
| qr_b64 = make_worker_qr_code(worker_url) | |
| html = _dashboard_html(session_id, worker_url, qr_b64) | |
| return HTMLResponse(content=html, status_code=200) | |
| def make_worker_qr_code(worker_join_url: str) -> str: | |
| img = qrcode.make(worker_join_url) | |
| buf = io.BytesIO() | |
| img.save(buf, format="PNG") | |
| buf.seek(0) | |
| return base64.b64encode(buf.read()).decode() | |
| def make_api_example(session_id: str) -> dict: | |
| public_url = Settings.get_public_url() | |
| return { | |
| "create_job": f"POST {public_url}/api/session/{session_id}/job", | |
| "get_job": f"GET {public_url}/api/job/{{job_id}}", | |
| "get_receipt": f"GET {public_url}/api/receipt/{{receipt_id}}", | |
| } | |
| def _home_html() -> str: | |
| return """<!DOCTYPE html> | |
| <html> | |
| <head><title>PhoneGPU Space</title> | |
| <style> | |
| body{font-family:sans-serif;max-width:800px;margin:40px auto;padding:0 20px} | |
| button{padding:12px 24px;font-size:16px;cursor:pointer;border-radius:6px;border:none;background:#2563eb;color:#fff} | |
| .code{background:#f3f4f6;padding:12px;border-radius:6px;font-family:monospace;overflow:auto} | |
| </style></head> | |
| <body> | |
| <h1>PhoneGPU Space</h1> | |
| <p>Turn an iPhone into a temporary private ML compute worker.</p> | |
| <button onclick="createSession()">Create Session</button> | |
| <div id="output"></div> | |
| <script> | |
| async function createSession(){ | |
| const r=await fetch('/api/session',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({})}); | |
| const j=await r.json(); | |
| document.getElementById('output').innerHTML='<p>Session: <b>'+j.session_id+'</b></p><p>Scan QR with iPhone:</p><img src="data:image/png;base64,'+j.qr+'"><p><a href="/dashboard/'+j.session_id+'">Open Dashboard</a></p>'; | |
| } | |
| </script> | |
| </body></html>""" | |
| def _worker_html(session_id: str, token: str) -> str: | |
| # This redirects to the static worker.html with query params | |
| return f"""<!DOCTYPE html> | |
| <html><head><meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>PhoneGPU Worker</title></head> | |
| <body> | |
| <script>window.WORKER_SESSION_ID='{session_id}';window.WORKER_TOKEN='{token}';</script> | |
| <script>window.location.href='/static/worker.html?session_id={session_id}&token={token}';</script> | |
| </body></html>""" | |
| def _dashboard_html(session_id: str, worker_url: str, qr_b64: str) -> str: | |
| return f"""<!DOCTYPE html> | |
| <html><head><meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>Session Dashboard</title> | |
| <style> | |
| body{{font-family:sans-serif;max-width:800px;margin:40px auto;padding:0 20px}} | |
| .card{{border:1px solid #e5e7eb;border-radius:8px;padding:16px;margin:12px 0}} | |
| </style></head> | |
| <body> | |
| <h1>Session Dashboard</h1> | |
| <p>Session: <code>{session_id}</code></p> | |
| <div class="card"> | |
| <h3>Connect iPhone</h3> | |
| <p>Scan this QR code with your iPhone camera:</p> | |
| <img src="data:image/png;base64,{qr_b64}" width="200"> | |
| <p>Or open: <a href="{worker_url}" target="_blank">{worker_url}</a></p> | |
| </div> | |
| <div class="card"> | |
| <h3>API</h3> | |
| <p><code>POST /api/session/{session_id}/job</code></p> | |
| <p><code>GET /api/job/{{job_id}}</code></p> | |
| <p><code>GET /api/receipt/{{receipt_id}}</code></p> | |
| </div> | |
| </body></html>""" | |