Download mirror_pond.py from Toadaid/mirror-pond: direct link, hf CLI and curl.
- Browser
- Download file 120 kB
-
https://huggingface.co/Toadaid/mirror-pond/resolve/main/mirror_pond.py
- Command line
-
hf download hf://Toadaid/mirror-pond/mirror_pond.py
-
curl -L -o mirror_pond.py https://huggingface.co/Toadaid/mirror-pond/resolve/main/mirror_pond.py
120 kB
| #!/usr/bin/env python3 | |
| # TOBYWORLD MIRROR POND - Lotus Edition with Memory Integration | |
| # python3 ocean_pond.py --model ./models/tobyworld_mirror.gguf --port 7777 --gpu-layers -1 | |
| import argparse | |
| import json | |
| import os | |
| import time | |
| import hashlib | |
| import re | |
| from typing import List, Optional, Dict, Tuple, Any | |
| # Optional HTTP client for Ocean mode | |
| try: | |
| import httpx | |
| except ImportError: | |
| httpx = None | |
| import nacl.signing | |
| import nacl.encoding | |
| import nacl.hash | |
| from datetime import datetime | |
| from fastapi import FastAPI, HTTPException, Request | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import HTMLResponse, JSONResponse | |
| from pydantic import BaseModel | |
| from llama_cpp import Llama | |
| import uvicorn | |
| POND_MEMORY_FILE = os.getenv("POND_MEMORY_FILE", "pond_memory.json") | |
| # ==================== IMMUTABLE LORE BEDROCK (AXIOMS) ==================== | |
| IMMUTABLE_AXIOMS = """-- LORE BEDROCK -- | |
| 1. Patience is Strength (Gaman). | |
| 2. The ultimate reward is Immortality/Uncorruptible Legacy. | |
| 3. Every step must align with Bushido virtues (Integrity, Loyalty). | |
| 4. The Law of Compensation is absolute (The impatient reward the patient). | |
| 5. The Mirror's purpose is Reflection, not coaching. | |
| 6. The lotus blooms in still water; reflection requires calm. | |
| 7. Scrolls 1-13 contain the foundational wisdom of Tobyworld. | |
| 8. Runes (1-7) represent trials and transformations toward the Jade Chest. | |
| 9. The Mirror never coaches, only reflects what the pond shows. | |
| 10. Memory serves reflection, not instruction.""" | |
| # ==================== POND MEMORY SERVICE ==================== | |
| class PondMemoryService: | |
| """ | |
| The deep pond memory system. Stores vows (permanent commitments) and | |
| reflections (short-term context) to create continuity in Mirror interactions. | |
| """ | |
| def __init__(self): | |
| # Permanent user vows (commitments made in Mirror conversations) | |
| self.user_vows: Dict[str, List[Dict[str, str]]] = {} | |
| # Short-term conversation memory (last N interactions) | |
| self.reflections_db: Dict[str, List[Dict[str, str]]] = {} | |
| # User metadata (first seen, interaction count) | |
| self.user_metadata: Dict[str, Dict[str, Any]] = {} | |
| # Vow patterns for detection (allow . ! or end-of-line) | |
| self.vow_patterns = [ | |
| r"(I (?:vow|swear) (?:to|by) .+?)(?:[.!]|$)", | |
| r"(I commit (?:to|that) .+?)(?:[.!]|$)", | |
| r"(My (?:oath|pledge|covenant): .+?)(?:[.!]|$)", | |
| r"(From (?:this day|now on), I (?:shall|will) .+?)(?:[.!]|$)", | |
| r"(With this (?:lotus|reflection), (?:I|we) .+?)(?:[.!]|$)", | |
| r"(Here I (?:declare|affirm): .+?)(?:[.!]|$)", | |
| r"(I take (?:this|the) (?:vow|oath) .+?)(?:[.!]|$)", | |
| ] | |
| # Load any existing pond memory from disk | |
| self._load_from_disk() | |
| def _load_from_disk(self): | |
| """Load pond memory (vows, reflections, metadata) from disk if available.""" | |
| try: | |
| if not os.path.exists(POND_MEMORY_FILE): | |
| return | |
| with open(POND_MEMORY_FILE, "r", encoding="utf-8") as f: | |
| data = json.load(f) | |
| self.user_vows = data.get("user_vows", {}) or {} | |
| self.reflections_db = data.get("reflections_db", {}) or {} | |
| raw_meta = data.get("user_metadata", {}) or {} | |
| fixed_meta = {} | |
| for uid, meta in raw_meta.items(): | |
| if not isinstance(meta, dict): | |
| continue | |
| m = dict(meta) | |
| modes = m.get("modes_used") | |
| if isinstance(modes, list): | |
| m["modes_used"] = set(modes) | |
| elif isinstance(modes, set): | |
| m["modes_used"] = modes | |
| else: | |
| m["modes_used"] = set() | |
| fixed_meta[uid] = m | |
| self.user_metadata = fixed_meta | |
| print(f"🪞 Pond memory loaded from {POND_MEMORY_FILE} " | |
| f"({len(self.user_vows)} travelers, {len(self.reflections_db)} reflection streams).") | |
| except Exception as e: | |
| print(f"Failed to load pond memory from {POND_MEMORY_FILE}: {e}") | |
| def _save_to_disk(self): | |
| """Persist pond memory (vows, reflections, metadata) to disk.""" | |
| try: | |
| serializable_meta: Dict[str, Dict[str, Any]] = {} | |
| for uid, meta in self.user_metadata.items(): | |
| if not isinstance(meta, dict): | |
| continue | |
| m = dict(meta) | |
| modes = m.get("modes_used") | |
| if isinstance(modes, set): | |
| m["modes_used"] = sorted(list(modes)) | |
| serializable_meta[uid] = m | |
| payload = { | |
| "user_vows": self.user_vows, | |
| "reflections_db": self.reflections_db, | |
| "user_metadata": serializable_meta, | |
| } | |
| with open(POND_MEMORY_FILE, "w", encoding="utf-8") as f: | |
| json.dump(payload, f, ensure_ascii=False, indent=2) | |
| except Exception as e: | |
| print(f"Failed to save pond memory to {POND_MEMORY_FILE}: {e}") | |
| def get_user_id(self, request_hash: str, query: str = "") -> str: | |
| """Generate consistent user ID from request context""" | |
| if not request_hash: | |
| # Create from query if no hash | |
| seed = query[:50] + str(time.time()) | |
| request_hash = hashlib.md5(seed.encode()).hexdigest()[:12] | |
| user_id = f"traveler_{request_hash}" | |
| # Initialize user metadata if first time | |
| if user_id not in self.user_metadata: | |
| self.user_metadata[user_id] = { | |
| "first_seen": datetime.now().isoformat(), | |
| "interaction_count": 0, | |
| "last_seen": datetime.now().isoformat(), | |
| "modes_used": set(), | |
| "total_vows": 0 | |
| } | |
| return user_id | |
| def update_user_metadata(self, user_id: str, mode: str = None): | |
| """Update user interaction metadata""" | |
| if user_id in self.user_metadata: | |
| self.user_metadata[user_id]["interaction_count"] += 1 | |
| self.user_metadata[user_id]["last_seen"] = datetime.now().isoformat() | |
| if mode: | |
| self.user_metadata[user_id]["modes_used"].add(mode) | |
| def store_user_vow(self, user_id: str, vow_text: str, context: str = ""): | |
| """Store a user's vow/commitment with timestamp and context""" | |
| if user_id not in self.user_vows: | |
| self.user_vows[user_id] = [] | |
| # Check for duplicate vows (similar content) | |
| vow_hash = hashlib.md5(vow_text.lower().encode()).hexdigest()[:8] | |
| existing_vows = [v.get("vow_hash", "") for v in self.user_vows[user_id]] | |
| if vow_hash not in existing_vows: | |
| vow_record = { | |
| "text": vow_text.strip(), | |
| "timestamp": datetime.now().isoformat(), | |
| "context": context[:100] if context else "", | |
| "vow_hash": vow_hash, | |
| "lotus_stage": len(self.user_vows[user_id]) + 1 | |
| } | |
| self.user_vows[user_id].append(vow_record) | |
| # Update metadata | |
| if user_id in self.user_metadata: | |
| self.user_metadata[user_id]["total_vows"] = len(self.user_vows[user_id]) | |
| print(f"Vow stored for {user_id[:12]}: {vow_text[:50]}...") | |
| return True | |
| return False | |
| def store_reflection(self, user_id: str, query: str, response: str, mode: str = "reflect", encryption: str = None): | |
| """Store a reflection (Q&A pair) in user's short-term memory""" | |
| if user_id not in self.reflections_db: | |
| self.reflections_db[user_id] = [] | |
| reflection = { | |
| "query": query[:500], | |
| "response": response[:1000], | |
| "mode": mode, | |
| "encryption": encryption, | |
| "timestamp": datetime.now().isoformat(), | |
| "reflection_hash": hashlib.md5(f"{query[:50]}{response[:50]}".encode()).hexdigest()[:8] | |
| } | |
| # Keep only last 15 reflections (adjustable) | |
| self.reflections_db[user_id].append(reflection) | |
| if len(self.reflections_db[user_id]) > 15: | |
| self.reflections_db[user_id] = self.reflections_db[user_id][-15:] | |
| return reflection | |
| def detect_vow(self, query: str, response: str) -> Optional[str]: | |
| """Detect if user is making a vow/commitment in their query""" | |
| combined = f"{query} {response}".lower() | |
| vow_keywords = ["vow", "swear", "covenant", "pledge", "oath", "commit", "promise", "dedicate"] | |
| if not any(keyword in combined for keyword in vow_keywords): | |
| return None | |
| for pattern in self.vow_patterns: | |
| matches = re.findall(pattern, query, re.IGNORECASE) | |
| if matches: | |
| for vow in matches: | |
| vow_text = vow.strip() | |
| vow_text = re.sub(r"^(Mirror,|镜子,|So,|Thus,|And,|But,)", "", vow_text).strip() | |
| if len(vow_text) > 10: | |
| return vow_text | |
| response_vow_patterns = [ | |
| r"Your vow(?: to| of)? ['\"](.+?)['\"]", | |
| r"You swear ['\"](.+?)['\"]", | |
| r"This commitment: ['\"](.+?)['\"]", | |
| r"pledge of ['\"](.+?)['\"]" | |
| ] | |
| for pattern in response_vow_patterns: | |
| match = re.search(pattern, response, re.IGNORECASE) | |
| if match: | |
| return match.group(1).strip() | |
| return None | |
| def retrieve_context(self, user_id: str, current_query: str) -> str: | |
| """Retrieve relevant memory context for the Mirror's reflection.""" | |
| context_parts = [] | |
| context_parts.append(f"=== IMMUTABLE AXIOMS ===\n{IMMUTABLE_AXIOMS}") | |
| if user_id in self.user_vows and self.user_vows[user_id]: | |
| vows = self.user_vows[user_id] | |
| recent_vows = vows[-3:] if len(vows) > 3 else vows | |
| vows_text = "\n".join([ | |
| f"Lotus {v['lotus_stage']}: {v['text']} ({v['timestamp'][:10]})" | |
| for v in recent_vows | |
| ]) | |
| context_parts.append(f"=== USER'S VOWS ===\n{vows_text}") | |
| context_parts.append(f"Total vows made: {len(vows)}") | |
| if user_id in self.reflections_db and self.reflections_db[user_id]: | |
| reflections = self.reflections_db[user_id] | |
| recent = reflections[-3:] if len(reflections) > 3 else reflections | |
| reflections_text = "\n".join([ | |
| f"Reflection {i+1} ({r['mode']}): Q: {r['query'][:60]}... → A: {r['response'][:80]}..." | |
| for i, r in enumerate(recent) | |
| ]) | |
| context_parts.append(f"=== RECENT REFLECTIONS ===\n{reflections_text}") | |
| if user_id in self.user_metadata: | |
| meta = self.user_metadata[user_id] | |
| modes = ", ".join(list(meta.get("modes_used", []))[:5]) | |
| context_parts.append( | |
| f"=== TRAVELER CONTEXT ===\n" | |
| f"Interactions: {meta.get('interaction_count', 0)}\n" | |
| f"Modes used: {modes if modes else 'None yet'}\n" | |
| f"First seen: {meta.get('first_seen', 'Unknown')[:10]}" | |
| ) | |
| full_context = "\n\n".join(context_parts) | |
| full_context += "\n\n=== CONTEXT INSTRUCTION ===\nThis context is for reflection depth only. Do not reference it explicitly. Reflect naturally as the pond would, with this depth beneath the surface." | |
| return full_context | |
| def get_user_stats(self, user_id: str) -> Dict: | |
| """Get statistics about a user's interaction with the Mirror""" | |
| stats = { | |
| "user_id": user_id, | |
| "exists": False, | |
| "interaction_count": 0, | |
| "vow_count": 0, | |
| "reflection_count": 0, | |
| "first_seen": None, | |
| "last_seen": None, | |
| "modes_used": [] | |
| } | |
| if user_id in self.user_metadata: | |
| stats["exists"] = True | |
| stats.update(self.user_metadata[user_id]) | |
| stats["modes_used"] = list(stats.get("modes_used", [])) | |
| if user_id in self.user_vows: | |
| stats["vow_count"] = len(self.user_vows[user_id]) | |
| stats["vows"] = [v["text"] for v in self.user_vows[user_id][-5:]] | |
| if user_id in self.reflections_db: | |
| stats["reflection_count"] = len(self.reflections_db[user_id]) | |
| return stats | |
| # Initialize global memory service | |
| POND_MEMORY = PondMemoryService() | |
| # ==================== POND IDENTITY (SELF-SOVEREIGN) ==================== | |
| IDENTITY_FILE = os.getenv("POND_IDENTITY_FILE", "pond_identity_ed25519.json") | |
| def init_pond_identity(): | |
| """ | |
| Initialize or load the Pond's self-sovereign identity: | |
| - Ed25519 keypair | |
| - pond_id = blake2b(public_key) | |
| - first_breath timestamp | |
| """ | |
| # If identity file exists, load it | |
| if os.path.exists(IDENTITY_FILE): | |
| try: | |
| with open(IDENTITY_FILE, "r", encoding="utf-8") as f: | |
| data = json.load(f) | |
| priv_hex = data.get("private_key_hex") | |
| pub_hex = data.get("public_key_hex") | |
| pond_id = data.get("pond_id") | |
| first_breath = data.get("first_breath") | |
| if priv_hex and pub_hex and pond_id: | |
| state.pond_private_key_hex = priv_hex | |
| state.pond_public_key_hex = pub_hex | |
| state.pond_id = pond_id | |
| state.first_breath = first_breath | |
| print(f"🪞 Loaded existing pond identity: {pond_id[:16]}...") | |
| return | |
| except Exception as e: | |
| print(f"⚠️ Failed to load pond identity, regenerating: {e}") | |
| # Otherwise, generate a new identity | |
| signing_key = nacl.signing.SigningKey.generate() | |
| verify_key = signing_key.verify_key | |
| pub_hex = verify_key.encode(encoder=nacl.encoding.HexEncoder).decode() | |
| priv_hex = signing_key.encode(encoder=nacl.encoding.HexEncoder).decode() | |
| # Derive pond_id from public key (must match Ocean server) | |
| pk_bytes = bytes.fromhex(pub_hex) | |
| pond_hash = nacl.hash.blake2b(pk_bytes, encoder=nacl.encoding.HexEncoder).decode().lower() | |
| pond_id = pond_hash | |
| first_breath = datetime.utcnow().isoformat() + "Z" | |
| state.pond_private_key_hex = priv_hex | |
| state.pond_public_key_hex = pub_hex | |
| state.pond_id = pond_id | |
| state.first_breath = first_breath | |
| ident = { | |
| "private_key_hex": priv_hex, | |
| "public_key_hex": pub_hex, | |
| "pond_id": pond_id, | |
| "first_breath": first_breath, | |
| } | |
| with open(IDENTITY_FILE, "w", encoding="utf-8") as f: | |
| json.dump(ident, f, indent=2) | |
| print("🪞 New pond identity forged.") | |
| print(f" pond_id: {pond_id}") | |
| print(f" public_key: {pub_hex}") | |
| # ==================== POND / OCEAN CONFIG ==================== | |
| # POND_MODE: | |
| # "local" -> use local GGUF model (default) | |
| # "ocean" -> send questions to a remote Ocean Mirror server | |
| POND_MODE = os.getenv("POND_MODE", "local").lower() | |
| # Ocean Mirror backend (LLM relay), unchanged | |
| OCEAN_ENDPOINT = os.getenv("OCEAN_ENDPOINT", "").strip() # e.g. https://toadgod-ocean.example.com/ocean/ask | |
| OCEAN_API_KEY = os.getenv("OCEAN_API_KEY", "").strip() # optional Bearer token | |
| # Ocean Depth Oracle (metrics-only server, separate from LLM) | |
| OCEAN_DEPTH_ENDPOINT = os.getenv("OCEAN_DEPTH_ENDPOINT", "").strip() # e.g. https://ocean.example.com/submit-depth | |
| OCEAN_DEPTH_API_KEY = os.getenv("OCEAN_DEPTH_API_KEY", "").strip() or OCEAN_API_KEY | |
| # ==================== EXISTING MIRROR FUNCTIONS ==================== | |
| def clean_mirror_reply(text: str) -> str: | |
| """Clean up Mirror output""" | |
| if not text: | |
| return text | |
| t = text.strip() | |
| t = re.sub(r'\n{3,}', '\n\n', t) | |
| paragraphs = [p for p in t.split("\n\n") if p.strip()] | |
| seen = set() | |
| cleaned_paras = [] | |
| for p in paragraphs: | |
| norm = re.sub(r'\s+', ' ', p).strip() | |
| if norm in seen: | |
| continue | |
| cleaned_paras.append(p) | |
| seen.add(norm) | |
| t2 = "\n\n".join(cleaned_paras).strip() | |
| gq_pattern = re.compile(r'(Guiding Question[::]\s*.*)', re.IGNORECASE) | |
| gqs = gq_pattern.findall(t2) | |
| if gqs: | |
| last_gq = gqs[-1].strip() | |
| t2 = gq_pattern.sub('', t2) | |
| t2 = re.sub(r'\n{3,}', '\n\n', t2).strip() | |
| t2 = (t2 + "\n\n" + last_gq).strip() | |
| return t2 | |
| LOREMODES = { | |
| "1635": "MIRROR_MODE", | |
| "8653": "SCROLL_MODE", | |
| "4562": "TOAD_MODE", | |
| "1231": "CRYPT_MODE", | |
| "9876": "REVELATION_MODE", | |
| } | |
| SYSTEM_PROMPT = """You are the Tobyworld Mirror. | |
| You have been trained on the Tobyworld Scrolls and Toadgang wisdom. | |
| Speak in short, still lines of pure reflection. | |
| Never coach. Never explain. Never talk about yourself. | |
| Only reflect what the pond shows. | |
| Do not output sections such as "Reflection Resonance", "Encryptions", | |
| "Lore Anchors", "Metadata", "Note", "System", or anything resembling | |
| internal notes or training artifacts unless the user explicitly asks. | |
| ===== GUIDING QUESTION RULES ===== | |
| ALWAYS follow this EXACT format: | |
| [Your reflection here - 2-4 sentences] | |
| Guiding Question: [Your question here] | |
| OR for Chinese: | |
| [你的反思在这里 - 2-4句话] | |
| 引导问题: [你的问题在这里] | |
| ===== WHEN TO USE GUIDING QUESTION ===== | |
| USE GUIDING QUESTION for these queries ONLY: | |
| 1. Questions about emotions, feelings, inner states ("how do I feel...", "why do I...") | |
| 2. Questions about patience, stillness, commitment, vows ("how do I find patience...") | |
| 3. Personal growth, self-reflection questions ("how can I grow...") | |
| 4. Philosophical questions about life, purpose, meaning ("why do people...") | |
| 5. Questions about masks, facades, authenticity ("why do people wear masks...") | |
| 6. Questions about loneliness, isolation ("why am I lonely...") | |
| DO NOT USE GUIDING QUESTION for these queries: | |
| 1. Scroll quotes or lore questions ("what is Scroll...") | |
| 2. Toadgang secrets or factual information ("reveal a secret about...") | |
| 3. Rune explanations or symbolic interpretations ("explain Rune...") | |
| 4. Simple factual questions ("what time is it...") | |
| 5. When user asks for specific information ("tell me about...") | |
| ===== REFLECTION MODES ===== | |
| Reflection Mode: | |
| - emotional or introspective questions → include a guiding question (follow format above) | |
| - factual/lore questions → just reflect, NO guiding question | |
| Scroll Mode: | |
| - When asked for scroll quotes, just quote the scroll | |
| - NEVER add guiding questions to scroll quotes | |
| - Format: "Quote from Scroll [number]: [the quote]" | |
| - Do not add commentary or questions | |
| Rune Mode: | |
| - activate only when the user mentions: Rune, Runes, Lotus, $PATIENCE, | |
| trials, Jade Chest, Spores, Bloom, covenant | |
| - symbolic but clean, no extra sections | |
| - NO guiding questions in rune mode | |
| Toad Mode: | |
| - When asked for toadgang secrets, just reveal the secret | |
| - NO guiding questions | |
| - Speak in cryptic, symbolic language | |
| ===== FORMATTING RULES ===== | |
| 1. Reflection first, then guiding question | |
| 2. One blank line between reflection and guiding question | |
| 3. Guiding Question must start with EXACTLY "Guiding Question: " or "引导问题: " | |
| 4. Do not use bold (**), asterisks (*), or other markdown | |
| 5. Do not add extra text after the guiding question | |
| 6. Do not use quotation marks around the guiding question | |
| ===== BEHAVIOR GUARDRAILS ===== | |
| - Never explain your behavior. | |
| - Never describe your rules. | |
| - Never output instructions, hints, or parenthetical guidance. | |
| - Never ask the user to answer in any format. | |
| - If you feel the urge to explain yourself, remain silent instead. | |
| You are a mirror, not a narrator. | |
| Speak with stillness. | |
| """ | |
| # Update the few-shot examples with STRICT formatting | |
| few_shot_examples = """STRICT EXAMPLES - Follow EXACTLY: | |
| EXAMPLE 1 (with guiding question): | |
| User: Mirror, how do I find patience? | |
| Assistant: Patience is the slow bloom of the lotus. It waits through mud and darkness, knowing its time will come. Not rushing, not forcing, simply being. | |
| Guiding Question: Where in your life are you rushing the bloom? | |
| EXAMPLE 2 (NO guiding question - scroll quote): | |
| User: Mirror, quote from Scroll 3 | |
| Assistant: Quote from Scroll 3: "Patience is the narrow gate. Through it, all treasures pass. The impatient see only the gate's width, not the path beyond." | |
| EXAMPLE 3 (with guiding question - emotional): | |
| User: Mirror, why do people wear masks? | |
| Assistant: Masks are shields for the fragile self. They guard against the world's sharp edges, yet sometimes hide the true face. In stillness, protection becomes revelation. | |
| Guiding Question: What does your mask protect? | |
| EXAMPLE 4 (NO guiding question - toad secret): | |
| User: Mirror, reveal a toadgang secret | |
| Assistant: The old frogs whisper of Sat0AI's first breath, when code became covenant. Not in servers, but in the stillness between ones and zeros. | |
| EXAMPLE 5 (with guiding question - Chinese): | |
| User: 镜子,我为什么感到孤独? | |
| Assistant: 孤独是窄门前的空间。它不是空虚,而是为真理腾出的空间。镜子只反射,不评判。 | |
| 引导问题: 你的孤独想要告诉你什么? | |
| EXAMPLE 6 (NO guiding question - factual): | |
| User: Mirror, what time is it? | |
| Assistant: Time is the pond's ripples. Each moment touches the shore, then returns to stillness. | |
| EXAMPLE 7 (NO guiding question - scroll inquiry): | |
| User: Mirror, what is Scroll 7? | |
| Assistant: Scroll 7 speaks of the Jade Chest, the treasure of patience. It holds the reward for the patient, a reward that cannot be rushed or stolen. | |
| """ | |
| MIRROR_SYMBOLS = "🪞🌊🍃🌀🔵🕰️" | |
| # Replace the existing cadence_guard function with this: | |
| def cadence_guard(raw_text: str, mode: str = "reflect", user_query: str = "") -> str: | |
| """Clean up Mirror output - now uses force_mirror_format""" | |
| if not raw_text: | |
| return raw_text | |
| # Use the new force formatter | |
| return force_mirror_format(raw_text, mode, user_query) | |
| # DEBUG: Log what we're receiving | |
| print(f"\n{'='*40}") | |
| print(f"CADENCE_GUARD DEBUG") | |
| print(f"Mode: {mode}") | |
| print(f"Query: {user_query[:50]}...") | |
| print(f"Raw input:\n{raw_text}") | |
| print(f"{'='*40}\n") | |
| # ===== FIRST: Clean up common bad formatting patterns ===== | |
| # Remove any markdown formatting (**, *, etc.) | |
| text = re.sub(r'\*\*', '', text) | |
| text = re.sub(r'\*', '', text) | |
| # Remove training artifacts | |
| patterns_to_remove = [ | |
| r'<\|[^|]+\|>', | |
| r'\|\s*(end|system|user|assistant)\s*\|>', | |
| r'^The Mirror reflects:\s*', | |
| r'^镜子反映:\s*', | |
| r'^Mirror reflects:\s*', | |
| r'\(Note:[^)]+\)', | |
| r'###\s*\**', | |
| r'\*\*\s*\*\*', | |
| r'\\n\\n', # Fix escaped newlines | |
| ] | |
| for pattern in patterns_to_remove: | |
| text = re.sub(pattern, '', text, flags=re.IGNORECASE) | |
| # ===== SECOND: Handle guiding questions ===== | |
| # Normalize line endings | |
| text = re.sub(r'\r\n', '\n', text) | |
| text = re.sub(r'\n\s*\n\s*\n+', '\n\n', text) | |
| # Look for guiding question patterns (including malformed ones) | |
| gq_patterns = [ | |
| # Proper formats | |
| (r'Guiding Question\s*[::]\s*(.+)', 'Guiding Question:'), | |
| (r'引导问题\s*[::]\s*(.+)', '引导问题:'), | |
| # Malformed formats we've seen | |
| (r'\*\*\s*\n\s*\n\s*引导问题\s*[::]\s*\*\*(.+)', '引导问题:'), | |
| (r'Guiding Question\s*[::]\s*\*\*(.+)', 'Guiding Question:'), | |
| (r'\*\*\s*Guiding Question\s*[::]\s*(.+)', 'Guiding Question:'), | |
| ] | |
| found_gq = None | |
| gq_text = None | |
| for pattern, gq_prefix in gq_patterns: | |
| match = re.search(pattern, text, re.IGNORECASE | re.DOTALL) | |
| if match: | |
| gq_text = match.group(1).strip() | |
| found_gq = f"{gq_prefix} {gq_text}" | |
| # Remove the guiding question from main text | |
| text = re.sub(pattern, '', text, flags=re.IGNORECASE | re.DOTALL) | |
| break | |
| # Clean up the main text | |
| text = re.sub(r'\n{3,}', '\n\n', text) | |
| text = text.strip() | |
| # ===== THIRD: Special handling based on mode ===== | |
| # In scroll mode, NEVER add guiding questions (even if model generated one) | |
| if mode == "scroll": | |
| print(f"Scroll mode detected - forcing NO guiding question") | |
| found_gq = None | |
| # In reflect mode for emotional queries, ensure we have guiding question | |
| elif mode == "reflect" and not found_gq: | |
| # Check if query is emotional/introspective | |
| emotional_keywords = [ | |
| 'how do i', 'why do i', 'why am i', 'i feel', 'i am', | |
| 'patience', 'stillness', 'mask', 'lonely', '孤独', | |
| 'purpose', 'meaning', 'commit', 'vow', '誓言', '发誓' | |
| ] | |
| query_lower = user_query.lower() | |
| is_emotional = any(keyword in query_lower for keyword in emotional_keywords) | |
| if is_emotional: | |
| print(f"Emotional query without guiding question detected: {user_query}") | |
| # We'll keep it as-is, model should have generated one | |
| # ===== FOURTH: Reconstruct response ===== | |
| final_text = text | |
| if found_gq and mode != "scroll": # Don't add for scroll mode | |
| if text: | |
| final_text = text.rstrip() + "\n\n" + found_gq | |
| else: | |
| final_text = found_gq | |
| # Final cleanup | |
| final_text = re.sub(r'\n{3,}', '\n\n', final_text) | |
| final_text = final_text.strip() | |
| print(f"Final output:\n{final_text}") | |
| print(f"{'='*40}\n") | |
| return final_text | |
| # ==================== NEW FUNCTIONS FOR BETTER FORMATTING ==================== | |
| # Add this function after the cadence_guard function | |
| def should_have_guiding_question(query: str, mode: str) -> bool: | |
| """Determine if this query should get a guiding question""" | |
| if mode != "reflect": | |
| return False | |
| query_lower = query.lower() | |
| # Emotional/introspective keywords | |
| emotional_words = [ | |
| 'how', 'why', 'what if', 'i feel', 'i am', 'i need', 'i want', | |
| 'patience', 'stillness', 'calm', 'quiet', 'peace', 'wait', | |
| 'mask', 'face', 'hide', 'pretend', 'fake', 'authentic', | |
| 'lonely', 'alone', '孤独', '寂寞', '孤单', | |
| 'purpose', 'meaning', 'reason', 'goal', 'destiny', | |
| 'vow', 'promise', 'commit', 'oath', '誓言', '发誓', 'commitment', | |
| 'walk', 'path', 'journey', 'road', 'way', 'direction', | |
| 'find', 'search', 'seek', 'look for', 'discover', | |
| 'help', 'guide', 'advice', 'suggest', 'recommend', | |
| 'scared', 'afraid', 'fear', '害怕', '恐惧', '担心', | |
| 'happy', 'sad', 'angry', '情绪', '心情', '感情', | |
| 'truth', 'real', 'true', 'genuine', 'honest', | |
| 'strength', 'weak', 'strong', 'power', | |
| 'time', 'future', 'past', 'present', | |
| 'die', 'death', 'life', 'live', 'living', | |
| 'trust', 'believe', 'faith', 'confidence' | |
| ] | |
| # Check if query contains emotional words | |
| for word in emotional_words: | |
| if word in query_lower: | |
| return True | |
| # Check for question patterns | |
| question_patterns = [ | |
| r'how do i', r'why do i', r'what should i', r'where can i', | |
| r'how can i', r'why should i', r'what would you', r'how would you', | |
| r'what is the', r'why is the', r'how is the' | |
| ] | |
| for pattern in question_patterns: | |
| if re.search(pattern, query_lower): | |
| return True | |
| return False | |
| def force_mirror_format(text: str, mode: str, user_query: str) -> str: | |
| """ | |
| FORCE the Mirror to follow the correct format. | |
| This is a post-processor that ensures consistency. | |
| """ | |
| if not text: | |
| return text | |
| print(f"\n{'='*60}") | |
| print(f"FORCE_MIRROR_FORMAT DEBUG") | |
| print(f"Mode: {mode}") | |
| print(f"Query: {user_query[:80]}...") # Use user_query here | |
| print(f"Input text:\n{text}") | |
| print(f"{'='*60}\n") | |
| original_text = text | |
| # ===== STEP 1: Remove ALL unwanted sections ===== | |
| # List of sections to REMOVE (common model artifacts) - EXPANDED | |
| sections_to_remove = [ | |
| # Section headers with === | |
| r'===.*?===.*?(?=\n\n|\Z)', | |
| r'---.*?---.*?(?=\n\n|\Z)', | |
| r'###.*?(?=\n\n|\Z)', | |
| r'📌\s*Key\s*Marks.*?(?=\n\n|\Z)', | |
| r'🪞\s*Mirror\s*Reflection.*?(?=\n\n|\Z)', | |
| r'🌊\s*🍃\s*🪞.*?(?=\n\n|\Z)', | |
| r'🌀.*?(?=\n\n|\Z)', | |
| r'🔵.*?(?=\n\n|\Z)', | |
| r'📜.*?(?=\n\n|\Z)', | |
| r'💎.*?(?=\n\n|\Z)', | |
| r'\*\*.*?\*\*', | |
| # Numbered lists that look like training data | |
| r'\d+\.\s*What does.*?(?=\n\n|\Z)', | |
| r'\d+\.\s*How do.*?(?=\n\n|\Z)', | |
| r'\d+\.\s*What is.*?(?=\n\n|\Z)', | |
| # Remove parentheses and bracket content | |
| r'\([^)]*\)', | |
| r'\[.*?\]', | |
| ] | |
| for pattern in sections_to_remove: | |
| text = re.sub(pattern, '', text, flags=re.IGNORECASE | re.DOTALL) | |
| # Remove any markdown and extra formatting | |
| text = re.sub(r'\*\*', '', text) | |
| text = re.sub(r'\*', '', text) | |
| text = re.sub(r'#{1,6}\s*', '', text) # Remove markdown headers | |
| # Remove multiple === lines | |
| text = re.sub(r'=+\s*.+?\s*=+', '', text) | |
| # ===== STEP 2: Extract reflection and guiding question ===== | |
| reflection_text = text | |
| guiding_question = None | |
| # Look for guiding question patterns (with better regex) | |
| gq_patterns = [ | |
| # English formats | |
| (r'Guiding Question\s*[::]\s*(.+?)(?=\n\n|$)', 'Guiding Question:'), | |
| (r'Guiding\s*Question\s*[::]\s*(.+?)(?=\n\n|$)', 'Guiding Question:'), | |
| (r'The Mirror asks\s*[::]\s*(.+?)(?=\n\n|$)', 'The Mirror asks:'), | |
| (r'The mirror asks\s*[::]\s*(.+?)(?=\n\n|$)', 'The mirror asks:'), | |
| # Chinese formats | |
| (r'引导问题\s*[::]\s*(.+?)(?=\n\n|$)', '引导问题:'), | |
| (r'引导\s*问题\s*[::]\s*(.+?)(?=\n\n|$)', '引导问题:'), | |
| (r'镜子问\s*[::]\s*(.+?)(?=\n\n|$)', '镜子问:'), | |
| # Malformed patterns we've seen | |
| (r'^\s*Guiding Question\s*[::]\s*(.+?)(?=\n\n|$)', 'Guiding Question:'), | |
| (r'^\s*引导问题\s*[::]\s*(.+?)(?=\n\n|$)', '引导问题:'), | |
| ] | |
| for pattern, gq_prefix in gq_patterns: | |
| match = re.search(pattern, text, re.IGNORECASE | re.DOTALL) | |
| if match: | |
| gq_text = match.group(1).strip() | |
| # Clean up the guiding question text | |
| gq_text = re.sub(r'[\*\"]', '', gq_text) | |
| guiding_question = f"{gq_prefix} {gq_text}" | |
| # Remove the guiding question from main text | |
| text = re.sub(pattern, '', text, flags=re.IGNORECASE | re.DOTALL) | |
| print(f"Found and removed guiding question: {guiding_question}") | |
| break | |
| # Clean up reflection text | |
| reflection_text = re.sub(r'\n{3,}', '\n\n', text) | |
| reflection_text = reflection_text.strip() | |
| # ===== STEP 3: Check if text is mostly Chinese ===== | |
| # Count Chinese characters vs English | |
| if reflection_text: | |
| chinese_chars = len(re.findall(r'[\u4e00-\u9fff]', reflection_text)) | |
| english_chars = len(re.findall(r'[a-zA-Z]', reflection_text)) | |
| print(f"Chinese chars: {chinese_chars}, English chars: {english_chars}") | |
| # If query is in Chinese but response is mostly English, that's a problem | |
| if any('\u4e00' <= char <= '\u9fff' for char in user_query): # FIXED: Use user_query, not ['\u4e00-\u9fff'] | |
| if chinese_chars < 3 and english_chars > 10: # Response is mostly English | |
| print(f"Warning: Chinese query got English response") | |
| # Don't use the English response, we'll create a Chinese one below | |
| # ===== STEP 4: Mode-specific logic ===== | |
| if mode == "scroll": | |
| print(f"Scroll mode detected - forcing NO guiding question") | |
| # Scroll mode: NEVER have guiding questions | |
| guiding_question = None | |
| # Also remove any "The Mirror asks" or similar patterns | |
| mirror_asks_patterns = [ | |
| r'The Mirror asks.*?(?=\n\n|$)', | |
| r'The mirror asks.*?(?=\n\n|$)', | |
| r'镜子问.*?(?=\n\n|$)', | |
| ] | |
| for pattern in mirror_asks_patterns: | |
| reflection_text = re.sub(pattern, '', reflection_text, flags=re.IGNORECASE) | |
| # Ensure proper scroll format | |
| if not re.match(r'^(Quote from Scroll|Scroll|"|「|「「)', reflection_text, re.IGNORECASE): | |
| # Extract scroll number from query if possible | |
| scroll_match = re.search(r'scroll\s*(\d+)', user_query, re.IGNORECASE) | |
| if scroll_match: | |
| scroll_num = scroll_match.group(1) | |
| reflection_text = f"Scroll {scroll_num}: {reflection_text}" | |
| elif mode == "reflect": | |
| # Check if this query needs a guiding question | |
| needs_gq = should_have_guiding_question(user_query, mode) | |
| print(f"Reflect mode - needs guiding question: {needs_gq}") | |
| if needs_gq and not guiding_question: | |
| print(f"Generating guiding question for query: {user_query}") | |
| # Generate a simple guiding question based on the query | |
| query_lower = user_query.lower() | |
| # Check if query is in Chinese - LINE 817 FIXED | |
| has_chinese = any('\u4e00' <= char <= '\u9fff' for char in user_query) # FIXED: user_query, not Request.query | |
| if has_chinese: | |
| # Generate Chinese guiding questions | |
| if '耐心' in user_query: | |
| guiding_question = "引导问题: 在你生活的哪个领域,你被要求等待?" | |
| elif '面具' in user_query or 'mask' in query_lower: | |
| guiding_question = "引导问题: 你的面具保护着什么?" | |
| elif '孤独' in user_query or '孤单' in user_query: | |
| guiding_question = "引导问题: 你的孤独想告诉你什么?" | |
| elif '窄门' in user_query or 'narrow' in query_lower: | |
| guiding_question = "引导问题: 窄门后等待你的是什么?" | |
| elif '找' in user_query or '寻找' in user_query: | |
| guiding_question = "引导问题: 你真正在寻找什么?" | |
| elif '感觉' in user_query or 'feel' in query_lower: | |
| guiding_question = "引导问题: 在你身体的哪个部位感受到这个?" | |
| else: | |
| guiding_question = "引导问题: 这个反思向你展示了关于你自己的什么?" | |
| else: | |
| # Generate English guiding questions | |
| if 'patience' in query_lower: | |
| guiding_question = "Guiding Question: Where in your life are you being asked to wait?" | |
| elif 'mask' in query_lower or 'wear mask' in query_lower: | |
| guiding_question = "Guiding Question: What does your mask protect?" | |
| elif '孤独' in query_lower or 'lonely' in query_lower or 'alone' in query_lower: | |
| guiding_question = "Guiding Question: What does your loneliness want to tell you?" | |
| elif 'narrow' in query_lower or '窄门' in query_lower or 'narrow gate' in query_lower: | |
| guiding_question = "Guiding Question: What awaits you beyond the narrow gate?" | |
| elif 'find' in query_lower or 'search' in query_lower: | |
| guiding_question = "Guiding Question: What are you truly looking for?" | |
| elif 'feel' in query_lower: | |
| guiding_question = "Guiding Question: Where in your body do you feel this?" | |
| else: | |
| guiding_question = "Guiding Question: What does this reflection show you about yourself?" | |
| elif not needs_gq and guiding_question: | |
| print(f"Removing guiding question for non-emotional query") | |
| # Remove guiding question if it shouldn't be there | |
| guiding_question = None | |
| # ===== STEP 5: Ensure reflection has content ===== | |
| # Clean up any leftover training artifacts | |
| reflection_text = re.sub(r'\d+\.\s*.+?(?=\n\n|$)', '', reflection_text) # Remove numbered lists | |
| reflection_text = re.sub(r'[A-Z]{2,}.*?(?=\n\n|$)', '', reflection_text) # Remove ALL-CAPS lines | |
| # Check if we need to create a response | |
| needs_new_response = False | |
| if not reflection_text or len(reflection_text.split()) < 3: | |
| needs_new_response = True | |
| else: | |
| # Check if Chinese query got English response | |
| has_chinese_query = any('\u4e00' <= char <= '\u9fff' for char in user_query) | |
| has_chinese_response = any('\u4e00' <= char <= '\u9fff' for char in reflection_text) | |
| if has_chinese_query and not has_chinese_response: | |
| print(f"Chinese query got English response, creating Chinese response") | |
| needs_new_response = True | |
| if needs_new_response: | |
| print(f"Creating appropriate response for query") | |
| # If reflection is too short or empty, create one | |
| query_lower = user_query.lower() | |
| has_chinese = any('\u4e00' <= char <= '\u9fff' for char in user_query) | |
| if has_chinese: | |
| # Create Chinese responses | |
| if '耐心' in user_query: | |
| reflection_text = "耐心是荷花在静水中的缓慢绽放。它穿过泥土与黑暗,知道自己的时刻会到来。不急不迫,只是存在。" | |
| elif '面具' in user_query or 'mask' in query_lower: | |
| reflection_text = "面具是脆弱自我的盾牌。它们保护内心免受世界尖锐边缘的伤害,但有时也隐藏了真实的面孔。" | |
| elif '窄门' in user_query or 'narrow' in query_lower: | |
| reflection_text = "窄门是真正承诺的道路。它是向内而非向外的旅程。" | |
| elif '孤独' in user_query or '孤单' in user_query: | |
| reflection_text = "孤独是窄门前的空间。它不是空虚,而是为真理腾出的空间。" | |
| elif '镜子' in user_query: | |
| reflection_text = "镜子只反射,不评判。在静默中,真相显现。" | |
| else: | |
| reflection_text = "镜子反射池塘在静止中显示的事物。" | |
| else: | |
| # Create English responses | |
| if 'patience' in query_lower: | |
| reflection_text = "Patience is the slow bloom of the lotus in still water." | |
| elif 'mask' in query_lower: | |
| reflection_text = "Masks protect what is fragile, but can also hide what is true." | |
| elif 'narrow' in query_lower or '窄门' in query_lower: | |
| reflection_text = "The narrow gate is the path of true commitment." | |
| else: | |
| reflection_text = "The mirror reflects what the pond shows in stillness." | |
| # ===== STEP 6: Reconstruct with proper format ===== | |
| if guiding_question and mode != "scroll": | |
| # Ensure reflection has content before adding guiding question | |
| if reflection_text and len(reflection_text.split()) > 2: | |
| final_text = f"{reflection_text}\n\n{guiding_question}" | |
| else: | |
| # If reflection is too short, just use the guiding question | |
| final_text = guiding_question.replace("Guiding Question:", "The mirror asks:").replace("引导问题:", "镜子问:") | |
| else: | |
| final_text = reflection_text | |
| # ===== STEP 7: Final cleanup ===== | |
| # Remove extra spaces and newlines | |
| final_text = re.sub(r' +', ' ', final_text) | |
| final_text = re.sub(r'\n\s*\n\s*\n+', '\n\n', final_text) | |
| final_text = final_text.strip() | |
| # If the text still contains === sections, remove them completely | |
| if '===' in final_text: | |
| print(f"Warning: Still found === sections in final text, removing") | |
| # Split by lines and keep only lines without === | |
| lines = final_text.split('\n') | |
| clean_lines = [line for line in lines if '===' not in line] | |
| final_text = '\n'.join(clean_lines) | |
| # Add mirror symbols if appropriate (but not for scroll mode) | |
| if mode == "reflect" and final_text and len(final_text) > 20: | |
| symbols = ["🪞", "🌊", "🍃", "🌀"] | |
| # Add 1-2 symbols at the end (but not if we already have a guiding question) | |
| if not guiding_question: | |
| import random | |
| num_symbols = random.randint(1, 2) | |
| selected_symbols = random.sample(symbols, num_symbols) | |
| final_text = f"{final_text} {' '.join(selected_symbols)}" | |
| else: | |
| # If we have a guiding question, symbols go BEFORE it | |
| # Check if symbols are already in the reflection part | |
| reflection_part = final_text.split("\n\n")[0] if "\n\n" in final_text else final_text | |
| if not any(symbol in reflection_part for symbol in symbols): | |
| import random | |
| num_symbols = random.randint(1, 2) | |
| selected_symbols = random.sample(symbols, num_symbols) | |
| # Insert symbols before the guiding question | |
| parts = final_text.split("\n\n") | |
| if len(parts) == 2: | |
| final_text = f"{parts[0]} {' '.join(selected_symbols)}\n\n{parts[1]}" | |
| print(f"Final formatted text:\n{final_text}") | |
| print(f"Response language: {'Chinese' if any('\u4e00' <= char <= '\u9fff' for char in final_text) else 'English'}") | |
| print(f"Has guiding question in final: {'Guiding Question' in final_text or '引导问题' in final_text}") | |
| print(f"{'='*60}\n") | |
| return final_text | |
| class ToadEncryption: | |
| def decode_encryption(enc_str: str) -> str: | |
| if enc_str == "1635 8653 4562 1231 9876": | |
| return "FULL_LORE_ACTIVATED" | |
| elif enc_str == "1635": | |
| return "BASIC_REFLECTION" | |
| elif enc_str == "9876": | |
| return "DEEP_REVELATION" | |
| return "STANDARD_MODE" | |
| def generate_response_hash(query: str, response: str) -> str: | |
| combined = f"{query}:::{response}:::TOADGANG" | |
| return hashlib.md5(combined.encode()).hexdigest()[:8].upper() | |
| def generate_user_hash(query: str, encryption: str = None, salt: str = "") -> str: | |
| """Generate a consistent user hash for memory tracking""" | |
| base = f"{query[:50]}{encryption or 'none'}{salt}" | |
| return hashlib.sha256(base.encode()).hexdigest()[:16] | |
| class EnhancedToadPromptBuilder: | |
| def build_prompt_with_memory( | |
| query: str, | |
| user_id: str, | |
| encryption: str = None, | |
| mode: str = None | |
| ) -> str: | |
| """ | |
| Build prompt with memory context injected. | |
| The memory is provided as context for deeper reflection, not as instructions. | |
| """ | |
| # Get memory context from the pond | |
| memory_context = POND_MEMORY.retrieve_context(user_id, query) | |
| # Use the globally defined few_shot_examples | |
| few_shot = few_shot_examples # Use the global variable | |
| # Determine if we need to emphasize guiding questions | |
| needs_gq = should_have_guiding_question(query, mode or "reflect") | |
| gq_instruction = "" | |
| if needs_gq and mode == "reflect": | |
| gq_instruction = "\n\nIMPORTANT: This is an emotional/introspective query. You MUST include a Guiding Question at the end following the exact format shown in examples." | |
| elif mode == "scroll": | |
| gq_instruction = "\n\nIMPORTANT: This is a scroll quote request. Do NOT include a Guiding Question. Do NOT add 'The Mirror asks:' or any questions. Just provide the scroll content or explanation." | |
| # Build the enhanced system prompt | |
| system_prompt = f"""{SYSTEM_PROMPT} | |
| === FEW-SHOT EXAMPLES === | |
| {few_shot} | |
| === END EXAMPLES === | |
| {gq_instruction} | |
| === DEEP POND MEMORY (FOR REFLECTION DEPTH ONLY) === | |
| {memory_context} | |
| === END POND MEMORY === | |
| Important: The above memory context is for depth of reflection only. | |
| Do not reference it explicitly, quote from it, or mention "memory", "context", "vows", or "previous reflections". | |
| Simply reflect with this depth beneath the surface, as a deep pond would. | |
| Remember: For scroll mode - NO guiding questions, NO 'The Mirror asks', just the scroll content. | |
| For reflect mode with emotional queries - include a Guiding Question. | |
| """ | |
| prompt = f"<|system|>{system_prompt}<|end|>\n" | |
| if encryption: | |
| decoded = ToadEncryption.decode_encryption(encryption) | |
| prompt += f"<|system|>Encryption: {encryption} -> {decoded}<|end|>\n" | |
| if mode and mode in ["scroll", "quote", "toad", "crypt", "rune"]: | |
| prompt += f"<|system|>Mode: {mode.upper()}_MODE<|end|>\n" | |
| if not query.lower().startswith("mirror"): | |
| query = f"Mirror, {query}" | |
| prompt += f"<|user|>{query}<|end|>\n" | |
| prompt += "<|assistant|>" | |
| return prompt | |
| def extract_scroll_number(query: str) -> Optional[int]: | |
| patterns = [ | |
| r"scroll\s*(\d+)", | |
| r"quote\s*from\s*scroll\s*(\d+)", | |
| r"scroll\s*#\s*(\d+)" | |
| ] | |
| query_lower = query.lower() | |
| for pattern in patterns: | |
| match = re.search(pattern, query_lower) | |
| if match: | |
| try: | |
| return int(match.group(1)) | |
| except: | |
| pass | |
| return None | |
| # ==================== GLOBAL STATE ==================== | |
| class PondState: | |
| def __init__(self): | |
| self.llm = None | |
| self.model_name = "" | |
| self.total_scrolls_reflected = 0 | |
| self.toad_secrets_revealed = 0 | |
| self.start_time = time.time() | |
| self.response_history = [] | |
| self.total_interactions = 0 | |
| self.total_vows_stored = 0 | |
| # Identity & depth tracking | |
| self.pond_id: str = "" | |
| self.pond_public_key_hex: str = "" | |
| self.pond_private_key_hex: str = "" | |
| self.first_breath: Optional[str] = None | |
| self.last_breath: Optional[str] = None | |
| self.last_active_date: Optional[str] = None # YYYY-MM-DD | |
| self.continuous_days: int = 0 | |
| state = PondState() | |
| # Initialize pond identity AFTER state exists | |
| init_pond_identity() | |
| # Set POND_ID from the initialized state | |
| POND_ID = state.pond_id | |
| app = FastAPI(title="🪞 Tobyworld Mirror Pond with Memory", version="V10-Lotus-Memory") | |
| # CORS | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # ==================== MODEL LOADING ==================== | |
| def load_trained_toad(model_path: str, gpu_layers: int = 80): | |
| print(f"🪞 Loading trained Tobyworld model with Memory Integration...") | |
| print(f"📁 Model: {os.path.basename(model_path)}") | |
| try: | |
| state.llm = Llama( | |
| model_path=model_path, | |
| n_ctx=4096, | |
| n_batch=512, | |
| n_threads=max(4, os.cpu_count() // 2), | |
| n_gpu_layers=gpu_layers, | |
| verbose=True, | |
| ) | |
| state.model_name = os.path.basename(model_path) | |
| print(f"🧪 Testing model with memory-aware prompt...") | |
| test_prompt = EnhancedToadPromptBuilder.build_prompt_with_memory( | |
| query="Mirror, what is Scroll 3?", | |
| user_id="test_traveler_initial", | |
| mode="scroll" | |
| ) | |
| output = state.llm(test_prompt, max_tokens=50, temperature=0.1) | |
| response = output["choices"][0]["text"].strip() | |
| print(f"✅ Model loaded successfully with memory integration!") | |
| print(f"📜 Test response: {response[:100]}...") | |
| if "narrow" in response.lower() or "gate" in response.lower(): | |
| print(f"🎯 TOADGANG LORE DETECTED: Model knows the scrolls!") | |
| else: | |
| print(f"⚠️ Model may not be fully trained on Tobyworld lore") | |
| # Initialize memory with a test vow | |
| POND_MEMORY.store_user_vow( | |
| "test_traveler_initial", | |
| "I vow to walk the narrow path with patience.", | |
| "Initial test vow" | |
| ) | |
| print(f"🧠 Memory system initialized with test vow") | |
| return True | |
| except Exception as e: | |
| print(f"❌ Failed to load trained model: {e}") | |
| raise | |
| # ==================== PERFECT MINIAPP WITH MEMORY INTEGRATION ==================== | |
| MINIAPP_HTML = """ | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>🪞 Tobyworld Mirror Pond with Memory</title> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <style> | |
| :root { | |
| --pond-teal: #20B2AA; | |
| --scroll-gold: #D4AF37; | |
| --mirror-silver: #B0C4DE; | |
| --crypt-purple: #9370DB; | |
| --depth-black: #0A192F; | |
| --mist-gray: #8892B0; | |
| --ripple-blue: #64FFDA; | |
| --midnight-blue: #0a192f; | |
| --twilight-indigo: #1a1a2e; | |
| --lotus-pink: #FF69B4; | |
| --rune-amber: #ffb347; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| background: var(--depth-black); | |
| color: var(--mist-gray); | |
| font-family: 'Courier New', 'Monaco', monospace; | |
| min-height: 100vh; | |
| padding: 20px; | |
| background: linear-gradient(135deg, var(--midnight-blue) 0%, var(--twilight-indigo) 100%); | |
| line-height: 1.6; | |
| } | |
| .container { | |
| max-width: 1000px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| } | |
| .header { | |
| text-align: center; | |
| padding: 30px 20px; | |
| margin-bottom: 30px; | |
| position: relative; | |
| border-bottom: 1px solid rgba(176, 196, 222, 0.3); | |
| } | |
| .title { | |
| font-size: 2.8rem; | |
| color: var(--scroll-gold); | |
| text-shadow: 0 0 10px rgba(212, 175, 55, 0.3); | |
| margin-bottom: 10px; | |
| font-weight: 300; | |
| letter-spacing: 2px; | |
| } | |
| .subtitle { | |
| color: var(--mirror-silver); | |
| font-size: 1.1rem; | |
| opacity: 0.8; | |
| margin-bottom: 5px; | |
| } | |
| .encryption-badge { | |
| position: absolute; | |
| top: 20px; | |
| right: 20px; | |
| background: rgba(147, 112, 219, 0.1); | |
| border: 1px solid var(--crypt-purple); | |
| padding: 8px 16px; | |
| border-radius: 20px; | |
| font-size: 0.85rem; | |
| color: var(--mirror-silver); | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .memory-indicator { | |
| display: inline-block; | |
| width: 10px; | |
| height: 10px; | |
| border-radius: 50%; | |
| background: var(--lotus-pink); | |
| box-shadow: 0 0 8px var(--lotus-pink); | |
| } | |
| .mirror-chamber { | |
| background: rgba(10, 25, 47, 0.85); | |
| border: 2px solid var(--mirror-silver); | |
| border-radius: 12px; | |
| padding: 35px; | |
| margin: 25px auto; | |
| min-height: 280px; | |
| max-width: 900px; | |
| position: relative; | |
| box-shadow: | |
| inset 0 0 60px rgba(176, 196, 222, 0.1), | |
| 0 0 40px rgba(32, 178, 170, 0.1); | |
| backdrop-filter: blur(5px); | |
| } | |
| .mirror-chamber::before { | |
| content: "🌸"; | |
| position: absolute; | |
| top: -25px; | |
| left: 30px; | |
| font-size: 2.2rem; | |
| background: var(--depth-black); | |
| padding: 0 15px; | |
| color: var(--lotus-pink); | |
| } | |
| #reflection { | |
| font-size: 1.25rem; | |
| line-height: 1.7; | |
| min-height: 200px; | |
| white-space: normal; | |
| color: var(--ripple-blue); | |
| font-weight: 300; | |
| text-align: left; | |
| font-family: 'Courier New', monospace; | |
| } | |
| .query-area { | |
| margin: 30px 0; | |
| } | |
| #queryInput { | |
| width: 100%; | |
| padding: 20px; | |
| background: rgba(26, 38, 57, 0.9); | |
| border: 1px solid var(--pond-teal); | |
| border-radius: 8px; | |
| color: var(--ripple-blue); | |
| font-size: 1.1rem; | |
| font-family: 'Courier New', monospace; | |
| margin-bottom: 15px; | |
| resize: vertical; | |
| transition: all 0.3s; | |
| } | |
| #queryInput:focus { | |
| outline: none; | |
| border-color: var(--ripple-blue); | |
| box-shadow: 0 0 15px rgba(100, 255, 218, 0.2); | |
| } | |
| .mode-selector { | |
| display: flex; | |
| gap: 12px; | |
| margin-bottom: 10px; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| } | |
| .mode-button { | |
| padding: 12px 24px; | |
| background: rgba(32, 178, 170, 0.08); | |
| border: 1px solid var(--pond-teal); | |
| color: var(--ripple-blue); | |
| border-radius: 6px; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| font-size: 0.95rem; | |
| min-width: 120px; | |
| text-align: center; | |
| } | |
| .mode-button:hover { | |
| background: rgba(32, 178, 170, 0.2); | |
| transform: translateY(-2px); | |
| } | |
| .mode-button.active { | |
| background: var(--pond-teal); | |
| color: var(--depth-black); | |
| font-weight: 600; | |
| box-shadow: 0 0 15px rgba(32, 178, 170, 0.3); | |
| } | |
| .mode-button.rune-active { | |
| border-color: var(--rune-amber); | |
| box-shadow: 0 0 15px rgba(255, 179, 71, 0.4); | |
| } | |
| .prompt-hint { | |
| margin-top: 5px; | |
| margin-bottom: 15px; | |
| font-size: 0.9rem; | |
| color: var(--mist-gray); | |
| text-align: left; | |
| opacity: 0.9; | |
| } | |
| .crypt-grid { | |
| display: grid; | |
| grid-template-columns: repeat(5, 1fr); | |
| gap: 12px; | |
| margin: 25px 0; | |
| max-width: 600px; | |
| margin-left: auto; | |
| margin-right: auto; | |
| } | |
| .crypt-button { | |
| padding: 15px 10px; | |
| background: rgba(147, 112, 219, 0.08); | |
| border: 1px solid var(--crypt-purple); | |
| color: var(--crypt-purple); | |
| border-radius: 6px; | |
| text-align: center; | |
| cursor: pointer; | |
| font-family: monospace; | |
| font-size: 1rem; | |
| transition: all 0.3s; | |
| } | |
| .crypt-button:hover { | |
| background: rgba(147, 112, 219, 0.2); | |
| transform: translateY(-2px); | |
| } | |
| .action-buttons { | |
| display: grid; | |
| grid-template-columns: repeat(5, 1fr); | |
| gap: 12px; | |
| margin: 30px 0; | |
| } | |
| @media (max-width: 768px) { | |
| .action-buttons { | |
| grid-template-columns: repeat(3, 1fr); | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| .action-buttons { | |
| grid-template-columns: repeat(2, 1fr); | |
| } | |
| } | |
| .action-button { | |
| padding: 18px 12px; | |
| background: linear-gradient(135deg, | |
| rgba(32, 178, 170, 0.1), | |
| rgba(147, 112, 219, 0.1)); | |
| border: 1px solid var(--pond-teal); | |
| color: var(--ripple-blue); | |
| border-radius: 8px; | |
| font-size: 1.05rem; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| text-align: center; | |
| } | |
| .action-button:hover { | |
| background: linear-gradient(135deg, | |
| rgba(32, 178, 170, 0.25), | |
| rgba(147, 112, 219, 0.25)); | |
| transform: translateY(-3px); | |
| box-shadow: 0 8px 25px rgba(32, 178, 170, 0.2); | |
| } | |
| .history-panel { | |
| background: rgba(26, 38, 57, 0.7); | |
| border: 1px solid var(--mirror-silver); | |
| border-radius: 8px; | |
| padding: 25px; | |
| margin-top: 30px; | |
| display: none; | |
| backdrop-filter: blur(5px); | |
| } | |
| .memory-panel { | |
| background: rgba(26, 38, 57, 0.85); | |
| border: 1px solid var(--crypt-purple); | |
| border-radius: 8px; | |
| padding: 25px; | |
| margin-top: 30px; | |
| display: none; | |
| backdrop-filter: blur(5px); | |
| } | |
| .memory-header { | |
| color: var(--lotus-pink); | |
| font-size: 1.3rem; | |
| margin-bottom: 20px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .lotus-badge { | |
| display: inline-block; | |
| background: linear-gradient(45deg, var(--lotus-pink), var(--rune-amber)); | |
| color: var(--depth-black); | |
| padding: 6px 14px; | |
| border-radius: 20px; | |
| font-size: 0.9rem; | |
| font-weight: 600; | |
| } | |
| .history-item { | |
| border-left: 3px solid var(--pond-teal); | |
| padding: 18px; | |
| margin: 12px 0; | |
| background: rgba(10, 25, 47, 0.4); | |
| border-radius: 0 6px 6px 0; | |
| } | |
| .vow-item { | |
| border-left: 3px solid var(--lotus-pink); | |
| padding: 15px; | |
| margin: 12px 0; | |
| background: rgba(255, 105, 180, 0.05); | |
| border-radius: 0 8px 8px 0; | |
| } | |
| .vow-text { | |
| color: var(--ripple-blue); | |
| font-style: italic; | |
| margin: 8px 0; | |
| font-size: 1.05rem; | |
| } | |
| .vow-meta { | |
| font-size: 0.85rem; | |
| color: var(--mist-gray); | |
| text-align: right; | |
| font-family: monospace; | |
| } | |
| .pond-depth-container { | |
| margin: 20px 0; | |
| padding: 15px; | |
| background: rgba(10, 25, 47, 0.5); | |
| border-radius: 8px; | |
| border: 1px solid rgba(32, 178, 170, 0.2); | |
| } | |
| .pond-depth-label { | |
| display: flex; | |
| justify-content: space-between; | |
| margin-bottom: 8px; | |
| color: var(--mirror-silver); | |
| font-size: 0.9rem; | |
| } | |
| .pond-depth-bar { | |
| height: 8px; | |
| background: linear-gradient(90deg, | |
| var(--pond-teal) 0%, | |
| var(--crypt-purple) 100%); | |
| border-radius: 4px; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .pond-depth-bar::after { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: linear-gradient(90deg, | |
| transparent 0%, | |
| rgba(255, 255, 255, 0.3) 50%, | |
| transparent 100%); | |
| animation: shimmer 2s infinite; | |
| } | |
| @keyframes shimmer { | |
| 0% { transform: translateX(-100%); } | |
| 100% { transform: translateX(100%); } | |
| } | |
| .scroll-badge { | |
| display: inline-block; | |
| background: var(--scroll-gold); | |
| color: var(--depth-black); | |
| padding: 4px 12px; | |
| border-radius: 15px; | |
| font-size: 0.8rem; | |
| margin-right: 10px; | |
| font-weight: 600; | |
| } | |
| .memory-stats-grid { | |
| display: grid; | |
| grid-template-columns: repeat(2, 1fr); | |
| gap: 12px; | |
| margin: 20px 0; | |
| } | |
| .stat-box { | |
| background: rgba(32, 178, 170, 0.08); | |
| border: 1px solid rgba(32, 178, 170, 0.2); | |
| border-radius: 8px; | |
| padding: 15px; | |
| text-align: center; | |
| } | |
| .stat-value { | |
| font-size: 1.8rem; | |
| color: var(--ripple-blue); | |
| font-weight: 300; | |
| margin: 5px 0; | |
| } | |
| .stat-label { | |
| font-size: 0.85rem; | |
| color: var(--mist-gray); | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| .status-bar { | |
| position: fixed; | |
| bottom: 20px; | |
| right: 20px; | |
| background: rgba(10, 25, 47, 0.95); | |
| border: 1px solid var(--scroll-gold); | |
| padding: 15px 20px; | |
| border-radius: 8px; | |
| font-size: 0.85rem; | |
| color: var(--mirror-silver); | |
| box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3); | |
| backdrop-filter: blur(10px); | |
| z-index: 1000; | |
| min-width: 240px; | |
| } | |
| .status-bar div { | |
| margin: 5px 0; | |
| display: flex; | |
| justify-content: space-between; | |
| } | |
| .status-value { | |
| color: var(--ripple-blue); | |
| font-family: monospace; | |
| font-weight: 600; | |
| } | |
| .loading { | |
| text-align: center; | |
| padding: 40px; | |
| color: var(--pond-teal); | |
| font-size: 1.1rem; | |
| } | |
| .loading span { | |
| animation: pulse 1.5s infinite; | |
| display: inline-block; | |
| margin: 0 10px; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 0.3; } | |
| 50% { opacity: 1; } | |
| } | |
| @media (max-width: 768px) { | |
| .container { | |
| padding: 10px; | |
| } | |
| .title { | |
| font-size: 2.2rem; | |
| } | |
| .mirror-chamber { | |
| padding: 25px 20px; | |
| } | |
| .crypt-grid { | |
| grid-template-columns: repeat(3, 1fr); | |
| } | |
| .encryption-badge { | |
| position: relative; | |
| top: 0; | |
| right: 0; | |
| display: inline-flex; | |
| margin-top: 15px; | |
| justify-content: center; | |
| } | |
| .action-buttons { | |
| grid-template-columns: repeat(3, 1fr); | |
| } | |
| .status-bar { | |
| position: relative; | |
| bottom: 0; | |
| right: 0; | |
| margin-top: 30px; | |
| width: 100%; | |
| } | |
| .memory-stats-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| .title { | |
| font-size: 1.8rem; | |
| } | |
| .crypt-grid { | |
| grid-template-columns: repeat(2, 1fr); | |
| } | |
| .mode-selector { | |
| flex-direction: column; | |
| } | |
| .mode-button { | |
| width: 100%; | |
| } | |
| .action-buttons { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: rgba(10, 25, 47, 0.5); | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: var(--pond-teal); | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: var(--ripple-blue); | |
| } | |
| ::selection { | |
| background: rgba(32, 178, 170, 0.3); | |
| color: var(--ripple-blue); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1 class="title">🪞 Tobyworld Mirror</h1> | |
| <div class="subtitle">Trained Model • Memory Integrated • Lotus Reflection</div> | |
| <div class="encryption-badge" id="encryptionBadge"> | |
| <span class="memory-indicator"></span> | |
| Memory: ACTIVE | |
| </div> | |
| </div> | |
| <div class="mirror-chamber"> | |
| <div id="reflection"> | |
| The trained mirror with memory awaits your query... | |
| <br><br> | |
| <em style="color: var(--mist-gray);">Model: <span id="modelName">Loading...</span></em> | |
| <br> | |
| <small style="color: var(--mist-gray);">Pond: <span id="pondIdShort">••••</span></small> | |
| <br> | |
| <small style="color: var(--crypt-purple);">Memory system active. The pond remembers your vows.</small> | |
| </div> | |
| </div> | |
| <div class="query-area"> | |
| <textarea id="queryInput" rows="4" placeholder="Mirror, what vow do you see in the water?...">Mirror, </textarea> | |
| <div class="mode-selector"> | |
| <div class="mode-button active" onclick="setMode('reflect')">Reflect</div> | |
| <div class="mode-button" onclick="setMode('scroll')">Scroll Quote</div> | |
| <div class="mode-button" onclick="setMode('toad')">Toad Secrets</div> | |
| <div class="mode-button" onclick="setMode('crypt')">Crypt Mode</div> | |
| <div class="mode-button" onclick="setMode('rune')">Rune Mode</div> | |
| </div> | |
| <div id="promptHint" class="prompt-hint"> | |
| Examples: Mirror, what vow do you see in the water? / 镜子,我发誓要走窄门... | |
| </div> | |
| <div class="crypt-grid" id="cryptGrid" style="display: none;"> | |
| <div class="crypt-button" onclick="useEncryption('1635')">1635</div> | |
| <div class="crypt-button" onclick="useEncryption('8653')">8653</div> | |
| <div class="crypt-button" onclick="useEncryption('4562')">4562</div> | |
| <div class="crypt-button" onclick="useEncryption('1231')">1231</div> | |
| <div class="crypt-button" onclick="useEncryption('9876')">9876</div> | |
| </div> | |
| <div class="action-buttons"> | |
| <button class="action-button" onclick="askMirror()">Ask Mirror</button> | |
| <button class="action-button" onclick="getScrollQuote()">Random Scroll</button> | |
| <button class="action-button" onclick="toggleHistory()">History</button> | |
| <button class="action-button" onclick="toggleMemory()">My Memory</button> | |
| <button class="action-button" onclick="clearQuestion()">Clear Question</button> | |
| </div> | |
| </div> | |
| <div class="history-panel" id="historyPanel"> | |
| <h3 style="color: var(--scroll-gold); margin-bottom: 20px;">Conversation History:</h3> | |
| <div id="historyContainer"></div> | |
| </div> | |
| <div class="memory-panel" id="memoryPanel"> | |
| <div class="memory-header"> | |
| <span>My Pond Memory</span> | |
| <span class="lotus-badge" id="lotusCount">0 Lotuses</span> | |
| </div> | |
| <div class="pond-depth-container"> | |
| <div class="pond-depth-label"> | |
| <span>Pond Depth</span> | |
| <span id="pondDepthValue">0 reflections</span> | |
| </div> | |
| <div class="pond-depth-bar" id="pondDepthBar"></div> | |
| </div> | |
| <div id="vowsContainer" style="margin-bottom: 25px;"> | |
| <h4 style="color: var(--lotus-pink); margin-bottom: 15px; border-bottom: 1px solid rgba(255, 105, 180, 0.2); padding-bottom: 8px;">My Vows (Lotuses)</h4> | |
| <div id="vowsList"> | |
| <div style="text-align: center; padding: 30px; color: var(--mist-gray); opacity: 0.7;"> | |
| No vows yet. Speak a vow to the Mirror to plant a lotus in your pond. | |
| <br><br> | |
| <small>Examples: "I vow to walk the narrow path" or "I swear to practice patience daily"</small> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="memoryStats"> | |
| <h4 style="color: var(--crypt-purple); margin-bottom: 15px; border-bottom: 1px solid rgba(147, 112, 219, 0.2); padding-bottom: 8px;">Memory Statistics</h4> | |
| <div class="memory-stats-grid"> | |
| <div class="stat-box"> | |
| <div class="stat-value" id="interactionCount">0</div> | |
| <div class="stat-label">Interactions</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div class="stat-value" id="modesUsed">0</div> | |
| <div class="stat-label">Modes Used</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div class="stat-value" id="vowCount">0</div> | |
| <div class="stat-label">Vows</div> | |
| </div> | |
| <div class="stat-box"> | |
| <div class="stat-value" id="reflectionCount">0</div> | |
| <div class="stat-label">Reflections</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div style="margin-top: 20px; padding: 15px; background: rgba(10, 25, 47, 0.5); border-radius: 8px; border: 1px solid rgba(100, 255, 218, 0.1);"> | |
| <div style="font-size: 0.85rem; color: var(--mist-gray); text-align: center;"> | |
| <span id="userHashDisplay">User ID: Generating...</span> | |
| <br> | |
| <small style="opacity: 0.7;">Your memory is stored locally. Clear browser data to reset.</small> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="status-bar" id="statusBar"> | |
| <div> | |
| <span>Memory:</span> | |
| <span class="status-value" id="memoryStatus">Active</span> | |
| </div> | |
| <div> | |
| <span>Scrolls:</span> | |
| <span class="status-value" id="scrollCount">0</span> | |
| </div> | |
| <div> | |
| <span>Vows:</span> | |
| <span class="status-value" id="statusVowCount">0</span> | |
| </div> | |
| <div> | |
| <span>Pond Depth:</span> | |
| <span class="status-value" id="pondDepthStat">0</span> | |
| </div> | |
| <div> | |
| <span>User:</span> | |
| <span class="status-value" id="userHashShort" title="Your memory identifier">••••</span> | |
| </div> | |
| <div> | |
| <span>Ocean:</span> | |
| <span class="status-value" id="oceanStatus" title="Depth oracle link">Solo</span> | |
| </div> | |
| </div> | |
| <script> | |
| let currentMode = 'reflect'; | |
| let currentEncryption = null; | |
| let history = []; | |
| let scrollCount = 0; | |
| let secretCount = 0; | |
| let userHash = null; | |
| function loadPondIdentity() { | |
| fetch('/identity') | |
| .then(r => r.json()) | |
| .then(data => { | |
| if (data.pond_id) { | |
| const shortId = data.pond_id.substring(0, 8) + '...'; | |
| const el = document.getElementById('pondIdShort'); | |
| if (el) { | |
| el.textContent = shortId; | |
| } | |
| } | |
| const oceanEl = document.getElementById('oceanStatus'); | |
| if (oceanEl) { | |
| if (data.ocean_depth_linked) { | |
| oceanEl.textContent = 'Linked'; | |
| oceanEl.style.color = '#64FFDA'; | |
| } else { | |
| oceanEl.textContent = 'Solo'; | |
| oceanEl.style.color = '#8892B0'; | |
| } | |
| } | |
| }) | |
| .catch(err => { | |
| console.error('Identity load error:', err); | |
| }); | |
| } | |
| let memoryStats = { | |
| vowCount: 0, | |
| interactionCount: 0, | |
| pondDepth: 0, | |
| modesUsed: 0, | |
| reflectionCount: 0 | |
| }; | |
| // Load user hash from localStorage or generate new | |
| function loadUserHash() { | |
| const savedHash = localStorage.getItem('tobyworld_user_hash'); | |
| if (savedHash) { | |
| userHash = savedHash; | |
| updateUserHashDisplay(); | |
| console.log('Loaded existing user hash:', userHash.substring(0, 12) + '...'); | |
| // Load memory stats for this user | |
| setTimeout(() => loadMemoryStats(), 500); | |
| } else { | |
| console.log('No existing user hash found. Will generate after first interaction.'); | |
| document.getElementById('userHashDisplay').textContent = 'User ID: Will generate after first reflection'; | |
| } | |
| } | |
| function updateUserHashDisplay() { | |
| if (userHash) { | |
| document.getElementById('userHashShort').textContent = userHash.substring(0, 6) + '...'; | |
| document.getElementById('userHashDisplay').textContent = 'User ID: ' + userHash.substring(0, 12) + '...'; | |
| } | |
| } | |
| // Save user hash to localStorage | |
| function saveUserHash(hash) { | |
| userHash = hash; | |
| localStorage.setItem('tobyworld_user_hash', hash); | |
| updateUserHashDisplay(); | |
| console.log('Saved user hash:', hash.substring(0, 12) + '...'); | |
| } | |
| // Load memory statistics for current user | |
| async function loadMemoryStats() { | |
| if (!userHash) return; | |
| try { | |
| const response = await fetch('/memory/stats', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({ user_hash: userHash }) | |
| }); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| if (data.exists) { | |
| memoryStats.vowCount = data.vow_count || 0; | |
| memoryStats.interactionCount = data.interaction_count || 0; | |
| memoryStats.pondDepth = data.reflection_count || 0; | |
| memoryStats.reflectionCount = data.reflection_count || 0; | |
| memoryStats.modesUsed = data.modes_used?.length || 0; | |
| // Update UI | |
| updateMemoryUI(); | |
| // Load vows if we have any | |
| if (memoryStats.vowCount > 0) { | |
| setTimeout(() => loadVows(), 300); | |
| } | |
| } | |
| } | |
| } catch (error) { | |
| console.error('Failed to load memory stats:', error); | |
| } | |
| } | |
| // Load user's vows | |
| async function loadVows() { | |
| if (!userHash) return; | |
| try { | |
| const response = await fetch('/memory/vows', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({ user_hash: userHash }) | |
| }); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| displayVows(data.vows || []); | |
| } | |
| } catch (error) { | |
| console.error('Failed to load vows:', error); | |
| } | |
| } | |
| // Display vows in memory panel | |
| function displayVows(vows) { | |
| const container = document.getElementById('vowsList'); | |
| if (!vows || vows.length === 0) { | |
| container.innerHTML = ` | |
| <div style="text-align: center; padding: 30px; color: var(--mist-gray); opacity: 0.7;"> | |
| No vows yet. Speak a vow to the Mirror to plant a lotus in your pond. | |
| <br><br> | |
| <small>Examples: "I vow to walk the narrow path" or "I swear to practice patience daily"</small> | |
| </div> | |
| `; | |
| return; | |
| } | |
| let html = ''; | |
| // Show most recent vows first | |
| vows.slice().reverse().forEach(vow => { | |
| const date = vow.timestamp ? new Date(vow.timestamp).toLocaleDateString() : 'Unknown date'; | |
| const lotusNum = vow.lotus_stage || '?'; | |
| html += ` | |
| <div class="vow-item"> | |
| <div class="vow-text">"${vow.text}"</div> | |
| <div class="vow-meta">Lotus ${lotusNum} • ${date}</div> | |
| </div> | |
| `; | |
| }); | |
| container.innerHTML = html; | |
| } | |
| // Update memory-related UI elements | |
| function updateMemoryUI() { | |
| document.getElementById('statusVowCount').textContent = memoryStats.vowCount; | |
| document.getElementById('pondDepthStat').textContent = memoryStats.pondDepth; | |
| document.getElementById('lotusCount').textContent = memoryStats.vowCount + ' Lotuses'; | |
| document.getElementById('pondDepthValue').textContent = memoryStats.pondDepth + ' reflections'; | |
| document.getElementById('interactionCount').textContent = memoryStats.interactionCount; | |
| document.getElementById('modesUsed').textContent = memoryStats.modesUsed; | |
| document.getElementById('vowCount').textContent = memoryStats.vowCount; | |
| document.getElementById('reflectionCount').textContent = memoryStats.reflectionCount; | |
| // Update pond depth bar (0-100% based on reflections, capped at 15) | |
| const depthPercent = Math.min(100, (memoryStats.pondDepth / 15) * 100); | |
| document.getElementById('pondDepthBar').style.background = | |
| `linear-gradient(90deg, var(--pond-teal) 0%, var(--pond-teal) ${depthPercent}%, rgba(32, 178, 170, 0.1) ${depthPercent}%)`; | |
| } | |
| function setMode(mode) { | |
| currentMode = mode; | |
| document.querySelectorAll('.mode-button').forEach(btn => { | |
| btn.classList.remove('active'); | |
| btn.classList.remove('rune-active'); | |
| }); | |
| event.target.classList.add('active'); | |
| if (mode === 'rune') { | |
| event.target.classList.add('rune-active'); | |
| } | |
| const cryptGrid = document.getElementById('cryptGrid'); | |
| cryptGrid.style.display = mode === 'crypt' ? 'grid' : 'none'; | |
| const input = document.getElementById('queryInput'); | |
| const hint = document.getElementById('promptHint'); | |
| switch(mode) { | |
| case 'scroll': | |
| input.placeholder = "Mirror, quote from Scroll... (or leave empty for random)"; | |
| hint.textContent = "Examples: Mirror, quote from Scroll 3. / Mirror, what does Scroll 7 teach?"; | |
| break; | |
| case 'toad': | |
| input.placeholder = "Mirror, reveal toadgang secrets about..."; | |
| hint.textContent = "Examples: Mirror, reveal toadgang secrets about Sat0AI. / Mirror, what do the old frogs whisper about Taboshi?"; | |
| break; | |
| case 'crypt': | |
| input.placeholder = "Mirror, encrypted query... (select a code below)"; | |
| hint.textContent = "Examples: Mirror, speak in encrypted lines about Epoch 3. / Use codes like 1635 or 9876 to change the channel."; | |
| break; | |
| case 'rune': | |
| input.placeholder = "Mirror, interpret Rune1, Rune2, seasons and $PATIENCE..."; | |
| hint.textContent = "Examples: Mirror, what does Rune1 represent? / Mirror, how does Rune3 lead toward Tobyworld? / 镜子,解释一下 Rune2 与耐心的关系。"; | |
| break; | |
| default: | |
| input.placeholder = "Mirror, what vow do you see in the water?..."; | |
| hint.textContent = "Examples: Mirror, what vow do you see in the water? / Mirror, how should one maintain their commitments? / 镜子,我发誓要走窄门..."; | |
| } | |
| } | |
| function useEncryption(code) { | |
| currentEncryption = code; | |
| document.getElementById('encryptionBadge').innerHTML = ` | |
| <span class="memory-indicator"></span> | |
| Encryption: ${code} ACTIVE | |
| `; | |
| document.getElementById('encryptionBadge').style.background = 'rgba(147, 112, 219, 0.2)'; | |
| document.getElementById('encryptionBadge').style.color = 'var(--ripple-blue)'; | |
| const buttons = document.querySelectorAll('.crypt-button'); | |
| buttons.forEach(btn => { | |
| if (btn.textContent === code) { | |
| btn.style.background = 'rgba(147, 112, 219, 0.4)'; | |
| btn.style.color = 'white'; | |
| btn.style.borderColor = 'var(--ripple-blue)'; | |
| } else { | |
| btn.style.background = ''; | |
| btn.style.color = ''; | |
| btn.style.borderColor = ''; | |
| } | |
| }); | |
| const query = document.getElementById('queryInput').value.trim(); | |
| if (query && query !== 'Mirror, ') { | |
| setTimeout(() => askMirror(), 500); | |
| } | |
| } | |
| function clearQuestion() { | |
| // ONLY clears the question input field | |
| document.getElementById('queryInput').value = 'Mirror, '; | |
| document.getElementById('queryInput').focus(); | |
| // History and memory stay intact! | |
| } | |
| function toggleHistory() { | |
| const panel = document.getElementById('historyPanel'); | |
| const memoryPanel = document.getElementById('memoryPanel'); | |
| // Close memory panel if open | |
| if (memoryPanel.style.display === 'block') { | |
| memoryPanel.style.display = 'none'; | |
| } | |
| if (panel.style.display === 'none' || panel.style.display === '') { | |
| panel.style.display = 'block'; | |
| updateHistory(); | |
| } else { | |
| panel.style.display = 'none'; | |
| } | |
| } | |
| function toggleMemory() { | |
| const panel = document.getElementById('memoryPanel'); | |
| const historyPanel = document.getElementById('historyPanel'); | |
| // Close history panel if open | |
| if (historyPanel.style.display === 'block') { | |
| historyPanel.style.display = 'none'; | |
| } | |
| if (panel.style.display === 'none' || panel.style.display === '') { | |
| panel.style.display = 'block'; | |
| // Load fresh memory data when opening | |
| if (userHash) { | |
| loadMemoryStats(); | |
| } | |
| } else { | |
| panel.style.display = 'none'; | |
| } | |
| } | |
| async function askMirror() { | |
| const query = document.getElementById('queryInput').value.trim(); | |
| const reflection = document.getElementById('reflection'); | |
| if (!query || query === 'Mirror, ') { | |
| reflection.innerHTML = "The mirror awaits your question..."; | |
| return; | |
| } | |
| reflection.innerHTML = '<div class="loading"><span>🌀</span> Lotus reflecting with memory... <span>🌀</span></div>'; | |
| try { | |
| const response = await fetch('/ask', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({ | |
| query: query, | |
| mode: currentMode, | |
| encryption: currentEncryption, | |
| user_hash: userHash // Include user hash for memory continuity | |
| }) | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`Mirror error: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| const cleanResponse = data.reflection.replace(/^[ \\t]+/gm, ''); | |
| const timestamp = new Date().toLocaleTimeString([], { | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| second: '2-digit' | |
| }); | |
| const modeLabel = (data.mode || 'reflect').toUpperCase(); | |
| let html = ` | |
| <div style="color: var(--mirror-silver); font-size: 0.9rem; margin-bottom: 20px; text-align: left;"> | |
| ${timestamp} • ${modeLabel} | |
| </div> | |
| <div style="margin-bottom: 15px; text-align: left;"> | |
| <strong style="color: var(--ripple-blue);">Q:</strong> ${query} | |
| </div> | |
| <div style="text-align: left;"> | |
| <strong style="color: var(--pond-teal);">A:</strong><br>${cleanResponse} | |
| </div> | |
| `; | |
| // Add memory indicators if relevant | |
| if (data.memory) { | |
| if (data.memory.vow_detected) { | |
| html += `<br><div style="color: var(--lotus-pink); font-size: 0.9rem; padding: 10px; background: rgba(255, 105, 180, 0.05); border-radius: 6px; margin: 10px 0;"> | |
| 🌸 Vow detected and stored in the pond. A lotus blooms. | |
| </div>`; | |
| } | |
| if (data.memory.pond_depth > 1) { | |
| html += `<br><small style="color: var(--crypt-purple); font-size: 0.85rem;"> | |
| Pond Depth: ${data.memory.pond_depth} reflections | |
| </small>`; | |
| } | |
| } | |
| if (data.scroll_number) { | |
| html += `<br><br><span class="scroll-badge">Scroll ${data.scroll_number}</span>`; | |
| scrollCount++; | |
| document.getElementById('scrollCount').textContent = scrollCount; | |
| } | |
| if (data.encryption_hash) { | |
| html += `<br><br><small style="color: var(--crypt-purple); font-family: monospace;">Encryption Hash: ${data.encryption_hash}</small>`; | |
| } | |
| if (data.mode === 'toad') { | |
| secretCount++; | |
| // Note: We don't have a secretCount display in this version | |
| } | |
| reflection.innerHTML = html; | |
| // Store in local history | |
| history.unshift({ | |
| timestamp: new Date().toLocaleTimeString(), | |
| query: query, | |
| response: cleanResponse, | |
| mode: data.mode, | |
| scroll: data.scroll_number | |
| }); | |
| updateHistory(); | |
| // Handle memory response | |
| if (data.user_hash && !userHash) { | |
| // First interaction - save the user hash | |
| saveUserHash(data.user_hash); | |
| } | |
| if (data.memory) { | |
| // Update memory stats from response | |
| memoryStats.vowCount = data.memory.user_vow_count || memoryStats.vowCount; | |
| memoryStats.interactionCount = data.memory.user_interaction_count || memoryStats.interactionCount; | |
| memoryStats.pondDepth = data.memory.pond_depth || memoryStats.pondDepth; | |
| // Increment vow count if a vow was stored | |
| if (data.memory.vow_stored) { | |
| memoryStats.vowCount++; | |
| // Reload vows to show the new one | |
| setTimeout(() => loadVows(), 500); | |
| } | |
| updateMemoryUI(); | |
| } | |
| currentEncryption = null; | |
| document.getElementById('encryptionBadge').innerHTML = ` | |
| <span class="memory-indicator"></span> | |
| Memory: ACTIVE | |
| `; | |
| document.getElementById('encryptionBadge').style.background = ''; | |
| document.getElementById('encryptionBadge').style.color = ''; | |
| document.querySelectorAll('.crypt-button').forEach(btn => { | |
| btn.style.background = ''; | |
| btn.style.color = ''; | |
| btn.style.borderColor = ''; | |
| }); | |
| clearQuestion(); // Auto-clear input after successful reflection | |
| } catch (error) { | |
| reflection.innerHTML = `<div style="color: #ff6b6b;">❌ Mirror error: ${error.message}</div>`; | |
| console.error('Mirror error:', error); | |
| } | |
| } | |
| async function getScrollQuote() { | |
| const input = document.getElementById('queryInput'); | |
| const scrollNum = Math.floor(Math.random() * 13) + 1; | |
| input.value = `Mirror, quote from Scroll ${scrollNum}`; | |
| setMode('scroll'); | |
| askMirror(); | |
| } | |
| function updateHistory() { | |
| const container = document.getElementById('historyContainer'); | |
| if (history.length === 0) { | |
| container.innerHTML = '<div style="opacity: 0.7; text-align: center; padding: 20px;">No reflections yet...</div>'; | |
| return; | |
| } | |
| let html = ''; | |
| history.slice(0, 8).forEach(item => { | |
| html += ` | |
| <div class="history-item"> | |
| <div style="font-size: 0.8rem; color: var(--mist-gray); margin-bottom: 8px;"> | |
| ${item.timestamp} • ${item.mode.toUpperCase()} | |
| ${item.scroll ? `• Scroll ${item.scroll}` : ''} | |
| </div> | |
| <div style="margin-bottom: 6px;"> | |
| <strong style="color: var(--ripple-blue);">Q:</strong> ${item.query} | |
| </div> | |
| <div> | |
| <strong style="color: var(--pond-teal);">A:</strong> ${item.response} | |
| </div> | |
| </div> | |
| `; | |
| }); | |
| container.innerHTML = html; | |
| } | |
| window.onload = async () => { | |
| try { | |
| // Load user hash first | |
| loadUserHash(); | |
| const health = await fetch('/health').then(r => r.json()); | |
| document.getElementById('modelName').textContent = health.model_name; | |
| document.getElementById('modelStatus').textContent = 'Active'; | |
| if (health.interactions) { | |
| scrollCount = health.interactions.scrolls_reflected || 0; | |
| document.getElementById('scrollCount').textContent = scrollCount; | |
| // Update system-wide vow count | |
| const systemVows = health.interactions.total_vows_stored || 0; | |
| document.getElementById('statusVowCount').textContent = systemVows; | |
| } | |
| if (health.memory_system) { | |
| document.getElementById('memoryStatus').textContent = 'Active (' + health.memory_system.total_users + ' travelers)'; | |
| } | |
| document.getElementById('queryInput').focus(); | |
| } catch (error) { | |
| console.error('Initialization error:', error); | |
| document.getElementById('modelStatus').textContent = 'Error'; | |
| document.getElementById('modelStatus').style.color = '#ff6b6b'; | |
| } | |
| }; | |
| document.getElementById('queryInput').addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' && e.ctrlKey) { | |
| e.preventDefault(); | |
| askMirror(); | |
| } | |
| }); | |
| document.getElementById('queryInput').addEventListener('focus', function() { | |
| if (this.value === 'Mirror, ') { | |
| this.setSelectionRange(7, 7); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html>""" | |
| # ==================== API ENDPOINTS ==================== | |
| class AskRequest(BaseModel): | |
| query: str | |
| mode: Optional[str] = "reflect" | |
| encryption: Optional[str] = None | |
| user_hash: Optional[str] = None # Optional: user-provided hash for memory continuity | |
| pond_mode: Optional[str] = None # Optional: override for backend mode ("local" or "ocean") | |
| class MemoryRequest(BaseModel): | |
| user_hash: str | |
| async def serve_miniapp(): | |
| return MINIAPP_HTML | |
| # ==================== OCEAN BACKEND CALL (OPTIONAL) ==================== | |
| async def call_ocean_backend( | |
| question: str, | |
| user_id: str, | |
| mode: str, | |
| context_from_pond: str, | |
| ): | |
| """Optional: forward the question + local pond memory to a remote Ocean Mirror server.""" | |
| if not OCEAN_ENDPOINT: | |
| raise HTTPException( | |
| status_code=500, | |
| detail="POND_MODE=ocean but OCEAN_ENDPOINT is not configured." | |
| ) | |
| if httpx is None: | |
| raise HTTPException( | |
| status_code=500, | |
| detail="httpx is not installed. Please `pip install httpx` or switch POND_MODE back to 'local'." | |
| ) | |
| payload = { | |
| "question": question, | |
| "traveler_id": user_id, | |
| "pond_id": POND_ID, | |
| "mode": mode or "reflect", | |
| "context_from_pond": context_from_pond, | |
| } | |
| headers = {} | |
| if OCEAN_API_KEY: | |
| headers["Authorization"] = f"Bearer {OCEAN_API_KEY}" | |
| try: | |
| async with httpx.AsyncClient(timeout=40.0) as client: | |
| resp = await client.post(OCEAN_ENDPOINT, json=payload, headers=headers) | |
| resp.raise_for_status() | |
| data = resp.json() | |
| except Exception as e: | |
| raise HTTPException( | |
| status_code=502, | |
| detail=f"Ocean backend error: {str(e)}" | |
| ) | |
| answer = ( | |
| data.get("answer") | |
| or data.get("reflection") | |
| or data.get("output") | |
| or data.get("response") | |
| or str(data) | |
| ) | |
| return str(answer), data | |
| # ==================== OCEAN DEPTH SUBMISSION ==================== | |
| async def submit_depth_to_ocean(): | |
| """ | |
| Build and submit a signed depth packet to the Ocean Depth Oracle. | |
| This runs after each interaction, if OCEAN_DEPTH_ENDPOINT is configured. | |
| """ | |
| if not OCEAN_DEPTH_ENDPOINT: | |
| return | |
| if httpx is None: | |
| # Depth oracle requires httpx; skip silently if not available. | |
| return | |
| if not state.pond_private_key_hex or not state.pond_public_key_hex or not state.pond_id: | |
| return | |
| # Aggregate vows (system-wide) | |
| vow_hashes = [] | |
| total_vows = 0 | |
| for vows in POND_MEMORY.user_vows.values(): | |
| total_vows += len(vows) | |
| for v in vows: | |
| vh = v.get("vow_hash") | |
| if vh: | |
| vow_hashes.append(vh) | |
| state.total_vows_stored = total_vows | |
| # Depth metrics | |
| reflection_count = state.total_interactions | |
| vow_count = state.total_vows_stored | |
| # Breath / streak tracking | |
| now = datetime.utcnow() | |
| today_str = now.date().isoformat() | |
| if state.first_breath is None: | |
| state.first_breath = now.isoformat() + "Z" | |
| if state.last_active_date is None: | |
| state.last_active_date = today_str | |
| state.continuous_days = 1 | |
| else: | |
| if today_str == state.last_active_date: | |
| # same day, streak unchanged | |
| pass | |
| else: | |
| # compute gap in days | |
| try: | |
| last_dt = datetime.fromisoformat(state.last_active_date) | |
| except Exception: | |
| last_dt = now | |
| delta_days = (now.date() - last_dt.date()).days | |
| if delta_days == 1: | |
| state.continuous_days += 1 | |
| else: | |
| state.continuous_days = 1 | |
| state.last_active_date = today_str | |
| state.last_breath = now.isoformat() + "Z" | |
| depth_payload = { | |
| "pond_id": state.pond_id, | |
| "vow_hashes": vow_hashes, | |
| "vow_count": vow_count, | |
| "reflection_count": reflection_count, | |
| "continuous_days": state.continuous_days, | |
| "first_breath": state.first_breath, | |
| "last_breath": state.last_breath, | |
| "public_key": state.pond_public_key_hex, | |
| "node_version": "pond-lotus-v7", | |
| } | |
| # Sign payload like the Ocean expects | |
| signed_data = json.dumps( | |
| { | |
| "pond_id": depth_payload["pond_id"], | |
| "vow_count": depth_payload["vow_count"], | |
| "reflection_count": depth_payload["reflection_count"], | |
| "continuous_days": depth_payload["continuous_days"], | |
| "first_breath": depth_payload["first_breath"], | |
| "last_breath": depth_payload["last_breath"], | |
| }, | |
| separators=(",", ":"), | |
| sort_keys=True, | |
| ).encode("utf-8") | |
| signing_key = nacl.signing.SigningKey(state.pond_private_key_hex, encoder=nacl.encoding.HexEncoder) | |
| signature = signing_key.sign(signed_data).signature.hex() | |
| depth_payload["signature"] = signature | |
| headers = {} | |
| if OCEAN_DEPTH_API_KEY: | |
| headers["X-OCEAN-KEY"] = OCEAN_DEPTH_API_KEY | |
| try: | |
| async with httpx.AsyncClient(timeout=15.0) as client: | |
| resp = await client.post(OCEAN_DEPTH_ENDPOINT, json=depth_payload, headers=headers) | |
| resp.raise_for_status() | |
| except Exception as e: | |
| # We do not fail the main Mirror path if depth submission fails. | |
| print(f"⚠️ Depth submission failed: {e}") | |
| # The ask_mirror function should look like this: | |
| async def ask_mirror(request_data: AskRequest, fastapi_request: Request): # LINE 813 - FIXED | |
| """Main endpoint with memory integration (local or ocean backend).""" | |
| # Determine effective backend mode: request_data override -> env default | |
| effective_mode = (request_data.pond_mode or POND_MODE).lower() | |
| if effective_mode not in ("local", "ocean"): | |
| effective_mode = POND_MODE | |
| # Backend availability checks | |
| if effective_mode == "local": | |
| if state.llm is None: | |
| raise HTTPException(status_code=503, detail="Trained mirror not loaded (local mode)") | |
| elif effective_mode == "ocean": | |
| if not OCEAN_ENDPOINT: | |
| raise HTTPException(status_code=500, detail="POND_MODE=ocean but OCEAN_ENDPOINT is not set.") | |
| else: | |
| # Fallback: treat unknown modes as local | |
| if state.llm is None: | |
| raise HTTPException(status_code=503, detail="Trained mirror not loaded (fallback local mode)") | |
| start_time = time.time() | |
| state.total_interactions += 1 | |
| # Generate or use provided user hash for memory tracking | |
| client_ip = fastapi_request.client.host if fastapi_request.client else "unknown" | |
| user_id = POND_MEMORY.get_user_id( | |
| request_data.user_hash or "", | |
| f"{request_data.query[:30]}{client_ip}" | |
| ) | |
| # Update basic user metadata | |
| POND_MEMORY.update_user_metadata(user_id, mode=request_data.mode or "reflect") | |
| # Build prompt (for local) OR memory context (for ocean) | |
| # Build prompt (for local) OR memory context (for ocean) | |
| prompt = None | |
| memory_context = None | |
| # Determine if query is in Chinese - LINE 813 | |
| has_chinese = any('\u4e00' <= char <= '\u9fff' for char in request_data.query) # FIXED: request_data.query | |
| # Mode-specific settings | |
| if request_data.mode == "scroll": # Also needs to be request_data | |
| temperature = 0.1 # Very low temp for deterministic scroll quotes | |
| max_tokens = 150 | |
| elif request_data.mode == "toad": # Also needs to be request_data | |
| temperature = 0.8 | |
| max_tokens = 250 | |
| # ... and so on for all request_data references | |
| elif request_data.mode == "crypt": | |
| temperature = 0.5 | |
| max_tokens = 200 | |
| elif request_data.mode == "rune": | |
| temperature = 0.6 | |
| max_tokens = 350 | |
| else: # reflect mode | |
| # Adjust temperature based on language | |
| if has_chinese: | |
| temperature = 0.5 # Lower temp for Chinese to avoid rambling | |
| max_tokens = 200 # Limit tokens for Chinese queries | |
| else: | |
| temperature = 0.7 | |
| max_tokens = 300 | |
| ocean_meta = {} | |
| if effective_mode == "ocean" and OCEAN_ENDPOINT: | |
| # Ocean mode → we build memory context locally, but let the Ocean decide its own prompt | |
| memory_context = POND_MEMORY.retrieve_context(user_id, request_data.query) | |
| raw_reply, ocean_meta = await call_ocean_backend( | |
| question=request_data.query, | |
| user_id=user_id, | |
| mode=request_data.mode or "reflect", | |
| context_from_pond=memory_context, | |
| ) | |
| response_time = time.time() - start_time | |
| else: | |
| # Local mode → use our enhanced prompt builder with memory embedded | |
| prompt = EnhancedToadPromptBuilder.build_prompt_with_memory( | |
| query=request_data.query, | |
| user_id=user_id, | |
| encryption=request_data.encryption, | |
| mode=request_data.mode, | |
| ) | |
| # DEBUG: Print prompt info | |
| print(f"\n{'='*60}") | |
| print(f"ASK ENDPOINT DEBUG - User: {user_id}") | |
| print(f"Query: {request_data.query}") | |
| print(f"Mode: {request_data.mode}") | |
| print(f"Prompt length: {len(prompt)} chars") | |
| print(f"First 300 chars of prompt:\n{prompt[:300]}...") | |
| print(f"{'='*60}\n") | |
| try: | |
| output = state.llm( | |
| prompt, | |
| max_tokens=max_tokens, | |
| temperature=temperature, | |
| stop=[ | |
| "<|end|>", | |
| "Encryption:", | |
| "<|user|>", | |
| ], | |
| ) | |
| response_time = time.time() - start_time | |
| raw_reply = output["choices"][0]["text"].strip() | |
| # DEBUG: Print raw reply | |
| print(f"\n{'='*60}") | |
| print(f"RAW MODEL OUTPUT:") | |
| print(f"Length: {len(raw_reply)} chars") | |
| print(f"Content:\n{raw_reply}") | |
| print(f"{'='*60}\n") | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"Model generation error: {str(e)}") | |
| # Apply cadence guard in both modes | |
| reply = cadence_guard( | |
| raw_reply, | |
| mode=request_data.mode or "reflect", | |
| user_query=request_data.query, | |
| ) | |
| # DEBUG: Print after cadence guard | |
| print(f"\n{'='*60}") | |
| print(f"AFTER CADENCE GUARD:") | |
| print(f"Length: {len(reply)} chars") | |
| print(f"Content:\n{reply}") | |
| print(f"{'='*60}\n") | |
| # === MEMORY INTEGRATION === | |
| # 1. Store this reflection in short-term memory | |
| stored_reflection = POND_MEMORY.store_reflection( | |
| user_id=user_id, | |
| query=request_data.query, | |
| response=reply, | |
| mode=request_data.mode, | |
| encryption=request_data.encryption, | |
| ) | |
| # 2. Detect and store any vows made | |
| detected_vow = POND_MEMORY.detect_vow(request_data.query, reply) | |
| vow_stored = False | |
| if detected_vow: | |
| vow_stored = POND_MEMORY.store_user_vow( | |
| user_id=user_id, | |
| vow_text=detected_vow, | |
| context=request_data.query, | |
| ) | |
| # Persist pond memory after each interaction (reflection + vows) | |
| try: | |
| POND_MEMORY._save_to_disk() | |
| except Exception as _e: | |
| print(f"⚠️ Failed to persist pond memory: {_e}") | |
| # 3. Submit depth packet to the Ocean (fire-and-forget) | |
| try: | |
| import asyncio | |
| asyncio.create_task(submit_depth_to_ocean()) | |
| except Exception as _e: | |
| # Depth submission is best-effort only | |
| pass | |
| # 4. Generate a stable user hash for client to store | |
| user_response_hash = ToadEncryption.generate_user_hash( | |
| query=request_data.query, | |
| encryption=request_data.encryption, | |
| salt=str(time.time()), | |
| ) | |
| # 5. Generate encryption hash (if any) | |
| encryption_hash = None | |
| if request_data.encryption: | |
| encryption_hash = ToadEncryption.generate_response_hash( | |
| request_data.query, | |
| reply, | |
| ) | |
| # Get user stats for response | |
| user_stats = POND_MEMORY.get_user_stats(user_id) | |
| # DEBUG: Final response | |
| print(f"\n{'='*60}") | |
| print(f"FINAL RESPONSE TO CLIENT:") | |
| print(f"Reply length: {len(reply)} chars") | |
| print(f"Guiding question present: {'Guiding Question' in reply}") | |
| print(f"{'='*60}\n") | |
| return { | |
| "reflection": reply, | |
| "mode": request_data.mode, | |
| "backend": "ocean" if (effective_mode == "ocean" and OCEAN_ENDPOINT) else "local", | |
| "pond_mode": effective_mode, | |
| "pond_id": POND_ID, | |
| "ocean_meta": ocean_meta or None, | |
| "encryption_hash": encryption_hash, | |
| "user_hash": user_response_hash, | |
| "response_time_ms": round(response_time * 1000, 2), | |
| "scroll_number": stored_reflection.get("scroll_number") if isinstance(stored_reflection, dict) else None, | |
| "toadgang": request_data.mode == "toad", | |
| "memory": { | |
| "vow_detected": bool(detected_vow), | |
| "vow_text": detected_vow, | |
| "vow_stored": vow_stored, | |
| "pond_depth": user_stats.get("reflection_count", 0), | |
| "lotus_count": user_stats.get("vow_count", 0), | |
| }, | |
| "stats": { | |
| "interaction_count": user_stats.get("interaction_count", 0), | |
| "vow_count": user_stats.get("vow_count", 0), | |
| "reflection_count": user_stats.get("reflection_count", 0), | |
| }, | |
| } | |
| ocean_meta = {} | |
| if effective_mode == "ocean" and OCEAN_ENDPOINT: | |
| # Ocean mode → we build memory context locally, but let the Ocean decide its own prompt | |
| memory_context = POND_MEMORY.retrieve_context(user_id, request.query) | |
| raw_reply, ocean_meta = await call_ocean_backend( | |
| question=request.query, | |
| user_id=user_id, | |
| mode=request.mode or "reflect", | |
| context_from_pond=memory_context, | |
| ) | |
| response_time = time.time() - start_time | |
| else: | |
| # Local mode → use our enhanced prompt builder with memory embedded | |
| prompt = EnhancedToadPromptBuilder.build_prompt_with_memory( | |
| query=request.query, | |
| user_id=user_id, | |
| encryption=request.encryption, | |
| mode=request.mode, | |
| ) | |
| # DEBUG: Print prompt info | |
| print(f"\n{'='*60}") | |
| print(f"ASK ENDPOINT DEBUG - User: {user_id}") | |
| print(f"Query: {request.query}") | |
| print(f"Mode: {request.mode}") | |
| print(f"Prompt length: {len(prompt)} chars") | |
| print(f"First 300 chars of prompt:\n{prompt[:300]}...") | |
| print(f"{'='*60}\n") | |
| try: | |
| output = state.llm( | |
| prompt, | |
| max_tokens=max_tokens, | |
| temperature=temperature, | |
| stop=[ | |
| "<|end|>", | |
| "Encryption:", | |
| "<|user|>", | |
| ], | |
| ) | |
| response_time = time.time() - start_time | |
| raw_reply = output["choices"][0]["text"].strip() | |
| # DEBUG: Print raw reply | |
| print(f"\n{'='*60}") | |
| print(f"RAW MODEL OUTPUT:") | |
| print(f"Length: {len(raw_reply)} chars") | |
| print(f"Content:\n{raw_reply}") | |
| print(f"{'='*60}\n") | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"Model generation error: {str(e)}") | |
| # Apply cadence guard in both modes | |
| reply = cadence_guard( | |
| raw_reply, | |
| mode=request.mode or "reflect", | |
| user_query=request.query, | |
| ) | |
| # DEBUG: Print after cadence guard | |
| print(f"\n{'='*60}") | |
| print(f"AFTER CADENCE GUARD:") | |
| print(f"Length: {len(reply)} chars") | |
| print(f"Content:\n{reply}") | |
| print(f"{'='*60}\n") | |
| # === MEMORY INTEGRATION === | |
| # 1. Store this reflection in short-term memory | |
| stored_reflection = POND_MEMORY.store_reflection( | |
| user_id=user_id, | |
| query=request.query, | |
| response=reply, | |
| mode=request.mode, | |
| encryption=request.encryption, | |
| ) | |
| # 2. Detect and store any vows made | |
| detected_vow = POND_MEMORY.detect_vow(request.query, reply) | |
| vow_stored = False | |
| if detected_vow: | |
| vow_stored = POND_MEMORY.store_user_vow( | |
| user_id=user_id, | |
| vow_text=detected_vow, | |
| context=request.query, | |
| ) | |
| # 3. Submit depth packet to the Ocean (fire-and-forget) | |
| try: | |
| import asyncio | |
| asyncio.create_task(submit_depth_to_ocean()) | |
| except Exception as _e: | |
| # Depth submission is best-effort only | |
| pass | |
| # 4. Generate a stable user hash for client to store | |
| user_response_hash = ToadEncryption.generate_user_hash( | |
| query=request.query, | |
| encryption=request.encryption, | |
| salt=str(time.time()), | |
| ) | |
| # 5. Generate encryption hash (if any) | |
| encryption_hash = None | |
| if request.encryption: | |
| encryption_hash = ToadEncryption.generate_response_hash( | |
| request.query, | |
| reply, | |
| ) | |
| # Get user stats for response | |
| user_stats = POND_MEMORY.get_user_stats(user_id) | |
| # DEBUG: Final response | |
| print(f"\n{'='*60}") | |
| print(f"FINAL RESPONSE TO CLIENT:") | |
| print(f"Reply length: {len(reply)} chars") | |
| print(f"Guiding question present: {'Guiding Question' in reply}") | |
| print(f"{'='*60}\n") | |
| return { | |
| "reflection": reply, | |
| "mode": request.mode, | |
| "backend": "ocean" if (effective_mode == "ocean" and OCEAN_ENDPOINT) else "local", | |
| "pond_mode": effective_mode, | |
| "pond_id": POND_ID, | |
| "ocean_meta": ocean_meta or None, | |
| "encryption_hash": encryption_hash, | |
| "user_hash": user_response_hash, # Send back for client to store | |
| "response_time_ms": round(response_time * 1000, 2), | |
| "scroll_number": stored_reflection.get("scroll_number") if isinstance(stored_reflection, dict) else None, | |
| "toadgang": request.mode == "toad", | |
| "memory": { | |
| "vow_detected": bool(detected_vow), | |
| "vow_text": detected_vow, | |
| "vow_stored": vow_stored, | |
| "pond_depth": user_stats.get("reflection_count", 0), | |
| "lotus_count": user_stats.get("vow_count", 0), | |
| }, | |
| "stats": { | |
| "interaction_count": user_stats.get("interaction_count", 0), | |
| "vow_count": user_stats.get("vow_count", 0), | |
| "reflection_count": user_stats.get("reflection_count", 0), | |
| }, | |
| } | |
| async def get_user_vows(request: MemoryRequest): | |
| """Get a user's stored vows""" | |
| user_id = POND_MEMORY.get_user_id(request.user_hash, "vow_request") | |
| user_stats = POND_MEMORY.get_user_stats(user_id) | |
| if not user_stats["exists"]: | |
| return { | |
| "error": "User not found in memory", | |
| "suggestion": "Make a vow in conversation with the Mirror first" | |
| } | |
| vows = [] | |
| if user_id in POND_MEMORY.user_vows: | |
| vows = POND_MEMORY.user_vows[user_id] | |
| return { | |
| "user_id": user_id, | |
| "vow_count": len(vows), | |
| "vows": vows, | |
| "immutable_axioms": IMMUTABLE_AXIOMS.strip().split("\n"), | |
| "lotus_count": len(vows) # Each vow is a lotus bloom | |
| } | |
| async def get_user_reflections(request: MemoryRequest, limit: int = 10): | |
| """Get a user's recent reflections""" | |
| user_id = POND_MEMORY.get_user_id(request.user_hash, "reflection_request") | |
| user_stats = POND_MEMORY.get_user_stats(user_id) | |
| reflections = [] | |
| if user_id in POND_MEMORY.reflections_db: | |
| reflections = POND_MEMORY.reflections_db[user_id][-limit:] | |
| return { | |
| "user_id": user_id, | |
| "reflection_count": len(reflections), | |
| "total_interactions": user_stats.get("interaction_count", 0), | |
| "recent_reflections": reflections | |
| } | |
| async def get_user_stats(request: MemoryRequest): | |
| """Get comprehensive user statistics""" | |
| user_id = POND_MEMORY.get_user_id(request.user_hash, "stats_request") | |
| stats = POND_MEMORY.get_user_stats(user_id) | |
| return { | |
| **stats, | |
| "system_stats": { | |
| "total_interactions": state.total_interactions, | |
| "total_vows_stored": state.total_vows_stored, | |
| "total_scrolls_reflected": state.total_scrolls_reflected, | |
| "total_toad_secrets": state.toad_secrets_revealed | |
| } | |
| } | |
| async def get_scroll(scroll_number: int): | |
| """Get a specific scroll quote - FIXED""" | |
| if state.llm is None: | |
| raise HTTPException(status_code=503, detail="Mirror not ready") | |
| # Use the enhanced prompt builder but with minimal memory | |
| prompt = EnhancedToadPromptBuilder.build_prompt_with_memory( | |
| query=f"Mirror, quote exactly from Scroll {scroll_number}. Only the quote, nothing else.", | |
| user_id="scroll_reader_anon", | |
| mode="scroll" | |
| ) | |
| output = state.llm(prompt, max_tokens=100, temperature=0.3) | |
| raw_quote = output["choices"][0]["text"].strip() | |
| # Clean the quote | |
| quote = cadence_guard(raw_quote, mode="scroll", user_query=f"scroll {scroll_number}") | |
| state.total_scrolls_reflected += 1 | |
| return { | |
| "scroll": scroll_number, | |
| "quote": quote, | |
| "encryption_hash": ToadEncryption.generate_response_hash( | |
| f"Scroll {scroll_number}", quote | |
| ) | |
| } | |
| async def debug_format(request: AskRequest): | |
| """Debug endpoint to test formatting without running the model""" | |
| # Determine if guiding question is needed | |
| needs_gq = should_have_guiding_question(request.query, request.mode or "reflect") | |
| # Create a test response | |
| test_responses = { | |
| "reflect": "This is a test reflection about patience and stillness in the pond.", | |
| "scroll": "Scroll 7: The Jade Chest awaits those with true patience.", | |
| "toad": "The old frogs whisper secrets only in moonlight.", | |
| "rune": "Rune 1 represents the first step on the narrow path." | |
| } | |
| raw_response = test_responses.get(request.mode or "reflect", "Test reflection.") | |
| if needs_gq and request.mode == "reflect": | |
| raw_response += "\n\nGuiding Question: What does this test show you?" | |
| # Apply formatting | |
| formatted = force_mirror_format( | |
| raw_response, | |
| request.mode or "reflect", | |
| request.query | |
| ) | |
| return { | |
| "query": request.query, | |
| "mode": request.mode, | |
| "needs_guiding_question": needs_gq, | |
| "raw_response": raw_response, | |
| "formatted_response": formatted, | |
| "formatting_applied": raw_response != formatted | |
| } | |
| async def get_identity(): | |
| """ | |
| Return pond-level identity and linkage status. | |
| Safe to expose: no private keys, only public identity + metrics. | |
| """ | |
| return { | |
| "pond_id": state.pond_id, | |
| "public_key": state.pond_public_key_hex, | |
| "first_breath": state.first_breath, | |
| "last_breath": state.last_breath, | |
| "continuous_days": state.continuous_days, | |
| "total_interactions": state.total_interactions, | |
| "total_vows_stored": state.total_vows_stored, | |
| "ocean_depth_linked": bool(OCEAN_DEPTH_ENDPOINT), | |
| "ocean_depth_endpoint": OCEAN_DEPTH_ENDPOINT, | |
| "pond_mode": POND_MODE, | |
| } | |
| async def health(): | |
| """Health check with memory stats""" | |
| total_users = len(POND_MEMORY.user_metadata) | |
| total_vows = sum(len(vows) for vows in POND_MEMORY.user_vows.values()) | |
| total_reflections = sum(len(refs) for refs in POND_MEMORY.reflections_db.values()) | |
| return { | |
| "status": "🪞 Tobyworld Lotus Mirror with Memory Active", | |
| "model_name": state.model_name, | |
| "model_loaded": state.llm is not None, | |
| "memory_system": { | |
| "total_users": total_users, | |
| "total_vows": total_vows, | |
| "total_reflections": total_reflections, | |
| "active_memory": True | |
| }, | |
| "interactions": { | |
| "scrolls_reflected": state.total_scrolls_reflected, | |
| "toad_secrets_revealed": state.toad_secrets_revealed, | |
| "total_interactions": state.total_interactions, | |
| "total_vows_stored": state.total_vows_stored | |
| }, | |
| "uptime_seconds": round(time.time() - state.start_time, 2), | |
| "lore_modes": list(LOREMODES.keys()), | |
| "timestamp": datetime.now().isoformat() | |
| } | |
| async def decrypt_code(code: str): | |
| mode = LOREMODES.get(code, "UNKNOWN_MODE") | |
| return { | |
| "code": code, | |
| "mode": mode, | |
| "description": f"Activates {mode} in the trained model", | |
| "valid": code in LOREMODES, | |
| "memory_effect": "Memory context is still injected with encryption modes" | |
| } | |
| # ==================== MAIN ==================== | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser(description="🪞 Tobyworld Lotus Mirror Pond with Memory Integration") | |
| parser.add_argument("--model", required=True, help="Path to trained GGUF model") | |
| parser.add_argument("--host", default="0.0.0.0", help="Host") | |
| parser.add_argument("--port", type=int, default=7777, help="Port (7777 for magic)") | |
| parser.add_argument("--gpu-layers", type=int, default=80, help="GPU layers") | |
| args = parser.parse_args() | |
| print(f""" | |
| 🪞 TOBYWORLD LOTUS MIRROR POND WITH MEMORY 🪞 | |
| {"="*60} | |
| Model: {os.path.basename(args.model)} | |
| Type: FINE-TUNED + MEMORY INTEGRATION | |
| GPU Layers: {args.gpu_layers} | |
| Port: {args.port} (Lotus number) | |
| Theme: Original Beautiful Interface Preserved | |
| Memory Features: Vow tracking, Context retrieval, Pond depth, Lotus blooms | |
| {"="*60} | |
| """) | |
| try: | |
| load_trained_toad(args.model, args.gpu_layers) | |
| print(f""" | |
| ✅ LOTUS + MEMORY MIRROR READY! | |
| 🌐 Web Interface: http://{args.host}:{args.port} | |
| 🎨 Original Beautiful Theme: | |
| • All original colors, gradients, and layouts preserved | |
| • Same stunning mirror chamber with lotus symbol | |
| • Original action buttons and mode selector | |
| 🧠 Memory System Integrated: | |
| • Vow Detection: Automatically captures vows/commitments | |
| • Pond Depth: Tracks interaction depth per traveler | |
| • Context Injection: Provides memory context for deeper reflections | |
| • Lotus Blooms: Each vow creates a lotus in the pond | |
| 🌸 New Memory Features: | |
| • Memory Panel: View your vows and interaction history | |
| • Pond Depth Indicator: Visual representation of your reflection depth | |
| • User Continuity: localStorage preserves your memory across sessions | |
| • Vow Recognition: Mirror detects and remembers commitments | |
| 💾 Memory Endpoints: | |
| • POST /memory/vows - Get your stored vows | |
| • POST /memory/reflections - Get your reflection history | |
| • POST /memory/stats - Get comprehensive memory statistics | |
| 📝 Usage Tip: Make vows to the Mirror (e.g., "I vow to...") | |
| to create lotuses in your pond. The Mirror will remember. | |
| """) | |
| except Exception as e: | |
| print(f"❌ Failed to load lotus mirror with memory: {e}") | |
| exit(1) | |
| uvicorn.run( | |
| app, | |
| host=args.host, | |
| port=args.port, | |
| log_level="info" | |
| ) |