|
|
|
|
|
|
|
|
|
|
|
|
|
|
import argparse |
|
|
import json |
|
|
import os |
|
|
import time |
|
|
import hashlib |
|
|
import re |
|
|
from typing import List, Optional, Dict, Tuple, Any |
|
|
|
|
|
|
|
|
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_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.""" |
|
|
|
|
|
|
|
|
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): |
|
|
|
|
|
self.user_vows: Dict[str, List[Dict[str, str]]] = {} |
|
|
|
|
|
self.reflections_db: Dict[str, List[Dict[str, str]]] = {} |
|
|
|
|
|
self.user_metadata: Dict[str, Dict[str, Any]] = {} |
|
|
|
|
|
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) .+?)(?:[.!]|$)", |
|
|
] |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
seed = query[:50] + str(time.time()) |
|
|
request_hash = hashlib.md5(seed.encode()).hexdigest()[:12] |
|
|
|
|
|
user_id = f"traveler_{request_hash}" |
|
|
|
|
|
|
|
|
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] = [] |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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] |
|
|
} |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
POND_MEMORY = PondMemoryService() |
|
|
|
|
|
|
|
|
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 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}") |
|
|
|
|
|
|
|
|
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() |
|
|
|
|
|
|
|
|
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_MODE = os.getenv("POND_MODE", "local").lower() |
|
|
|
|
|
|
|
|
OCEAN_ENDPOINT = os.getenv("OCEAN_ENDPOINT", "").strip() |
|
|
OCEAN_API_KEY = os.getenv("OCEAN_API_KEY", "").strip() |
|
|
|
|
|
|
|
|
OCEAN_DEPTH_ENDPOINT = os.getenv("OCEAN_DEPTH_ENDPOINT", "").strip() |
|
|
OCEAN_DEPTH_API_KEY = os.getenv("OCEAN_DEPTH_API_KEY", "").strip() or OCEAN_API_KEY |
|
|
|
|
|
|
|
|
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. |
|
|
""" |
|
|
|
|
|
|
|
|
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 = "🪞🌊🍃🌀🔵🕰️" |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
return force_mirror_format(raw_text, mode, user_query) |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
text = re.sub(r'\*\*', '', text) |
|
|
text = re.sub(r'\*', '', text) |
|
|
|
|
|
|
|
|
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', |
|
|
] |
|
|
|
|
|
for pattern in patterns_to_remove: |
|
|
text = re.sub(pattern, '', text, flags=re.IGNORECASE) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
text = re.sub(r'\r\n', '\n', text) |
|
|
text = re.sub(r'\n\s*\n\s*\n+', '\n\n', text) |
|
|
|
|
|
|
|
|
gq_patterns = [ |
|
|
|
|
|
(r'Guiding Question\s*[::]\s*(.+)', 'Guiding Question:'), |
|
|
(r'引导问题\s*[::]\s*(.+)', '引导问题:'), |
|
|
|
|
|
|
|
|
(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}" |
|
|
|
|
|
|
|
|
text = re.sub(pattern, '', text, flags=re.IGNORECASE | re.DOTALL) |
|
|
break |
|
|
|
|
|
|
|
|
text = re.sub(r'\n{3,}', '\n\n', text) |
|
|
text = text.strip() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if mode == "scroll": |
|
|
print(f"Scroll mode detected - forcing NO guiding question") |
|
|
found_gq = None |
|
|
|
|
|
|
|
|
elif mode == "reflect" and not found_gq: |
|
|
|
|
|
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}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final_text = text |
|
|
|
|
|
if found_gq and mode != "scroll": |
|
|
if text: |
|
|
final_text = text.rstrip() + "\n\n" + found_gq |
|
|
else: |
|
|
final_text = found_gq |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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_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' |
|
|
] |
|
|
|
|
|
|
|
|
for word in emotional_words: |
|
|
if word in query_lower: |
|
|
return True |
|
|
|
|
|
|
|
|
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]}...") |
|
|
print(f"Input text:\n{text}") |
|
|
print(f"{'='*60}\n") |
|
|
|
|
|
original_text = text |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sections_to_remove = [ |
|
|
|
|
|
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'\*\*.*?\*\*', |
|
|
|
|
|
|
|
|
r'\d+\.\s*What does.*?(?=\n\n|\Z)', |
|
|
r'\d+\.\s*How do.*?(?=\n\n|\Z)', |
|
|
r'\d+\.\s*What is.*?(?=\n\n|\Z)', |
|
|
|
|
|
|
|
|
r'\([^)]*\)', |
|
|
r'\[.*?\]', |
|
|
] |
|
|
|
|
|
for pattern in sections_to_remove: |
|
|
text = re.sub(pattern, '', text, flags=re.IGNORECASE | re.DOTALL) |
|
|
|
|
|
|
|
|
text = re.sub(r'\*\*', '', text) |
|
|
text = re.sub(r'\*', '', text) |
|
|
text = re.sub(r'#{1,6}\s*', '', text) |
|
|
|
|
|
|
|
|
text = re.sub(r'=+\s*.+?\s*=+', '', text) |
|
|
|
|
|
|
|
|
|
|
|
reflection_text = text |
|
|
guiding_question = None |
|
|
|
|
|
|
|
|
gq_patterns = [ |
|
|
|
|
|
(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:'), |
|
|
|
|
|
|
|
|
(r'引导问题\s*[::]\s*(.+?)(?=\n\n|$)', '引导问题:'), |
|
|
(r'引导\s*问题\s*[::]\s*(.+?)(?=\n\n|$)', '引导问题:'), |
|
|
(r'镜子问\s*[::]\s*(.+?)(?=\n\n|$)', '镜子问:'), |
|
|
|
|
|
|
|
|
(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() |
|
|
|
|
|
gq_text = re.sub(r'[\*\"]', '', gq_text) |
|
|
guiding_question = f"{gq_prefix} {gq_text}" |
|
|
|
|
|
|
|
|
text = re.sub(pattern, '', text, flags=re.IGNORECASE | re.DOTALL) |
|
|
print(f"Found and removed guiding question: {guiding_question}") |
|
|
break |
|
|
|
|
|
|
|
|
reflection_text = re.sub(r'\n{3,}', '\n\n', text) |
|
|
reflection_text = reflection_text.strip() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 any('\u4e00' <= char <= '\u9fff' for char in user_query): |
|
|
if chinese_chars < 3 and english_chars > 10: |
|
|
print(f"Warning: Chinese query got English response") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if mode == "scroll": |
|
|
print(f"Scroll mode detected - forcing NO guiding question") |
|
|
|
|
|
guiding_question = None |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
if not re.match(r'^(Quote from Scroll|Scroll|"|「|「「)', reflection_text, re.IGNORECASE): |
|
|
|
|
|
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": |
|
|
|
|
|
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}") |
|
|
|
|
|
query_lower = user_query.lower() |
|
|
|
|
|
|
|
|
has_chinese = any('\u4e00' <= char <= '\u9fff' for char in user_query) |
|
|
|
|
|
if has_chinese: |
|
|
|
|
|
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: |
|
|
|
|
|
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") |
|
|
|
|
|
guiding_question = None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
reflection_text = re.sub(r'\d+\.\s*.+?(?=\n\n|$)', '', reflection_text) |
|
|
reflection_text = re.sub(r'[A-Z]{2,}.*?(?=\n\n|$)', '', reflection_text) |
|
|
|
|
|
|
|
|
needs_new_response = False |
|
|
if not reflection_text or len(reflection_text.split()) < 3: |
|
|
needs_new_response = True |
|
|
else: |
|
|
|
|
|
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") |
|
|
|
|
|
query_lower = user_query.lower() |
|
|
has_chinese = any('\u4e00' <= char <= '\u9fff' for char in user_query) |
|
|
|
|
|
if has_chinese: |
|
|
|
|
|
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: |
|
|
|
|
|
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." |
|
|
|
|
|
|
|
|
|
|
|
if guiding_question and mode != "scroll": |
|
|
|
|
|
if reflection_text and len(reflection_text.split()) > 2: |
|
|
final_text = f"{reflection_text}\n\n{guiding_question}" |
|
|
else: |
|
|
|
|
|
final_text = guiding_question.replace("Guiding Question:", "The mirror asks:").replace("引导问题:", "镜子问:") |
|
|
else: |
|
|
final_text = reflection_text |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 '===' in final_text: |
|
|
print(f"Warning: Still found === sections in final text, removing") |
|
|
|
|
|
lines = final_text.split('\n') |
|
|
clean_lines = [line for line in lines if '===' not in line] |
|
|
final_text = '\n'.join(clean_lines) |
|
|
|
|
|
|
|
|
if mode == "reflect" and final_text and len(final_text) > 20: |
|
|
symbols = ["🪞", "🌊", "🍃", "🌀"] |
|
|
|
|
|
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: |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
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: |
|
|
@staticmethod |
|
|
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" |
|
|
|
|
|
@staticmethod |
|
|
def generate_response_hash(query: str, response: str) -> str: |
|
|
combined = f"{query}:::{response}:::TOADGANG" |
|
|
return hashlib.md5(combined.encode()).hexdigest()[:8].upper() |
|
|
|
|
|
@staticmethod |
|
|
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: |
|
|
@staticmethod |
|
|
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. |
|
|
""" |
|
|
|
|
|
|
|
|
memory_context = POND_MEMORY.retrieve_context(user_id, query) |
|
|
|
|
|
|
|
|
few_shot = few_shot_examples |
|
|
|
|
|
|
|
|
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." |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
@staticmethod |
|
|
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 |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
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 |
|
|
self.continuous_days: int = 0 |
|
|
|
|
|
state = PondState() |
|
|
|
|
|
|
|
|
init_pond_identity() |
|
|
|
|
|
|
|
|
POND_ID = state.pond_id |
|
|
|
|
|
app = FastAPI(title="🪞 Tobyworld Mirror Pond with Memory", version="V10-Lotus-Memory") |
|
|
|
|
|
|
|
|
app.add_middleware( |
|
|
CORSMiddleware, |
|
|
allow_origins=["*"], |
|
|
allow_credentials=True, |
|
|
allow_methods=["*"], |
|
|
allow_headers=["*"], |
|
|
) |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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>""" |
|
|
|
|
|
|
|
|
class AskRequest(BaseModel): |
|
|
query: str |
|
|
mode: Optional[str] = "reflect" |
|
|
encryption: Optional[str] = None |
|
|
user_hash: Optional[str] = None |
|
|
pond_mode: Optional[str] = None |
|
|
|
|
|
class MemoryRequest(BaseModel): |
|
|
user_hash: str |
|
|
|
|
|
@app.get("/", response_class=HTMLResponse) |
|
|
async def serve_miniapp(): |
|
|
return MINIAPP_HTML |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
return |
|
|
if not state.pond_private_key_hex or not state.pond_public_key_hex or not state.pond_id: |
|
|
return |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
reflection_count = state.total_interactions |
|
|
vow_count = state.total_vows_stored |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
pass |
|
|
else: |
|
|
|
|
|
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", |
|
|
} |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
print(f"⚠️ Depth submission failed: {e}") |
|
|
|
|
|
|
|
|
|
|
|
@app.post("/ask") |
|
|
async def ask_mirror(request_data: AskRequest, fastapi_request: Request): |
|
|
"""Main endpoint with memory integration (local or ocean backend).""" |
|
|
|
|
|
|
|
|
effective_mode = (request_data.pond_mode or POND_MODE).lower() |
|
|
if effective_mode not in ("local", "ocean"): |
|
|
effective_mode = POND_MODE |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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}" |
|
|
) |
|
|
|
|
|
|
|
|
POND_MEMORY.update_user_metadata(user_id, mode=request_data.mode or "reflect") |
|
|
|
|
|
|
|
|
|
|
|
prompt = None |
|
|
memory_context = None |
|
|
|
|
|
|
|
|
has_chinese = any('\u4e00' <= char <= '\u9fff' for char in request_data.query) |
|
|
|
|
|
|
|
|
if request_data.mode == "scroll": |
|
|
temperature = 0.1 |
|
|
max_tokens = 150 |
|
|
elif request_data.mode == "toad": |
|
|
temperature = 0.8 |
|
|
max_tokens = 250 |
|
|
|
|
|
elif request_data.mode == "crypt": |
|
|
temperature = 0.5 |
|
|
max_tokens = 200 |
|
|
elif request_data.mode == "rune": |
|
|
temperature = 0.6 |
|
|
max_tokens = 350 |
|
|
else: |
|
|
|
|
|
if has_chinese: |
|
|
temperature = 0.5 |
|
|
max_tokens = 200 |
|
|
else: |
|
|
temperature = 0.7 |
|
|
max_tokens = 300 |
|
|
|
|
|
ocean_meta = {} |
|
|
|
|
|
if effective_mode == "ocean" and OCEAN_ENDPOINT: |
|
|
|
|
|
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: |
|
|
|
|
|
prompt = EnhancedToadPromptBuilder.build_prompt_with_memory( |
|
|
query=request_data.query, |
|
|
user_id=user_id, |
|
|
encryption=request_data.encryption, |
|
|
mode=request_data.mode, |
|
|
) |
|
|
|
|
|
|
|
|
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() |
|
|
|
|
|
|
|
|
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)}") |
|
|
|
|
|
|
|
|
reply = cadence_guard( |
|
|
raw_reply, |
|
|
mode=request_data.mode or "reflect", |
|
|
user_query=request_data.query, |
|
|
) |
|
|
|
|
|
|
|
|
print(f"\n{'='*60}") |
|
|
print(f"AFTER CADENCE GUARD:") |
|
|
print(f"Length: {len(reply)} chars") |
|
|
print(f"Content:\n{reply}") |
|
|
print(f"{'='*60}\n") |
|
|
|
|
|
|
|
|
|
|
|
stored_reflection = POND_MEMORY.store_reflection( |
|
|
user_id=user_id, |
|
|
query=request_data.query, |
|
|
response=reply, |
|
|
mode=request_data.mode, |
|
|
encryption=request_data.encryption, |
|
|
) |
|
|
|
|
|
|
|
|
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, |
|
|
) |
|
|
|
|
|
|
|
|
try: |
|
|
POND_MEMORY._save_to_disk() |
|
|
except Exception as _e: |
|
|
print(f"⚠️ Failed to persist pond memory: {_e}") |
|
|
|
|
|
|
|
|
try: |
|
|
import asyncio |
|
|
asyncio.create_task(submit_depth_to_ocean()) |
|
|
except Exception as _e: |
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
user_response_hash = ToadEncryption.generate_user_hash( |
|
|
query=request_data.query, |
|
|
encryption=request_data.encryption, |
|
|
salt=str(time.time()), |
|
|
) |
|
|
|
|
|
|
|
|
encryption_hash = None |
|
|
if request_data.encryption: |
|
|
encryption_hash = ToadEncryption.generate_response_hash( |
|
|
request_data.query, |
|
|
reply, |
|
|
) |
|
|
|
|
|
|
|
|
user_stats = POND_MEMORY.get_user_stats(user_id) |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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: |
|
|
|
|
|
prompt = EnhancedToadPromptBuilder.build_prompt_with_memory( |
|
|
query=request.query, |
|
|
user_id=user_id, |
|
|
encryption=request.encryption, |
|
|
mode=request.mode, |
|
|
) |
|
|
|
|
|
|
|
|
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() |
|
|
|
|
|
|
|
|
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)}") |
|
|
|
|
|
|
|
|
reply = cadence_guard( |
|
|
raw_reply, |
|
|
mode=request.mode or "reflect", |
|
|
user_query=request.query, |
|
|
) |
|
|
|
|
|
|
|
|
print(f"\n{'='*60}") |
|
|
print(f"AFTER CADENCE GUARD:") |
|
|
print(f"Length: {len(reply)} chars") |
|
|
print(f"Content:\n{reply}") |
|
|
print(f"{'='*60}\n") |
|
|
|
|
|
|
|
|
|
|
|
stored_reflection = POND_MEMORY.store_reflection( |
|
|
user_id=user_id, |
|
|
query=request.query, |
|
|
response=reply, |
|
|
mode=request.mode, |
|
|
encryption=request.encryption, |
|
|
) |
|
|
|
|
|
|
|
|
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, |
|
|
) |
|
|
|
|
|
|
|
|
try: |
|
|
import asyncio |
|
|
asyncio.create_task(submit_depth_to_ocean()) |
|
|
except Exception as _e: |
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
user_response_hash = ToadEncryption.generate_user_hash( |
|
|
query=request.query, |
|
|
encryption=request.encryption, |
|
|
salt=str(time.time()), |
|
|
) |
|
|
|
|
|
|
|
|
encryption_hash = None |
|
|
if request.encryption: |
|
|
encryption_hash = ToadEncryption.generate_response_hash( |
|
|
request.query, |
|
|
reply, |
|
|
) |
|
|
|
|
|
|
|
|
user_stats = POND_MEMORY.get_user_stats(user_id) |
|
|
|
|
|
|
|
|
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, |
|
|
"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), |
|
|
}, |
|
|
} |
|
|
|
|
|
@app.post("/memory/vows") |
|
|
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) |
|
|
} |
|
|
|
|
|
@app.post("/memory/reflections") |
|
|
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 |
|
|
} |
|
|
|
|
|
@app.post("/memory/stats") |
|
|
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 |
|
|
} |
|
|
} |
|
|
|
|
|
@app.get("/scroll/{scroll_number}") |
|
|
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") |
|
|
|
|
|
|
|
|
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() |
|
|
|
|
|
|
|
|
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 |
|
|
) |
|
|
} |
|
|
|
|
|
@app.post("/debug/format") |
|
|
async def debug_format(request: AskRequest): |
|
|
"""Debug endpoint to test formatting without running the model""" |
|
|
|
|
|
needs_gq = should_have_guiding_question(request.query, request.mode or "reflect") |
|
|
|
|
|
|
|
|
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?" |
|
|
|
|
|
|
|
|
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 |
|
|
} |
|
|
@app.get("/identity") |
|
|
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, |
|
|
} |
|
|
|
|
|
@app.get("/health") |
|
|
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() |
|
|
} |
|
|
|
|
|
@app.get("/encryption/{code}") |
|
|
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" |
|
|
} |
|
|
|
|
|
|
|
|
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" |
|
|
) |