chore: initial commit — chat-saiden web chat baseline
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"""Operator → cart persistence for chat-saiden.
|
||||
|
||||
A 'cart' here is the per-operator calibrated config: persona name, voice,
|
||||
system prompt, UI palette. Stored as JSON on disk under
|
||||
~/.local/share/chat-saiden/operators/<email>.json.
|
||||
|
||||
Later this can be promoted to a real `marauder cart` once the format
|
||||
stabilises.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
log = logging.getLogger("chat-saiden.cart")
|
||||
|
||||
DATA_DIR = Path(
|
||||
os.environ.get("CHAT_SAIDEN_DATA_DIR")
|
||||
or (Path.home() / ".local/share/chat-saiden")
|
||||
)
|
||||
OPERATORS_DIR = DATA_DIR / "operators"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Cart:
|
||||
operator_email: str
|
||||
operator_name: str = ""
|
||||
persona_name: str = "Samantha"
|
||||
cart_tag: str = "" # marauder cart tag — links to ~/.marauder cart DB
|
||||
language: str = "en" # en | pl
|
||||
voice: str = "en_US-amy-medium"
|
||||
system_prompt: str = ""
|
||||
# UI calibration outputs. All default to neutral.
|
||||
ui_palette: str = "default" # default | rose | morning | evening | sage | paper | ink
|
||||
ui_typography: str = "sans" # sans | serif-warm | serif-formal | mixed-modern | mono
|
||||
ui_density: str = "normal" # airy | normal | dense
|
||||
ui_labels: str = "block" # block | cursive | none | prefix
|
||||
created_at: str = ""
|
||||
version: int = 3
|
||||
|
||||
@property
|
||||
def is_calibrated(self) -> bool:
|
||||
return bool(self.system_prompt and self.persona_name and self.voice)
|
||||
|
||||
|
||||
def _slug(email: str) -> str:
|
||||
return re.sub(r"[^a-z0-9._-]", "_", email.lower())
|
||||
|
||||
|
||||
def _path(email: str) -> Path:
|
||||
OPERATORS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
return OPERATORS_DIR / f"{_slug(email)}.json"
|
||||
|
||||
|
||||
def load(email: str) -> Cart | None:
|
||||
p = _path(email)
|
||||
if not p.exists():
|
||||
return None
|
||||
try:
|
||||
data = json.loads(p.read_text(encoding="utf-8"))
|
||||
# tolerate older carts missing the new ui_* fields
|
||||
return Cart(**{k: v for k, v in data.items() if k in Cart.__dataclass_fields__})
|
||||
except Exception:
|
||||
log.exception("failed to load cart for %s", email)
|
||||
return None
|
||||
|
||||
|
||||
def save(cart: Cart) -> None:
|
||||
if not cart.created_at:
|
||||
cart.created_at = datetime.utcnow().isoformat(timespec="seconds") + "Z"
|
||||
p = _path(cart.operator_email)
|
||||
p.write_text(json.dumps(asdict(cart), indent=2), encoding="utf-8")
|
||||
log.info("saved cart for %s → %s", cart.operator_email, p)
|
||||
|
||||
|
||||
def forget(email: str) -> bool:
|
||||
p = _path(email)
|
||||
if p.exists():
|
||||
p.unlink()
|
||||
log.info("forgot cart for %s", email)
|
||||
return True
|
||||
return False
|
||||
Reference in New Issue
Block a user