feat: persona chat loop, web UI, and local (Ollama) embeddings

Phase 1 — persona + persistent memory chat loop:
- lyra/persona.py + personas/lyra.md: editable identity/voice (friend-first,
  honest, never invents poker math)
- lyra/chat.py: turn loop assembling persona + cross-session recall + recent
  context, persisting both sides to SQLite
- lyra/session.py, lyra/__main__.py: session lifecycle + `lyra` REPL

Phase 1.25 — reuse the old web UI:
- vendored the prior single-page UI into lyra/web/static, repointed to
  same-origin
- lyra/web/server.py (FastAPI): serves the UI and backs its endpoint contract
  (/v1/chat/completions, session CRUD, health, inert thinking-stream) with the
  new chat loop + memory; SQLite stays the single source of truth
- `lyra-web` console script

Local backends — test for free, no OpenAI key:
- llm.embed routes via EMBED_BACKEND (cloud=OpenAI, local=Ollama /api/embed)
- simplified UI backend selector to Local (Ollama) / Cloud (OpenAI), default local
- memory connection opened check_same_thread=False for the threaded server

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 18:36:31 +00:00
parent 6d88505697
commit 3b9e0bb1e0
17 changed files with 2973 additions and 4 deletions
+36
View File
@@ -0,0 +1,36 @@
"""`python -m lyra` (or `lyra`): a terminal REPL to talk to Lyra."""
from __future__ import annotations
import sys
from lyra import chat
from lyra.session import Session
_QUIT = {"exit", "quit", ":q"}
def main() -> int:
session = Session()
print(f"Lyra — session {session.id}. Ctrl-D or 'exit' to leave.\n")
while True:
try:
user_msg = input("you > ").strip()
except (EOFError, KeyboardInterrupt):
print()
break
if not user_msg:
continue
if user_msg.lower() in _QUIT:
break
try:
reply = chat.respond(session.id, user_msg)
except Exception as exc: # keep the loop alive; surface the error
print(f"\n[error] {exc}\n", file=sys.stderr)
continue
print(f"\nlyra > {reply}\n")
print("later.")
return 0
if __name__ == "__main__":
raise SystemExit(main())