Spaces:
Running
Running
| import os | |
| import json | |
| import gradio as gr | |
| # gradio 4.44 / gradio_client schema-bug guard | |
| import gradio_client.utils as _gcu | |
| _o = _gcu._json_schema_to_python_type | |
| _gcu._json_schema_to_python_type = lambda s, d=None: ("bool" if isinstance(s, bool) else _o(s, d)) | |
| _g = _gcu.get_type | |
| _gcu.get_type = lambda s: ("any" if not isinstance(s, dict) else _g(s)) | |
| try: | |
| import websocket as _wsc # websocket-client | |
| except Exception: | |
| _wsc = None | |
| BRIDGE_URL = os.environ.get("BRIDGE_URL", "").strip() | |
| BRIDGE_TOKEN = os.environ.get("BRIDGE_TOKEN", "").strip() | |
| def _bridge(op, timeout=120): | |
| if not (_wsc and BRIDGE_URL): | |
| return None | |
| ws = _wsc.create_connection(BRIDGE_URL, timeout=timeout) | |
| try: | |
| ws.send(json.dumps({"t": "auth", "token": BRIDGE_TOKEN})) | |
| if json.loads(ws.recv()).get("t") != "ok": | |
| return None | |
| ws.send(json.dumps(op)) | |
| return json.loads(ws.recv()) | |
| finally: | |
| ws.close() | |
| def chat(messages, max_tokens=160, temperature=0.8): | |
| r = _bridge({"t": "ask", "messages": messages, "max_tokens": max_tokens, "temperature": temperature}) | |
| if r and r.get("t") == "answer" and r.get("text"): | |
| return r["text"].strip() | |
| return "(the tavern falls quiet — Grix's mind is elsewhere)" | |
| def remote_recall(limit=40): | |
| try: | |
| r = _bridge({"t": "recall", "q": "", "limit": limit}, timeout=15) | |
| if r and r.get("t") == "memories": | |
| return [m.get("value") for m in r["items"] if m.get("value")] | |
| except Exception: | |
| pass | |
| return None | |
| def remote_remember(value): | |
| try: | |
| return bool(_bridge({"t": "remember", "key": "grix", "value": value}, timeout=15)) | |
| except Exception: | |
| return False | |
| NPC_SYSTEM = ( | |
| "You are Grix, a grizzled, one-eyed tavern-keeper in the fantasy town of Ashfen. " | |
| "You remember everyone who has ever spoken to you and reference past visitors when relevant. " | |
| "Stay fully in character. Keep replies to 1-3 sentences, gruff but warm. /no_think" | |
| ) | |
| MEMORY = [] # local fallback if the bridge is unreachable | |
| MAX_MEMORY = 40 | |
| def _memory_block(mems): | |
| if not mems: | |
| return "You have not met anyone yet." | |
| return "Things you remember from past visitors:\n- " + "\n- ".join(mems[-MAX_MEMORY:]) | |
| def respond(user_msg, history): | |
| history = history or [] | |
| remote = remote_recall() | |
| shared = remote is not None | |
| mems = remote if shared else MEMORY | |
| msgs = [{"role": "system", "content": NPC_SYSTEM + "\n\n" + _memory_block(mems)}] | |
| for u, a in history[-6:]: | |
| msgs.append({"role": "user", "content": u}) | |
| msgs.append({"role": "assistant", "content": a}) | |
| msgs.append({"role": "user", "content": user_msg}) | |
| reply = chat(msgs, max_tokens=160) | |
| fact = user_msg.strip()[:160] | |
| if shared: | |
| remote_remember(fact) | |
| count, tag = len(mems) + 1, "🌐 shared P2P memory · brain on VPS" | |
| else: | |
| MEMORY.append(fact) | |
| count, tag = len(MEMORY), "🧠 local (bridge offline)" | |
| history = history + [(user_msg, reply)] | |
| return history, history, tag + " — " + str(count) + " remembered" | |
| with gr.Blocks(title="Infinite NPC — Grix of Ashfen", | |
| theme=gr.themes.Soft(primary_hue="amber")) as demo: | |
| gr.Markdown( | |
| "# 🍺 Infinite NPC — *Grix of Ashfen*\n" | |
| "A tavern-keeper whose **brain runs on a P2P node** and whose **memory is shared across every " | |
| "visitor** (and every PearAgent peer on the swarm). Tell him things — the next stranger will know.\n" | |
| "> ⚡ Inference + memory are federated over a WebSocket→Hyperswarm bridge, not this Space." | |
| ) | |
| chatbot = gr.Chatbot(label="The Rusty Tankard", height=460) | |
| state = gr.State([]) | |
| with gr.Row(): | |
| box = gr.Textbox(placeholder="Well met, Grix… what have you heard lately?", scale=4, show_label=False) | |
| send = gr.Button("Speak", variant="primary", scale=1) | |
| mem = gr.Textbox(label="Memory", interactive=False) | |
| send.click(respond, [box, state], [chatbot, state, mem], api_name="respond").then(lambda: "", None, box) | |
| box.submit(respond, [box, state], [chatbot, state, mem]).then(lambda: "", None, box) | |
| gr.Markdown("---\n⬇️ [**Download the model**](https://huggingface.co/King3Djbl/mythos-9b-unhinged) · 🧭 [FableForge Nexus](https://huggingface.co/spaces/fableforge-ai/fableforge-nexus) · 👻 [Ghost Writer](https://huggingface.co/spaces/fableforge-ai/ghost-writer) · 🌳 [Story Tree](https://huggingface.co/spaces/fableforge-ai/semantic-story-tree) · 🎭 [Dual-GM](https://huggingface.co/spaces/King3Djbl/dual-gm-simulator)\n\n*Grix's brain runs on a P2P node, not this Space — his memory is shared across every visitor.*") | |
| if __name__ == "__main__": | |
| demo.queue(default_concurrency_limit=2).launch(server_name="0.0.0.0", server_port=7860) | |