Spaces:
Sleeping
Sleeping
| # /memory/sessions.py | |
| """ | |
| Simple in-memory session manager for chatbot history. | |
| Supports TTL, max history, and JSON persistence. | |
| """ | |
| from __future__ import annotations | |
| import time, json, uuid | |
| from pathlib import Path | |
| from dataclasses import dataclass, field | |
| from typing import Dict, List, Tuple, Optional, Any | |
| History = List[Tuple[str, str]] # [("user","..."), ("bot","...")] | |
| class Session: | |
| session_id: str | |
| user_id: Optional[str] = None | |
| history: History = field(default_factory=list) | |
| data: Dict[str, Any] = field(default_factory=dict) | |
| created_at: float = field(default_factory=time.time) | |
| updated_at: float = field(default_factory=time.time) | |
| class SessionStore: | |
| def __init__(self, ttl_seconds: Optional[int] = 3600, max_history: Optional[int] = 50): | |
| self.ttl_seconds = ttl_seconds | |
| self.max_history = max_history | |
| self._sessions: Dict[str, Session] = {} | |
| # --- internals --- | |
| def _expired(self, sess: Session) -> bool: | |
| if self.ttl_seconds is None: | |
| return False | |
| return (time.time() - sess.updated_at) > self.ttl_seconds | |
| # --- CRUD --- | |
| def create(self, user_id: Optional[str] = None) -> Session: | |
| sid = str(uuid.uuid4()) | |
| sess = Session(session_id=sid, user_id=user_id) | |
| self._sessions[sid] = sess | |
| return sess | |
| def get(self, sid: str) -> Optional[Session]: | |
| return self._sessions.get(sid) | |
| def get_history(self, sid: str) -> History: | |
| sess = self.get(sid) | |
| return list(sess.history) if sess else [] | |
| def append_user(self, sid: str, text: str) -> None: | |
| self._append(sid, "user", text) | |
| def append_bot(self, sid: str, text: str) -> None: | |
| self._append(sid, "bot", text) | |
| def _append(self, sid: str, who: str, text: str) -> None: | |
| sess = self.get(sid) | |
| if not sess: | |
| return | |
| sess.history.append((who, text)) | |
| if self.max_history and len(sess.history) > self.max_history: | |
| sess.history = sess.history[-self.max_history:] | |
| sess.updated_at = time.time() | |
| # --- Data store --- | |
| def set(self, sid: str, key: str, value: Any) -> None: | |
| sess = self.get(sid) | |
| if sess: | |
| sess.data[key] = value | |
| sess.updated_at = time.time() | |
| def get_value(self, sid: str, key: str, default=None) -> Any: | |
| sess = self.get(sid) | |
| return sess.data.get(key, default) if sess else default | |
| def data_dict(self, sid: str) -> Dict[str, Any]: | |
| sess = self.get(sid) | |
| return dict(sess.data) if sess else {} | |
| # --- TTL management --- | |
| def sweep(self) -> int: | |
| """Remove expired sessions; return count removed.""" | |
| expired = [sid for sid, s in self._sessions.items() if self._expired(s)] | |
| for sid in expired: | |
| self._sessions.pop(sid, None) | |
| return len(expired) | |
| def all_ids(self): | |
| return list(self._sessions.keys()) | |
| # --- persistence --- | |
| def save(self, path: Path) -> None: | |
| payload = { | |
| sid: { | |
| "user_id": s.user_id, | |
| "history": s.history, | |
| "data": s.data, | |
| "created_at": s.created_at, | |
| "updated_at": s.updated_at, | |
| } | |
| for sid, s in self._sessions.items() | |
| } | |
| path.write_text(json.dumps(payload, indent=2)) | |
| def load(cls, path: Path) -> "SessionStore": | |
| store = cls() | |
| if not path.exists(): | |
| return store | |
| raw = json.loads(path.read_text()) | |
| for sid, d in raw.items(): | |
| s = Session( | |
| session_id=sid, | |
| user_id=d.get("user_id"), | |
| history=d.get("history", []), | |
| data=d.get("data", {}), | |
| created_at=d.get("created_at", time.time()), | |
| updated_at=d.get("updated_at", time.time()), | |
| ) | |
| store._sessions[sid] = s | |
| return store | |
| # --- Module-level singleton for convenience --- | |
| _store = SessionStore() | |
| def new_session(user_id: Optional[str] = None) -> Session: | |
| return _store.create(user_id) | |
| def history(sid: str) -> History: | |
| return _store.get_history(sid) | |
| def append_user(sid: str, text: str) -> None: | |
| _store.append_user(sid, text) | |
| def append_bot(sid: str, text: str) -> None: | |
| _store.append_bot(sid, text) | |
| def set_value(sid: str, key: str, value: Any) -> None: | |
| _store.set(sid, key, value) | |
| def get_value(sid: str, key: str, default=None) -> Any: | |
| return _store.get_value(sid, key, default) | |