choephix commited on
Commit
9f74130
·
1 Parent(s): c62eb3e

Refactor server response handling and add frontend HTML

Browse files

- Removed the inline HTML landing page from `server.py` and replaced it with a static `index.html` file in the `frontend` directory.
- Updated the root endpoint to serve the new HTML file.
- Enhanced the health check response to include the application version.
- Cleaned up unused imports in `server.py`.

Files changed (2) hide show
  1. frontend/index.html +83 -0
  2. server.py +3 -88
frontend/index.html ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>TRELLIS.2 API</title>
7
+ <style>
8
+ *{margin:0;padding:0;box-sizing:border-box}
9
+ :root{--bg:#0e0e10;--card:#18181b;--border:#27272a;--text:#e4e4e7;--dim:#71717a;--accent:#a78bfa;--green:#4ade80;--red:#f87171}
10
+ body{font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:2rem}
11
+ .wrap{max-width:480px;width:100%}
12
+ h1{font-size:1.6rem;font-weight:700;letter-spacing:-.02em;margin-bottom:.25rem}
13
+ .version{color:var(--dim);font-size:.85rem;margin-bottom:1.5rem}
14
+ .grid{display:grid;grid-template-columns:1fr 1fr;gap:.75rem;margin-bottom:1.5rem}
15
+ .stat{background:var(--card);border:1px solid var(--border);border-radius:.75rem;padding:1rem}
16
+ .stat-label{font-size:.7rem;text-transform:uppercase;letter-spacing:.08em;color:var(--dim);margin-bottom:.35rem}
17
+ .stat-value{font-size:1.5rem;font-weight:600;font-variant-numeric:tabular-nums}
18
+ .stat-value.ready{color:var(--green)}
19
+ .stat-value.not-ready{color:var(--red)}
20
+ .wide{grid-column:1/-1}
21
+ .endpoints{background:var(--card);border:1px solid var(--border);border-radius:.75rem;padding:1rem;margin-bottom:1rem}
22
+ .endpoints h2{font-size:.75rem;text-transform:uppercase;letter-spacing:.08em;color:var(--dim);margin-bottom:.6rem}
23
+ .ep{display:flex;justify-content:space-between;align-items:center;padding:.35rem 0;border-bottom:1px solid var(--border);font-size:.82rem}
24
+ .ep:last-child{border-bottom:none}
25
+ .ep-method{font-weight:600;color:var(--accent);min-width:3.2rem}
26
+ .ep-path{color:var(--text);font-family:ui-monospace,monospace;font-size:.78rem}
27
+ .pulse{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:.5rem;vertical-align:middle;animation:blink 2s ease-in-out infinite}
28
+ @keyframes blink{0%,100%{opacity:1}50%{opacity:.3}}
29
+ .footer{text-align:center;color:var(--dim);font-size:.7rem;margin-top:1rem}
30
+ </style>
31
+ </head>
32
+ <body>
33
+ <div class="wrap">
34
+ <h1>TRELLIS.2</h1>
35
+ <div class="version">Image-to-3D API · <span id="version">...</span></div>
36
+ <div class="grid">
37
+ <div class="stat wide">
38
+ <div class="stat-label">Worker Pool</div>
39
+ <div class="stat-value" id="pool"><span class="pulse" id="pulse"></span><span id="pool-text">...</span></div>
40
+ </div>
41
+ <div class="stat">
42
+ <div class="stat-label">Active Jobs</div>
43
+ <div class="stat-value"><span id="active">-</span><span style="color:var(--dim);font-size:.85rem"> / <span id="max">-</span></span></div>
44
+ </div>
45
+ <div class="stat">
46
+ <div class="stat-label">Queued</div>
47
+ <div class="stat-value" id="queued">-</div>
48
+ </div>
49
+ </div>
50
+ <div class="endpoints">
51
+ <h2>Endpoints</h2>
52
+ <div class="ep"><span class="ep-method">POST</span><span class="ep-path">/v1/image-to-glb</span></div>
53
+ <div class="ep"><span class="ep-method">GET</span><span class="ep-path">/v1/jobs/{job_id}</span></div>
54
+ <div class="ep"><span class="ep-method">GET</span><span class="ep-path">/v1/jobs/{job_id}/result</span></div>
55
+ <div class="ep"><span class="ep-method">GET</span><span class="ep-path">/healthz</span></div>
56
+ </div>
57
+ <div class="footer">auto-refreshes every 3 s</div>
58
+ </div>
59
+ <script>
60
+ async function poll() {
61
+ try {
62
+ const response = await fetch("/healthz");
63
+ const data = await response.json();
64
+ const ok = data.worker_pool_ready;
65
+ document.getElementById("version").textContent = `v${data.version ?? "1.2.0"}`;
66
+ document.getElementById("pool-text").textContent = ok ? "Ready" : "Not Ready";
67
+ document.getElementById("pool").className = `stat-value ${ok ? "ready" : "not-ready"}`;
68
+ document.getElementById("pulse").style.background = ok ? "var(--green)" : "var(--red)";
69
+ document.getElementById("active").textContent = data.active_jobs;
70
+ document.getElementById("max").textContent = data.max_concurrent_jobs;
71
+ document.getElementById("queued").textContent = data.queued_jobs;
72
+ } catch {
73
+ document.getElementById("pool-text").textContent = "Unreachable";
74
+ document.getElementById("pool").className = "stat-value not-ready";
75
+ document.getElementById("pulse").style.background = "var(--red)";
76
+ }
77
+ }
78
+
79
+ poll();
80
+ setInterval(poll, 3000);
81
+ </script>
82
+ </body>
83
+ </html>
server.py CHANGED
@@ -15,7 +15,7 @@ from typing import Annotated, Any
15
 
16
  from fastapi import FastAPI, File, Form, HTTPException, UploadFile
17
  from fastapi.middleware.cors import CORSMiddleware
18
- from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
19
  from PIL import Image, UnidentifiedImageError
20
 
21
  import runtime_env # noqa: F401
@@ -477,6 +477,7 @@ async def healthz() -> dict[str, object]:
477
  queue_state = state.queue.snapshot()
478
  return {
479
  "ok": state.executor is not None,
 
480
  "worker_pool_ready": state.executor is not None,
481
  "max_concurrent_jobs": MAX_CONCURRENT_JOBS,
482
  "active_jobs": queue_state["active_jobs"],
@@ -485,95 +486,9 @@ async def healthz() -> dict[str, object]:
485
  }
486
 
487
 
488
- _LANDING_HTML = """\
489
- <!DOCTYPE html>
490
- <html lang="en">
491
- <head>
492
- <meta charset="utf-8">
493
- <meta name="viewport" content="width=device-width,initial-scale=1">
494
- <title>TRELLIS.2 API</title>
495
- <style>
496
- *{margin:0;padding:0;box-sizing:border-box}
497
- :root{--bg:#0e0e10;--card:#18181b;--border:#27272a;--text:#e4e4e7;--dim:#71717a;--accent:#a78bfa;--green:#4ade80;--red:#f87171;--yellow:#facc15}
498
- body{font-family:system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:2rem}
499
- .wrap{max-width:480px;width:100%}
500
- h1{font-size:1.6rem;font-weight:700;letter-spacing:-.02em;margin-bottom:.25rem}
501
- .version{color:var(--dim);font-size:.85rem;margin-bottom:1.5rem}
502
- .grid{display:grid;grid-template-columns:1fr 1fr;gap:.75rem;margin-bottom:1.5rem}
503
- .stat{background:var(--card);border:1px solid var(--border);border-radius:.75rem;padding:1rem}
504
- .stat-label{font-size:.7rem;text-transform:uppercase;letter-spacing:.08em;color:var(--dim);margin-bottom:.35rem}
505
- .stat-value{font-size:1.5rem;font-weight:600;font-variant-numeric:tabular-nums}
506
- .stat-value.ready{color:var(--green)}
507
- .stat-value.not-ready{color:var(--red)}
508
- .wide{grid-column:1/-1}
509
- .endpoints{background:var(--card);border:1px solid var(--border);border-radius:.75rem;padding:1rem;margin-bottom:1rem}
510
- .endpoints h2{font-size:.75rem;text-transform:uppercase;letter-spacing:.08em;color:var(--dim);margin-bottom:.6rem}
511
- .ep{display:flex;justify-content:space-between;align-items:center;padding:.35rem 0;border-bottom:1px solid var(--border);font-size:.82rem}
512
- .ep:last-child{border-bottom:none}
513
- .ep-method{font-weight:600;color:var(--accent);min-width:3.2rem}
514
- .ep-path{color:var(--text);font-family:ui-monospace,monospace;font-size:.78rem}
515
- .pulse{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:.5rem;vertical-align:middle;animation:blink 2s ease-in-out infinite}
516
- @keyframes blink{0%,100%{opacity:1}50%{opacity:.3}}
517
- .footer{text-align:center;color:var(--dim);font-size:.7rem;margin-top:1rem}
518
- </style>
519
- </head>
520
- <body>
521
- <div class="wrap">
522
- <h1>TRELLIS.2</h1>
523
- <div class="version">Image-to-3D API &middot; v""" + app.version + """</div>
524
- <div class="grid">
525
- <div class="stat wide">
526
- <div class="stat-label">Worker Pool</div>
527
- <div class="stat-value" id="pool"><span class="pulse" id="pulse"></span><span id="pool-text">...</span></div>
528
- </div>
529
- <div class="stat">
530
- <div class="stat-label">Active Jobs</div>
531
- <div class="stat-value"><span id="active">-</span><span style="color:var(--dim);font-size:.85rem"> / <span id="max">-</span></span></div>
532
- </div>
533
- <div class="stat">
534
- <div class="stat-label">Queued</div>
535
- <div class="stat-value" id="queued">-</div>
536
- </div>
537
- </div>
538
- <div class="endpoints">
539
- <h2>Endpoints</h2>
540
- <div class="ep"><span class="ep-method">POST</span><span class="ep-path">/v1/image-to-glb</span></div>
541
- <div class="ep"><span class="ep-method">GET</span><span class="ep-path">/v1/jobs/{job_id}</span></div>
542
- <div class="ep"><span class="ep-method">GET</span><span class="ep-path">/v1/jobs/{job_id}/result</span></div>
543
- <div class="ep"><span class="ep-method">GET</span><span class="ep-path">/healthz</span></div>
544
- </div>
545
- <div class="footer">auto-refreshes every 3 s</div>
546
- </div>
547
- <script>
548
- async function poll(){
549
- try{
550
- const r=await fetch("/healthz");
551
- const d=await r.json();
552
- const ok=d.worker_pool_ready;
553
- document.getElementById("pool-text").textContent=ok?"Ready":"Not Ready";
554
- const pv=document.getElementById("pool");
555
- pv.className="stat-value "+(ok?"ready":"not-ready");
556
- document.getElementById("pulse").style.background=ok?"var(--green)":"var(--red)";
557
- document.getElementById("active").textContent=d.active_jobs;
558
- document.getElementById("max").textContent=d.max_concurrent_jobs;
559
- document.getElementById("queued").textContent=d.queued_jobs;
560
- }catch(e){
561
- document.getElementById("pool-text").textContent="Unreachable";
562
- document.getElementById("pool").className="stat-value not-ready";
563
- document.getElementById("pulse").style.background="var(--red)";
564
- }
565
- }
566
- poll();
567
- setInterval(poll,3000);
568
- </script>
569
- </body>
570
- </html>
571
- """
572
-
573
-
574
  @app.get("/")
575
  async def root():
576
- return HTMLResponse(content=_LANDING_HTML)
577
 
578
 
579
  @app.post("/v1/image-to-glb")
 
15
 
16
  from fastapi import FastAPI, File, Form, HTTPException, UploadFile
17
  from fastapi.middleware.cors import CORSMiddleware
18
+ from fastapi.responses import FileResponse, JSONResponse
19
  from PIL import Image, UnidentifiedImageError
20
 
21
  import runtime_env # noqa: F401
 
477
  queue_state = state.queue.snapshot()
478
  return {
479
  "ok": state.executor is not None,
480
+ "version": app.version,
481
  "worker_pool_ready": state.executor is not None,
482
  "max_concurrent_jobs": MAX_CONCURRENT_JOBS,
483
  "active_jobs": queue_state["active_jobs"],
 
486
  }
487
 
488
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
489
  @app.get("/")
490
  async def root():
491
+ return FileResponse(ROOT_DIR / "frontend" / "index.html")
492
 
493
 
494
  @app.post("/v1/image-to-glb")