Spaces:
Running
Running
| import os | |
| import time | |
| import mimetypes | |
| from dotenv import load_dotenv | |
| from fastapi import FastAPI, Request | |
| from fastapi.responses import JSONResponse, RedirectResponse | |
| from fastapi.staticfiles import StaticFiles | |
| from huggingface_hub import InferenceClient | |
| load_dotenv() | |
| HF_TOKEN = os.getenv("HF_TOKEN") | |
| MODEL_ID = os.getenv("MODEL_ID", "Qwen/Qwen2.5-1.5B-Instruct") | |
| GAME_DIR = "game" | |
| MAX_TOKENS = 80 | |
| TEMPERATURE = 0.5 | |
| TOP_P = 0.85 | |
| client = InferenceClient(token=HF_TOKEN) if HF_TOKEN else None | |
| mimetypes.add_type("application/wasm", ".wasm") | |
| mimetypes.add_type("application/octet-stream", ".pck") | |
| def generate_reply(prompt: str) -> str: | |
| """Generate short NPC dialogue for the Godot game.""" | |
| if client is None: | |
| time.sleep(0.2) | |
| return "The air is calm today. Bring what you find, and we will make it useful." | |
| start_time = time.time() | |
| completion = client.chat.completions.create( | |
| model=MODEL_ID, | |
| messages=[ | |
| { | |
| "role": "system", | |
| "content": ( | |
| "You are an NPC in the post-nuclear RPG Shadows of Tomorrow. " | |
| "Answer in plain text only, with 1 or 2 short natural sentences." | |
| ), | |
| }, | |
| {"role": "user", "content": prompt}, | |
| ], | |
| max_tokens=MAX_TOKENS, | |
| temperature=TEMPERATURE, | |
| top_p=TOP_P, | |
| ) | |
| print(f"HF inference model={MODEL_ID} took={time.time() - start_time:.2f}s") | |
| return completion.choices[0].message.content.strip() | |
| app = FastAPI() | |
| async def root(): | |
| """Make the Space open straight into the fullscreen Godot web export.""" | |
| return RedirectResponse(url="/game/index.html", status_code=307) | |
| async def healthz(): | |
| return {"ok": True, "model": MODEL_ID} | |
| async def ai_response(request: Request): | |
| """Contract used by Godot: {"prompt": str} -> {"output": str}.""" | |
| try: | |
| data = await request.json() | |
| except Exception: | |
| return JSONResponse({"error": "Invalid JSON body"}, status_code=400) | |
| prompt = (data or {}).get("prompt") | |
| if not prompt: | |
| return JSONResponse({"error": "Missing 'prompt' in request body"}, status_code=400) | |
| try: | |
| return {"output": generate_reply(prompt)} | |
| except Exception as e: | |
| print(f"Inference error: {e}") | |
| return JSONResponse({"error": str(e)}, status_code=500) | |
| if os.path.isdir(GAME_DIR): | |
| app.mount("/game", StaticFiles(directory=GAME_DIR, html=True), name="game") | |
| else: | |
| print(f"WARNING: '{GAME_DIR}/' not found. Export the Godot Web build before deploying.") | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=7860) | |