Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -3,15 +3,13 @@ import gradio as gr
|
|
| 3 |
import subprocess, os, sys, time
|
| 4 |
import urllib.request, urllib.error
|
| 5 |
from fastapi import Request
|
| 6 |
-
from fastapi.responses import Response, PlainTextResponse, HTMLResponse
|
| 7 |
|
| 8 |
PORT = 9001
|
| 9 |
FM_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "file-manager")
|
| 10 |
MODEL_ID = "Qwen/Qwen2.5-0.5B-Instruct"
|
| 11 |
|
| 12 |
-
#
|
| 13 |
-
# Launch the file manager as a background child process
|
| 14 |
-
# =================================================================
|
| 15 |
proc = {"p": None}
|
| 16 |
def start_fm():
|
| 17 |
if proc["p"] and proc["p"].poll() is None:
|
|
@@ -25,9 +23,7 @@ def start_fm():
|
|
| 25 |
|
| 26 |
start_fm()
|
| 27 |
|
| 28 |
-
#
|
| 29 |
-
# GPU info: runs nvidia-smi INSIDE the gate (GPU only attached here)
|
| 30 |
-
# =================================================================
|
| 31 |
@spaces.GPU
|
| 32 |
def gpu_report():
|
| 33 |
import torch
|
|
@@ -45,9 +41,7 @@ def gpu_report():
|
|
| 45 |
lines.append(f"nvidia-smi error: {e}")
|
| 46 |
return "\n".join(lines)
|
| 47 |
|
| 48 |
-
#
|
| 49 |
-
# LLM inference: model loads + generates INSIDE the gate
|
| 50 |
-
# =================================================================
|
| 51 |
_state = {"model": None, "tok": None}
|
| 52 |
|
| 53 |
@spaces.GPU
|
|
@@ -66,11 +60,9 @@ def run_llm(prompt: str):
|
|
| 66 |
reply = tok.decode(out[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
|
| 67 |
return f"[GPU: {torch.cuda.get_device_name(0)}]\n\n{reply}"
|
| 68 |
|
| 69 |
-
#
|
| 70 |
-
# Gradio owns the ONLY public port 7860
|
| 71 |
-
# =================================================================
|
| 72 |
with gr.Blocks() as demo:
|
| 73 |
-
gr.Markdown("Self-editing computer.
|
| 74 |
with gr.Row():
|
| 75 |
prompt = gr.Textbox(label="Ask the LLM")
|
| 76 |
out = gr.Textbox(label="Reply")
|
|
@@ -82,36 +74,24 @@ demo.queue()
|
|
| 82 |
demo.launch(server_name="0.0.0.0", server_port=7860,
|
| 83 |
ssr_mode=False, prevent_thread_lock=True)
|
| 84 |
|
| 85 |
-
app = demo.app
|
| 86 |
|
| 87 |
-
#
|
| 88 |
-
# Hot-reload: respawn ONLY the file-manager process, no Space rebuild
|
| 89 |
-
# =================================================================
|
| 90 |
@app.get("/reload")
|
| 91 |
def reload_fm():
|
| 92 |
start_fm()
|
| 93 |
time.sleep(1)
|
| 94 |
return HTMLResponse('Reloaded file-manager process. <a href="/fm/">back to /fm</a>')
|
| 95 |
|
| 96 |
-
# =================================================================
|
| 97 |
-
# GPU info route
|
| 98 |
-
# =================================================================
|
| 99 |
@app.get("/gpuinfo")
|
| 100 |
def gpuinfo():
|
| 101 |
return PlainTextResponse(gpu_report())
|
| 102 |
|
| 103 |
-
# =================================================================
|
| 104 |
-
# LLM route
|
| 105 |
-
# =================================================================
|
| 106 |
@app.get("/llm")
|
| 107 |
def llm(q: str = "Say hello and tell me which GPU you are running on."):
|
| 108 |
return PlainTextResponse(run_llm(q))
|
| 109 |
|
| 110 |
-
#
|
| 111 |
-
# Proxy: /fm/... -> 127.0.0.1:9001/fm/...
|
| 112 |
-
# Streams text/plain (live shell) chunk by chunk; buffers everything else.
|
| 113 |
-
# Forwards method + body so POST forms and the JSON REPL work.
|
| 114 |
-
# =================================================================
|
| 115 |
@app.api_route("/fm", methods=["GET", "POST"])
|
| 116 |
@app.api_route("/fm/{path:path}", methods=["GET", "POST"])
|
| 117 |
async def proxy(request: Request, path: str = ""):
|
|
@@ -122,33 +102,15 @@ async def proxy(request: Request, path: str = ""):
|
|
| 122 |
fwd = {k: v for k, v in request.headers.items() if k.lower() != "host"}
|
| 123 |
req = urllib.request.Request(url, data=body or None, method=request.method, headers=fwd)
|
| 124 |
try:
|
| 125 |
-
|
|
|
|
| 126 |
except urllib.error.HTTPError as e:
|
| 127 |
-
|
| 128 |
-
content=e.read(), status_code=e.code,
|
| 129 |
-
headers={"content-type": e.headers.get("content-type", "text/plain")})
|
| 130 |
except urllib.error.URLError:
|
| 131 |
return Response(content=b"file-manager not up yet on 9001", status_code=502)
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
-
ctype = r.headers.get("content-type", "text/html")
|
| 134 |
-
# Stream the live shell output; buffer normal pages
|
| 135 |
-
if ctype.startswith("text/plain"):
|
| 136 |
-
def gen():
|
| 137 |
-
while True:
|
| 138 |
-
chunk = r.read(1024)
|
| 139 |
-
if not chunk:
|
| 140 |
-
break
|
| 141 |
-
yield chunk
|
| 142 |
-
return StreamingResponse(gen(), media_type="text/plain")
|
| 143 |
-
|
| 144 |
-
out_headers = {"content-type": ctype}
|
| 145 |
-
for k, v in r.headers.items():
|
| 146 |
-
if k.lower() in ("set-cookie", "location"):
|
| 147 |
-
out_headers[k] = v
|
| 148 |
-
return Response(content=r.read(), status_code=r.status, headers=out_headers)
|
| 149 |
-
|
| 150 |
-
# =================================================================
|
| 151 |
-
# Keep the process alive (launch returned control)
|
| 152 |
-
# =================================================================
|
| 153 |
while True:
|
| 154 |
time.sleep(3600)
|
|
|
|
| 3 |
import subprocess, os, sys, time
|
| 4 |
import urllib.request, urllib.error
|
| 5 |
from fastapi import Request
|
| 6 |
+
from fastapi.responses import Response, PlainTextResponse, HTMLResponse
|
| 7 |
|
| 8 |
PORT = 9001
|
| 9 |
FM_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "file-manager")
|
| 10 |
MODEL_ID = "Qwen/Qwen2.5-0.5B-Instruct"
|
| 11 |
|
| 12 |
+
# --- Launch the file manager as a background child process ---
|
|
|
|
|
|
|
| 13 |
proc = {"p": None}
|
| 14 |
def start_fm():
|
| 15 |
if proc["p"] and proc["p"].poll() is None:
|
|
|
|
| 23 |
|
| 24 |
start_fm()
|
| 25 |
|
| 26 |
+
# --- GPU info (runs INSIDE the gate) ---
|
|
|
|
|
|
|
| 27 |
@spaces.GPU
|
| 28 |
def gpu_report():
|
| 29 |
import torch
|
|
|
|
| 41 |
lines.append(f"nvidia-smi error: {e}")
|
| 42 |
return "\n".join(lines)
|
| 43 |
|
| 44 |
+
# --- LLM (loads + generates INSIDE the gate) ---
|
|
|
|
|
|
|
| 45 |
_state = {"model": None, "tok": None}
|
| 46 |
|
| 47 |
@spaces.GPU
|
|
|
|
| 60 |
reply = tok.decode(out[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
|
| 61 |
return f"[GPU: {torch.cuda.get_device_name(0)}]\n\n{reply}"
|
| 62 |
|
| 63 |
+
# --- Gradio owns the ONLY public port 7860 ---
|
|
|
|
|
|
|
| 64 |
with gr.Blocks() as demo:
|
| 65 |
+
gr.Markdown("Self-editing computer. Terminal + file manager at /fm")
|
| 66 |
with gr.Row():
|
| 67 |
prompt = gr.Textbox(label="Ask the LLM")
|
| 68 |
out = gr.Textbox(label="Reply")
|
|
|
|
| 74 |
demo.launch(server_name="0.0.0.0", server_port=7860,
|
| 75 |
ssr_mode=False, prevent_thread_lock=True)
|
| 76 |
|
| 77 |
+
app = demo.app
|
| 78 |
|
| 79 |
+
# --- Hot-reload: respawn ONLY the file-manager process, no Space rebuild ---
|
|
|
|
|
|
|
| 80 |
@app.get("/reload")
|
| 81 |
def reload_fm():
|
| 82 |
start_fm()
|
| 83 |
time.sleep(1)
|
| 84 |
return HTMLResponse('Reloaded file-manager process. <a href="/fm/">back to /fm</a>')
|
| 85 |
|
|
|
|
|
|
|
|
|
|
| 86 |
@app.get("/gpuinfo")
|
| 87 |
def gpuinfo():
|
| 88 |
return PlainTextResponse(gpu_report())
|
| 89 |
|
|
|
|
|
|
|
|
|
|
| 90 |
@app.get("/llm")
|
| 91 |
def llm(q: str = "Say hello and tell me which GPU you are running on."):
|
| 92 |
return PlainTextResponse(run_llm(q))
|
| 93 |
|
| 94 |
+
# --- Proxy: /fm/... -> 127.0.0.1:9001/fm/... (method + body forwarded) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
@app.api_route("/fm", methods=["GET", "POST"])
|
| 96 |
@app.api_route("/fm/{path:path}", methods=["GET", "POST"])
|
| 97 |
async def proxy(request: Request, path: str = ""):
|
|
|
|
| 102 |
fwd = {k: v for k, v in request.headers.items() if k.lower() != "host"}
|
| 103 |
req = urllib.request.Request(url, data=body or None, method=request.method, headers=fwd)
|
| 104 |
try:
|
| 105 |
+
with urllib.request.urlopen(req) as r:
|
| 106 |
+
data, status, headers = r.read(), r.status, r.headers
|
| 107 |
except urllib.error.HTTPError as e:
|
| 108 |
+
data, status, headers = e.read(), e.code, e.headers
|
|
|
|
|
|
|
| 109 |
except urllib.error.URLError:
|
| 110 |
return Response(content=b"file-manager not up yet on 9001", status_code=502)
|
| 111 |
+
out = {k: v for k, v in headers.items()
|
| 112 |
+
if k.lower() in ("set-cookie", "location", "content-type")}
|
| 113 |
+
return Response(content=data, status_code=status, headers=out)
|
| 114 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
while True:
|
| 116 |
time.sleep(3600)
|