Spaces:
Runtime error
Runtime error
Commit ·
ea809e6
1
Parent(s): 67226a3
fix: FastAPI serves index.html at root, Gradio API at /gradio/run/*
Browse files- app.py +92 -141
- index.html +1 -1
- requirements.txt +2 -1
app.py
CHANGED
|
@@ -1,10 +1,7 @@
|
|
| 1 |
-
"""app.py — SnapQuest
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
- index.html serves the full game UI as a static file
|
| 6 |
-
- JS in index.html calls /run/upload, /run/action etc. directly
|
| 7 |
-
- Zero gr.HTML, zero SSE timing issues, zero iframe sandbox problems
|
| 8 |
"""
|
| 9 |
import base64
|
| 10 |
import json
|
|
@@ -12,6 +9,9 @@ import os
|
|
| 12 |
import tempfile
|
| 13 |
|
| 14 |
import gradio as gr
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
from engine_photo import start_photo_game, take_photo_action
|
| 17 |
from dungeon import current_room, can_advance
|
|
@@ -19,17 +19,14 @@ from game_data import roll_loot
|
|
| 19 |
from vision import analyze_scene
|
| 20 |
from voice import clean_for_speech, speak, transcribe_audio
|
| 21 |
|
|
|
|
| 22 |
|
| 23 |
-
# ── Session store
|
| 24 |
-
_SESSIONS: dict
|
| 25 |
|
| 26 |
def _get(sid: str) -> dict:
|
| 27 |
if sid not in _SESSIONS:
|
| 28 |
-
_SESSIONS[sid] = {
|
| 29 |
-
"photos": [],
|
| 30 |
-
"selected_class": None,
|
| 31 |
-
"game": {},
|
| 32 |
-
}
|
| 33 |
return _SESSIONS[sid]
|
| 34 |
|
| 35 |
def _save_b64(data_url: str) -> str:
|
|
@@ -47,31 +44,60 @@ def _fmt_story(state: dict) -> str:
|
|
| 47 |
room = current_room(state)
|
| 48 |
diff = room.get("difficulty", "").upper()
|
| 49 |
sym = {"EASY": "◆", "MEDIUM": "◈", "HARD": "⬡"}.get(diff, "◆")
|
| 50 |
-
lines = [
|
| 51 |
-
|
| 52 |
-
"",
|
| 53 |
-
room.get("scene_description", ""),
|
| 54 |
-
]
|
| 55 |
for entry in state.get("history", [])[-8:]:
|
| 56 |
lines.append(f"\n▷ {entry.get('action','')}")
|
| 57 |
st = entry.get("response", {}).get("story", "")
|
| 58 |
-
if st:
|
| 59 |
-
lines.append(st)
|
| 60 |
if can_advance(state):
|
| 61 |
lines.append("\n\n[ Room cleared — click DESCEND DEEPER ]")
|
| 62 |
return "\n".join(str(l) for l in lines if l is not None)
|
| 63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
-
# ── API
|
| 66 |
|
| 67 |
def api_upload(sid: str, slot: int, data_url: str, name: str) -> str:
|
| 68 |
app = _get(sid)
|
| 69 |
photos = list(app.get("photos", []))
|
| 70 |
-
while len(photos) <= slot:
|
| 71 |
-
photos.append({})
|
| 72 |
if not data_url:
|
| 73 |
-
photos[slot] = {}
|
| 74 |
-
app["photos"] = photos
|
| 75 |
return json.dumps({"ok": True, "photos": _photos_summary(photos)})
|
| 76 |
try:
|
| 77 |
path = _save_b64(data_url)
|
|
@@ -83,22 +109,14 @@ def api_upload(sid: str, slot: int, data_url: str, name: str) -> str:
|
|
| 83 |
app["photos"] = photos
|
| 84 |
return json.dumps({"ok": True, "photos": _photos_summary(photos)})
|
| 85 |
|
| 86 |
-
def _photos_summary(photos):
|
| 87 |
-
return [
|
| 88 |
-
{"has": bool(p.get("path")), "objects": p.get("objects", []), "name": p.get("name", "")}
|
| 89 |
-
for p in photos
|
| 90 |
-
]
|
| 91 |
-
|
| 92 |
def api_start(sid: str, character_class: str) -> str:
|
| 93 |
app = _get(sid)
|
| 94 |
-
|
| 95 |
-
paths = [p["path"] for p in photos if p.get("path")]
|
| 96 |
if not paths:
|
| 97 |
return json.dumps({"ok": False, "error": "No photos uploaded"})
|
| 98 |
try:
|
| 99 |
gs = start_photo_game(paths, character_class)
|
| 100 |
-
app["game"] = gs
|
| 101 |
-
app["selected_class"] = character_class
|
| 102 |
return json.dumps({"ok": True, "state": _serialize_state(gs)})
|
| 103 |
except Exception as exc:
|
| 104 |
return json.dumps({"ok": False, "error": str(exc)})
|
|
@@ -124,18 +142,14 @@ def api_action(sid: str, text: str) -> str:
|
|
| 124 |
try:
|
| 125 |
txt = clean_for_speech(parsed)
|
| 126 |
if txt:
|
| 127 |
-
|
| 128 |
-
with open(
|
| 129 |
audio_b64 = base64.b64encode(f.read()).decode()
|
| 130 |
except Exception:
|
| 131 |
pass
|
| 132 |
-
return json.dumps({
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
"loot": loot,
|
| 136 |
-
"audio_b64": audio_b64,
|
| 137 |
-
"story": parsed.get("story", ""),
|
| 138 |
-
})
|
| 139 |
except Exception as exc:
|
| 140 |
return json.dumps({"ok": False, "error": str(exc)})
|
| 141 |
|
|
@@ -145,8 +159,7 @@ def api_voice(sid: str, audio_b64: str) -> str:
|
|
| 145 |
try:
|
| 146 |
raw = base64.b64decode(audio_b64)
|
| 147 |
fd, path = tempfile.mkstemp(suffix=".wav", dir="/tmp")
|
| 148 |
-
with os.fdopen(fd, "wb") as f:
|
| 149 |
-
f.write(raw)
|
| 150 |
text = transcribe_audio(path)
|
| 151 |
if not text:
|
| 152 |
return json.dumps({"ok": False, "text": ""})
|
|
@@ -156,105 +169,43 @@ def api_voice(sid: str, audio_b64: str) -> str:
|
|
| 156 |
except Exception as exc:
|
| 157 |
return json.dumps({"ok": False, "error": str(exc), "text": ""})
|
| 158 |
|
| 159 |
-
|
| 160 |
-
app = _get(sid)
|
| 161 |
-
gs = app.get("game", {})
|
| 162 |
-
return json.dumps({
|
| 163 |
-
"ok": True,
|
| 164 |
-
"photos": _photos_summary(app.get("photos", [])),
|
| 165 |
-
"selected_class": app.get("selected_class"),
|
| 166 |
-
"has_game": bool(gs.get("rooms")),
|
| 167 |
-
"state": _serialize_state(gs) if gs.get("rooms") else {},
|
| 168 |
-
})
|
| 169 |
|
| 170 |
-
|
| 171 |
-
rooms = gs.get("rooms", [])
|
| 172 |
-
idx = gs.get("room_index", 0)
|
| 173 |
-
room = rooms[min(idx, len(rooms)-1)] if rooms else {}
|
| 174 |
-
boss = room.get("boss") if room.get("is_boss") else None
|
| 175 |
-
return {
|
| 176 |
-
"hp": gs.get("hp", 100),
|
| 177 |
-
"max_hp": gs.get("max_hp", 100),
|
| 178 |
-
"xp": gs.get("xp", 0),
|
| 179 |
-
"level": 1 + gs.get("xp", 0) // 100,
|
| 180 |
-
"inventory": gs.get("inventory", []),
|
| 181 |
-
"character_class": gs.get("character_class", ""),
|
| 182 |
-
"room_index": idx,
|
| 183 |
-
"total_rooms": len(rooms),
|
| 184 |
-
"room": {
|
| 185 |
-
"scene_name": room.get("scene_name", ""),
|
| 186 |
-
"scene_description": room.get("scene_description", ""),
|
| 187 |
-
"atmosphere": room.get("atmosphere", ""),
|
| 188 |
-
"objects_found": room.get("objects_found", []),
|
| 189 |
-
"difficulty": room.get("difficulty", "easy"),
|
| 190 |
-
"is_boss": room.get("is_boss", False),
|
| 191 |
-
"cleared": room.get("cleared", False),
|
| 192 |
-
"enemy_alive": room.get("enemy_alive", True),
|
| 193 |
-
"enemy_hp": room.get("enemy_hp", 30),
|
| 194 |
-
"enemy_max_hp": room.get("enemy_max_hp", 30),
|
| 195 |
-
},
|
| 196 |
-
"boss": {
|
| 197 |
-
"name": boss.get("name", "") if boss else "",
|
| 198 |
-
"hp": boss.get("hp", 0) if boss else 0,
|
| 199 |
-
"max_hp": boss.get("max_hp", 100) if boss else 100,
|
| 200 |
-
"alive": boss.get("alive", True) if boss else False,
|
| 201 |
-
} if boss else None,
|
| 202 |
-
"choices": gs.get("current_choices", ["Look around", "Move forward", "Hold position"]),
|
| 203 |
-
"story": _fmt_story(gs),
|
| 204 |
-
"rooms_summary": [
|
| 205 |
-
{"cleared": r.get("cleared", False), "is_boss": r.get("is_boss", False), "scene_name": r.get("scene_name","")}
|
| 206 |
-
for r in rooms
|
| 207 |
-
],
|
| 208 |
-
}
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
# ── Gradio app — API only, no visible UI ────────────────────────────────────
|
| 212 |
-
|
| 213 |
-
with gr.Blocks(title="SnapQuest API") as demo:
|
| 214 |
-
gr.Markdown("## SnapQuest API — open the App tab to play")
|
| 215 |
-
|
| 216 |
-
with gr.Row():
|
| 217 |
-
sid_box = gr.Textbox(label="sid", visible=False)
|
| 218 |
-
result_box = gr.Textbox(label="result", visible=False)
|
| 219 |
-
|
| 220 |
-
# Upload endpoint
|
| 221 |
with gr.Row(visible=False):
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
up_btn.click(api_upload, inputs=[up_sid, up_slot, up_data, up_name], outputs=[up_out], api_name="upload")
|
| 226 |
-
|
| 227 |
-
# Start endpoint
|
| 228 |
with gr.Row(visible=False):
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
st_btn.click(api_start, inputs=[st_sid, st_cls], outputs=[st_out], api_name="start")
|
| 233 |
-
|
| 234 |
-
# Action endpoint
|
| 235 |
with gr.Row(visible=False):
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
ac_btn.click(api_action, inputs=[ac_sid, ac_txt], outputs=[ac_out], api_name="action")
|
| 240 |
-
|
| 241 |
-
# State endpoint
|
| 242 |
with gr.Row(visible=False):
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
gs_btn.click(api_state, inputs=[gs_sid], outputs=[gs_out], api_name="getstate")
|
| 247 |
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
|
|
|
|
|
|
|
| 255 |
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
allowed_paths=["/tmp", tempfile.gettempdir()],
|
| 260 |
-
)
|
|
|
|
| 1 |
+
"""app.py — SnapQuest.
|
| 2 |
+
FastAPI serves index.html at / and /game.
|
| 3 |
+
Gradio mounts at /gradio — its API endpoints at /gradio/run/*.
|
| 4 |
+
index.html calls /gradio/run/<endpoint>.
|
|
|
|
|
|
|
|
|
|
| 5 |
"""
|
| 6 |
import base64
|
| 7 |
import json
|
|
|
|
| 9 |
import tempfile
|
| 10 |
|
| 11 |
import gradio as gr
|
| 12 |
+
from fastapi import FastAPI
|
| 13 |
+
from fastapi.responses import FileResponse
|
| 14 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 15 |
|
| 16 |
from engine_photo import start_photo_game, take_photo_action
|
| 17 |
from dungeon import current_room, can_advance
|
|
|
|
| 19 |
from vision import analyze_scene
|
| 20 |
from voice import clean_for_speech, speak, transcribe_audio
|
| 21 |
|
| 22 |
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
| 23 |
|
| 24 |
+
# ── Session store ─────────────────────────────────────────────────────────────
|
| 25 |
+
_SESSIONS: dict = {}
|
| 26 |
|
| 27 |
def _get(sid: str) -> dict:
|
| 28 |
if sid not in _SESSIONS:
|
| 29 |
+
_SESSIONS[sid] = {"photos": [], "selected_class": None, "game": {}}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
return _SESSIONS[sid]
|
| 31 |
|
| 32 |
def _save_b64(data_url: str) -> str:
|
|
|
|
| 44 |
room = current_room(state)
|
| 45 |
diff = room.get("difficulty", "").upper()
|
| 46 |
sym = {"EASY": "◆", "MEDIUM": "◈", "HARD": "⬡"}.get(diff, "◆")
|
| 47 |
+
lines = [f"{'─'*5} {sym} {room.get('scene_name','Unknown')} {sym} {'─'*5}", "",
|
| 48 |
+
room.get("scene_description", "")]
|
|
|
|
|
|
|
|
|
|
| 49 |
for entry in state.get("history", [])[-8:]:
|
| 50 |
lines.append(f"\n▷ {entry.get('action','')}")
|
| 51 |
st = entry.get("response", {}).get("story", "")
|
| 52 |
+
if st: lines.append(st)
|
|
|
|
| 53 |
if can_advance(state):
|
| 54 |
lines.append("\n\n[ Room cleared — click DESCEND DEEPER ]")
|
| 55 |
return "\n".join(str(l) for l in lines if l is not None)
|
| 56 |
|
| 57 |
+
def _photos_summary(photos):
|
| 58 |
+
return [{"has": bool(p.get("path")), "objects": p.get("objects", []), "name": p.get("name", "")}
|
| 59 |
+
for p in photos]
|
| 60 |
+
|
| 61 |
+
def _serialize_state(gs: dict) -> dict:
|
| 62 |
+
rooms = gs.get("rooms", [])
|
| 63 |
+
idx = gs.get("room_index", 0)
|
| 64 |
+
room = rooms[min(idx, len(rooms)-1)] if rooms else {}
|
| 65 |
+
boss = room.get("boss") if room.get("is_boss") else None
|
| 66 |
+
return {
|
| 67 |
+
"hp": gs.get("hp", 100), "max_hp": gs.get("max_hp", 100),
|
| 68 |
+
"xp": gs.get("xp", 0), "level": 1 + gs.get("xp", 0) // 100,
|
| 69 |
+
"inventory": gs.get("inventory", []),
|
| 70 |
+
"character_class": gs.get("character_class", ""),
|
| 71 |
+
"room_index": idx, "total_rooms": len(rooms),
|
| 72 |
+
"room": {
|
| 73 |
+
"scene_name": room.get("scene_name", ""),
|
| 74 |
+
"scene_description": room.get("scene_description", ""),
|
| 75 |
+
"atmosphere": room.get("atmosphere", ""),
|
| 76 |
+
"objects_found": room.get("objects_found", []),
|
| 77 |
+
"difficulty": room.get("difficulty", "easy"),
|
| 78 |
+
"is_boss": room.get("is_boss", False),
|
| 79 |
+
"cleared": room.get("cleared", False),
|
| 80 |
+
"enemy_alive": room.get("enemy_alive", True),
|
| 81 |
+
"enemy_hp": room.get("enemy_hp", 30),
|
| 82 |
+
"enemy_max_hp": room.get("enemy_max_hp", 30),
|
| 83 |
+
},
|
| 84 |
+
"boss": {"name": boss.get("name",""), "hp": boss.get("hp",0),
|
| 85 |
+
"max_hp": boss.get("max_hp",100), "alive": boss.get("alive",True)
|
| 86 |
+
} if boss else None,
|
| 87 |
+
"choices": gs.get("current_choices", ["Look around","Move forward","Hold position"]),
|
| 88 |
+
"story": _fmt_story(gs),
|
| 89 |
+
"rooms_summary": [{"cleared": r.get("cleared",False), "is_boss": r.get("is_boss",False),
|
| 90 |
+
"scene_name": r.get("scene_name","")} for r in rooms],
|
| 91 |
+
}
|
| 92 |
|
| 93 |
+
# ── Gradio API functions ──────────────────────────────────────────────────────
|
| 94 |
|
| 95 |
def api_upload(sid: str, slot: int, data_url: str, name: str) -> str:
|
| 96 |
app = _get(sid)
|
| 97 |
photos = list(app.get("photos", []))
|
| 98 |
+
while len(photos) <= slot: photos.append({})
|
|
|
|
| 99 |
if not data_url:
|
| 100 |
+
photos[slot] = {}; app["photos"] = photos
|
|
|
|
| 101 |
return json.dumps({"ok": True, "photos": _photos_summary(photos)})
|
| 102 |
try:
|
| 103 |
path = _save_b64(data_url)
|
|
|
|
| 109 |
app["photos"] = photos
|
| 110 |
return json.dumps({"ok": True, "photos": _photos_summary(photos)})
|
| 111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
def api_start(sid: str, character_class: str) -> str:
|
| 113 |
app = _get(sid)
|
| 114 |
+
paths = [p["path"] for p in app.get("photos", []) if p.get("path")]
|
|
|
|
| 115 |
if not paths:
|
| 116 |
return json.dumps({"ok": False, "error": "No photos uploaded"})
|
| 117 |
try:
|
| 118 |
gs = start_photo_game(paths, character_class)
|
| 119 |
+
app["game"] = gs; app["selected_class"] = character_class
|
|
|
|
| 120 |
return json.dumps({"ok": True, "state": _serialize_state(gs)})
|
| 121 |
except Exception as exc:
|
| 122 |
return json.dumps({"ok": False, "error": str(exc)})
|
|
|
|
| 142 |
try:
|
| 143 |
txt = clean_for_speech(parsed)
|
| 144 |
if txt:
|
| 145 |
+
apath = speak(txt)
|
| 146 |
+
with open(apath, "rb") as f:
|
| 147 |
audio_b64 = base64.b64encode(f.read()).decode()
|
| 148 |
except Exception:
|
| 149 |
pass
|
| 150 |
+
return json.dumps({"ok": True, "state": _serialize_state(new_gs),
|
| 151 |
+
"loot": loot, "audio_b64": audio_b64,
|
| 152 |
+
"story": parsed.get("story", "")})
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
except Exception as exc:
|
| 154 |
return json.dumps({"ok": False, "error": str(exc)})
|
| 155 |
|
|
|
|
| 159 |
try:
|
| 160 |
raw = base64.b64decode(audio_b64)
|
| 161 |
fd, path = tempfile.mkstemp(suffix=".wav", dir="/tmp")
|
| 162 |
+
with os.fdopen(fd, "wb") as f: f.write(raw)
|
|
|
|
| 163 |
text = transcribe_audio(path)
|
| 164 |
if not text:
|
| 165 |
return json.dumps({"ok": False, "text": ""})
|
|
|
|
| 169 |
except Exception as exc:
|
| 170 |
return json.dumps({"ok": False, "error": str(exc), "text": ""})
|
| 171 |
|
| 172 |
+
# ── Gradio Blocks ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
|
| 174 |
+
with gr.Blocks(title="SnapQuest") as demo:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
with gr.Row(visible=False):
|
| 176 |
+
gr.Button().click(api_upload,
|
| 177 |
+
inputs=[gr.Textbox(),gr.Number(),gr.Textbox(),gr.Textbox()],
|
| 178 |
+
outputs=[gr.Textbox()], api_name="upload")
|
|
|
|
|
|
|
|
|
|
| 179 |
with gr.Row(visible=False):
|
| 180 |
+
gr.Button().click(api_start,
|
| 181 |
+
inputs=[gr.Textbox(),gr.Textbox()],
|
| 182 |
+
outputs=[gr.Textbox()], api_name="start")
|
|
|
|
|
|
|
|
|
|
| 183 |
with gr.Row(visible=False):
|
| 184 |
+
gr.Button().click(api_action,
|
| 185 |
+
inputs=[gr.Textbox(),gr.Textbox()],
|
| 186 |
+
outputs=[gr.Textbox()], api_name="action")
|
|
|
|
|
|
|
|
|
|
| 187 |
with gr.Row(visible=False):
|
| 188 |
+
gr.Button().click(api_voice,
|
| 189 |
+
inputs=[gr.Textbox(),gr.Textbox()],
|
| 190 |
+
outputs=[gr.Textbox()], api_name="voice")
|
|
|
|
| 191 |
|
| 192 |
+
# ── FastAPI: / and /game → index.html, /gradio → Gradio API ──────────────────
|
| 193 |
+
|
| 194 |
+
fastapi_app = FastAPI()
|
| 195 |
+
fastapi_app.add_middleware(CORSMiddleware, allow_origins=["*"],
|
| 196 |
+
allow_methods=["*"], allow_headers=["*"])
|
| 197 |
+
|
| 198 |
+
@fastapi_app.get("/")
|
| 199 |
+
async def root():
|
| 200 |
+
return FileResponse(os.path.join(_HERE, "index.html"))
|
| 201 |
+
|
| 202 |
+
@fastapi_app.get("/game")
|
| 203 |
+
async def game():
|
| 204 |
+
return FileResponse(os.path.join(_HERE, "index.html"))
|
| 205 |
|
| 206 |
+
# Mount Gradio at /gradio — API at /gradio/run/upload etc.
|
| 207 |
+
fastapi_app = gr.mount_gradio_app(fastapi_app, demo, path="/gradio")
|
| 208 |
|
| 209 |
+
if __name__ == "__main__":
|
| 210 |
+
import uvicorn
|
| 211 |
+
uvicorn.run(fastapi_app, host="0.0.0.0", port=7860)
|
|
|
|
|
|
index.html
CHANGED
|
@@ -421,7 +421,7 @@ const LOOT_TIERS = {
|
|
| 421 |
// API CALLS — Gradio /run/<api_name> endpoints
|
| 422 |
// ════════════════════════════════════════════════════════════════════════════
|
| 423 |
async function callAPI(endpoint, data) {
|
| 424 |
-
const res = await fetch(`/run/${endpoint}`, {
|
| 425 |
method:'POST',
|
| 426 |
headers:{'Content-Type':'application/json'},
|
| 427 |
body: JSON.stringify({data}),
|
|
|
|
| 421 |
// API CALLS — Gradio /run/<api_name> endpoints
|
| 422 |
// ════════════════════════════════════════════════════════════════════════════
|
| 423 |
async function callAPI(endpoint, data) {
|
| 424 |
+
const res = await fetch(`/gradio/run/${endpoint}`, {
|
| 425 |
method:'POST',
|
| 426 |
headers:{'Content-Type':'application/json'},
|
| 427 |
body: JSON.stringify({data}),
|
requirements.txt
CHANGED
|
@@ -3,4 +3,5 @@ edge-tts
|
|
| 3 |
openai-whisper
|
| 4 |
Pillow
|
| 5 |
huggingface_hub
|
| 6 |
-
faster-whisper
|
|
|
|
|
|
| 3 |
openai-whisper
|
| 4 |
Pillow
|
| 5 |
huggingface_hub
|
| 6 |
+
faster-whisper
|
| 7 |
+
uvicorn
|