Commit ·
a62cfd3
1
Parent(s): 2d88665
Add FastAPI + vanilla JS frontend mounted alongside ZeroGPU Gradio
Browse filesGradio is mounted at /gradio purely as the ZeroGPU harness. Custom
routes live on /api/*, and the vanilla JS UI is served at / via
StaticFiles. Previous crash was StaticFiles raising on a missing
frontend/ directory that hadn't actually been pushed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- app.py +22 -2
- frontend/app.js +3 -5
- frontend/index.html +2 -6
- frontend/style.css +1 -16
- requirements.txt +1 -0
app.py
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
|
|
|
|
|
| 1 |
import spaces
|
| 2 |
import gradio as gr
|
| 3 |
import torch
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
|
| 6 |
@spaces.GPU(duration=30)
|
|
@@ -8,5 +14,19 @@ def infer(x):
|
|
| 8 |
return f"cuda available: {torch.cuda.is_available()}, got: {x}"
|
| 9 |
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
import spaces
|
| 4 |
import gradio as gr
|
| 5 |
import torch
|
| 6 |
+
from fastapi import FastAPI
|
| 7 |
+
from fastapi.staticfiles import StaticFiles
|
| 8 |
+
|
| 9 |
+
FRONTEND = os.path.join(os.path.dirname(os.path.abspath(__file__)), "frontend")
|
| 10 |
|
| 11 |
|
| 12 |
@spaces.GPU(duration=30)
|
|
|
|
| 14 |
return f"cuda available: {torch.cuda.is_available()}, got: {x}"
|
| 15 |
|
| 16 |
|
| 17 |
+
with gr.Blocks() as demo:
|
| 18 |
+
inp, out = gr.Textbox(), gr.Textbox()
|
| 19 |
+
gr.Button("Run").click(infer, inp, out, api_name="infer")
|
| 20 |
+
|
| 21 |
+
demo.queue()
|
| 22 |
+
|
| 23 |
+
app = FastAPI()
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@app.get("/api/health")
|
| 27 |
+
def health():
|
| 28 |
+
return {"status": "ok"}
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
app = gr.mount_gradio_app(app, demo, path="/gradio")
|
| 32 |
+
app.mount("/", StaticFiles(directory=FRONTEND, html=True), name="ui")
|
frontend/app.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
const promptEl = document.getElementById("prompt");
|
| 2 |
-
const tokensEl = document.getElementById("tokens");
|
| 3 |
const outputEl = document.getElementById("output");
|
| 4 |
const runBtn = document.getElementById("run");
|
| 5 |
|
|
@@ -14,7 +13,7 @@ async function generate() {
|
|
| 14 |
const startRes = await fetch("/gradio/call/infer", {
|
| 15 |
method: "POST",
|
| 16 |
headers: { "Content-Type": "application/json" },
|
| 17 |
-
body: JSON.stringify({ data: [prompt
|
| 18 |
});
|
| 19 |
if (!startRes.ok) throw new Error(`start failed: ${startRes.status}`);
|
| 20 |
const { event_id } = await startRes.json();
|
|
@@ -26,13 +25,12 @@ async function generate() {
|
|
| 26 |
outputEl.textContent = payload[0] ?? "";
|
| 27 |
});
|
| 28 |
|
| 29 |
-
es.addEventListener("
|
| 30 |
es.close();
|
| 31 |
runBtn.disabled = false;
|
| 32 |
});
|
| 33 |
|
| 34 |
-
|
| 35 |
-
es.addEventListener("complete", () => {
|
| 36 |
es.close();
|
| 37 |
runBtn.disabled = false;
|
| 38 |
});
|
|
|
|
| 1 |
const promptEl = document.getElementById("prompt");
|
|
|
|
| 2 |
const outputEl = document.getElementById("output");
|
| 3 |
const runBtn = document.getElementById("run");
|
| 4 |
|
|
|
|
| 13 |
const startRes = await fetch("/gradio/call/infer", {
|
| 14 |
method: "POST",
|
| 15 |
headers: { "Content-Type": "application/json" },
|
| 16 |
+
body: JSON.stringify({ data: [prompt] }),
|
| 17 |
});
|
| 18 |
if (!startRes.ok) throw new Error(`start failed: ${startRes.status}`);
|
| 19 |
const { event_id } = await startRes.json();
|
|
|
|
| 25 |
outputEl.textContent = payload[0] ?? "";
|
| 26 |
});
|
| 27 |
|
| 28 |
+
es.addEventListener("complete", () => {
|
| 29 |
es.close();
|
| 30 |
runBtn.disabled = false;
|
| 31 |
});
|
| 32 |
|
| 33 |
+
es.addEventListener("error", () => {
|
|
|
|
| 34 |
es.close();
|
| 35 |
runBtn.disabled = false;
|
| 36 |
});
|
frontend/index.html
CHANGED
|
@@ -9,12 +9,8 @@
|
|
| 9 |
<body>
|
| 10 |
<main class="wrap">
|
| 11 |
<h1>dllm</h1>
|
| 12 |
-
<textarea id="prompt" placeholder="Type
|
| 13 |
-
<
|
| 14 |
-
<label for="tokens">Max tokens</label>
|
| 15 |
-
<input id="tokens" type="number" value="128" min="16" max="512" step="16" />
|
| 16 |
-
<button id="run">Generate</button>
|
| 17 |
-
</div>
|
| 18 |
<pre id="output"></pre>
|
| 19 |
</main>
|
| 20 |
<script src="/app.js"></script>
|
|
|
|
| 9 |
<body>
|
| 10 |
<main class="wrap">
|
| 11 |
<h1>dllm</h1>
|
| 12 |
+
<textarea id="prompt" placeholder="Type something..."></textarea>
|
| 13 |
+
<button id="run">Generate</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
<pre id="output"></pre>
|
| 15 |
</main>
|
| 16 |
<script src="/app.js"></script>
|
frontend/style.css
CHANGED
|
@@ -25,23 +25,8 @@ textarea {
|
|
| 25 |
resize: vertical;
|
| 26 |
}
|
| 27 |
|
| 28 |
-
.row {
|
| 29 |
-
display: flex;
|
| 30 |
-
align-items: center;
|
| 31 |
-
gap: 0.5rem;
|
| 32 |
-
margin: 0.75rem 0;
|
| 33 |
-
}
|
| 34 |
-
|
| 35 |
-
.row input {
|
| 36 |
-
width: 5rem;
|
| 37 |
-
padding: 0.4rem;
|
| 38 |
-
background: #1a1d24;
|
| 39 |
-
color: inherit;
|
| 40 |
-
border: 1px solid #2a2e37;
|
| 41 |
-
border-radius: 6px;
|
| 42 |
-
}
|
| 43 |
-
|
| 44 |
button {
|
|
|
|
| 45 |
padding: 0.5rem 1rem;
|
| 46 |
background: #6d5ef8;
|
| 47 |
color: white;
|
|
|
|
| 25 |
resize: vertical;
|
| 26 |
}
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
button {
|
| 29 |
+
margin: 0.75rem 0;
|
| 30 |
padding: 0.5rem 1rem;
|
| 31 |
background: #6d5ef8;
|
| 32 |
color: white;
|
requirements.txt
CHANGED
|
@@ -1,3 +1,4 @@
|
|
| 1 |
torch
|
| 2 |
transformers
|
| 3 |
spaces
|
|
|
|
|
|
| 1 |
torch
|
| 2 |
transformers
|
| 3 |
spaces
|
| 4 |
+
fastapi
|