From fca13c4c89490e5311c52a04c53a470f2fc12b50 Mon Sep 17 00:00:00 2001 From: serversdown Date: Wed, 17 Jun 2026 03:58:37 +0000 Subject: [PATCH] =?UTF-8?q?feat(web):=20"read=20her=20mind"=20=E2=80=94=20?= =?UTF-8?q?live=20self-state=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A pull-up-anytime view of Lyra's interiority, so her thoughts aren't buried in a DB blob. Mobile-first, auto-refreshing every 12s (and on tab focus). - GET /self serves the page; GET /self/state returns her self-state + the timestamp it last changed - shows: current mood + feeling meters (valence/energy/confidence/curiosity), her drives as bars, her self-narrative, the relationship line, and the reflections list (newest first), plus cycle/reflection counters and "last cycle Xm ago" - memory.self_state_updated_at(): when her mind last changed - index.html: "🧠 Mind" button opens /self Co-Authored-By: Claude Opus 4.8 (1M context) --- lyra/memory.py | 9 ++ lyra/web/server.py | 12 ++- lyra/web/static/index.html | 1 + lyra/web/static/self.html | 172 +++++++++++++++++++++++++++++++++++++ 4 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 lyra/web/static/self.html diff --git a/lyra/memory.py b/lyra/memory.py index 75938a0..c2f3d4e 100644 --- a/lyra/memory.py +++ b/lyra/memory.py @@ -512,6 +512,15 @@ def get_self_state(state_id: str = "lyra") -> dict | None: return json.loads(r["data"]) if r else None +def self_state_updated_at(state_id: str = "lyra") -> str | None: + """ISO timestamp her self-state was last written (None if never).""" + conn = _connection() + r = conn.execute( + "SELECT updated_at FROM self_state WHERE id = ?", (state_id,) + ).fetchone() + return r["updated_at"] if r else None + + def set_self_state(state: dict, state_id: str = "lyra") -> None: now = datetime.now(timezone.utc).isoformat() conn = _connection() diff --git a/lyra/web/server.py b/lyra/web/server.py index 6a428cc..bf79b3e 100644 --- a/lyra/web/server.py +++ b/lyra/web/server.py @@ -18,7 +18,7 @@ from fastapi import FastAPI, Request from fastapi.responses import FileResponse, StreamingResponse from fastapi.staticfiles import StaticFiles -from lyra import chat, logbus, memory, summary +from lyra import chat, logbus, memory, self_state, summary from lyra.llm import Backend @@ -115,6 +115,16 @@ def create_app() -> FastAPI: """Full-page, mobile-friendly live log viewer (separate from the chat UI).""" return FileResponse(str(_STATIC / "logs.html")) + @app.get("/self") + async def self_page() -> FileResponse: + """'Read her mind' β€” a view of Lyra's current self-state.""" + return FileResponse(str(_STATIC / "self.html")) + + @app.get("/self/state") + async def self_state_json() -> dict: + """Lyra's current interiority + when it last changed.""" + return {"state": self_state.load(), "updated_at": memory.self_state_updated_at()} + @app.get("/stream/logs") async def stream_logs(request: Request) -> StreamingResponse: """Live activity feed: replay the recent buffer, then stream new events.""" diff --git a/lyra/web/static/index.html b/lyra/web/static/index.html index da75ca9..b8cc3f9 100644 --- a/lyra/web/static/index.html +++ b/lyra/web/static/index.html @@ -70,6 +70,7 @@ β›Ά Full Log + 🧠 Mind diff --git a/lyra/web/static/self.html b/lyra/web/static/self.html new file mode 100644 index 0000000..5547afa --- /dev/null +++ b/lyra/web/static/self.html @@ -0,0 +1,172 @@ + + + + + + + Lyra β€” Mind + + + +
+
+ +

🧠 Lyra · Mind

+ ← Chat + β€” +
+
+

Reading her mind…

+ + + +