From a9000e8d4be0041075ac0e64bee47bb2167909c3 Mon Sep 17 00:00:00 2001 From: Timmy Date: Wed, 8 Jul 2026 19:47:16 +0000 Subject: [PATCH] portal: add live wizard web portal + websocket persona chat --- portal.py | 79 ++++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 4 +++ 2 files changed, 83 insertions(+) create mode 100644 portal.py create mode 100644 requirements.txt diff --git a/portal.py b/portal.py new file mode 100644 index 0000000..9be022c --- /dev/null +++ b/portal.py @@ -0,0 +1,79 @@ +"""Minimal live wizard portal: persona-aware chat over WebSocket.""" +import os, json +from pathlib import Path +from flask import Flask, render_template_string +from flask_sock import Sock +import yaml + +app = Flask(__name__) +sock = Sock(app) +ROOT = Path(os.getenv("PORTAL_ROOT", Path(__file__).resolve().parent)) +PERSONA_PATH = ROOT / "persona.yaml" + +with open(PERSONA_PATH, "r", encoding="utf-8") as f: + persona = yaml.safe_load(f) or {} + +PAGE = """ + + + + Timmy Time — Live Wizard Portal + + + +
+
+
+ + +
+
+ + + +""" +@app.get("/") +def index(): + return render_template_string(PAGE) + +@app.get("/health") +def health(): + return {"status":"ok","persona":persona.get("wizard",{}).get("name","Unknown")} + +@sock.route("/ws") +def ws(ws): + wizard = persona.get("wizard", {}) + catchphrases = wizard.get("catchphrases", []) + while True: + msg = ws.receive() + if msg is None: + break + try: + data = json.loads(msg) + except Exception: + continue + if data.get("type") == "text": + text = data.get("text", "").strip() + prefix = catchphrases[0] if catchphrases else "The mists reveal:" + reply = f"{prefix} {text}" + ws.send(json.dumps({"text": reply})) + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=8787) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..113afe2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +flask +flask-sock +PyYAML +requests