Spaces:
Sleeping
Sleeping
File size: 6,013 Bytes
9ae78b5 2fd89cd 0c725f1 2fd89cd 9f5ba86 9ae78b5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | """
memory_cloud.py — conversation long-term memory for Jarvis.
Backend: Firebase Firestore (cloud only). No local fallback — if Firestore
is unavailable, remember() reports failure and recall() returns nothing.
"""
import os
import time
_firestore = None
_firestore_failed = False
def _credentials_path():
p = os.environ.get("FIREBASE_CREDENTIALS_PATH", "").strip()
if p:
return p
here = os.path.dirname(os.path.abspath(__file__))
return os.path.join(here, "firebase_service_account.json")
def _init_firestore():
"""Lazily init Firestore once. Returns client or None."""
global _firestore, _firestore_failed
if _firestore is not None:
return _firestore
if _firestore_failed:
return None
try:
import firebase_admin
from firebase_admin import credentials, firestore
import json as _json
js = (os.environ.get("FIREBASE_CREDENTIALS_JSON") or "").strip()
if js:
info = _json.loads(js)
app = firebase_admin.get_app() if firebase_admin._apps else firebase_admin.initialize_app(
credentials.Certificate(info)
)
else:
path = _credentials_path()
if not os.path.exists(path):
print("[warn] No firebase_service_account.json - memory disabled (no local fallback).")
_firestore_failed = True
return None
app = firebase_admin.get_app() if firebase_admin._apps else firebase_admin.initialize_app(
credentials.Certificate(path)
)
_firestore = firestore.client(app)
print("[ok] Firebase Firestore connected for memory.")
return _firestore
except Exception as exc:
print(f"[warn] Firestore unavailable ({exc}) - memory disabled.")
_firestore_failed = True
return None
def remember(content: str) -> bool:
"""Save a fact/interaction to Firestore. False if Firestore unavailable."""
if not content:
return False
db = _init_firestore()
if db is None:
return False
try:
db.collection("jarvis_memory").add({"content": content, "ts": time.time()})
return True
except Exception as exc:
print(f"[warn] Firestore write failed ({exc}).")
return False
CHAT_DOC = "shared"
CHAT_CAP = 12
def set_pref(key: str, value: str) -> bool:
"""Cross-channel preference (one doc, never raises)."""
try:
db = _init_firestore()
if db is None:
return False
import time as _t
ref = db.collection("jarvis_prefs").document("user")
doc = ref.get()
prefs = ((doc.to_dict() or {}).get("prefs") or {}) if doc.exists else {}
prefs[key] = (value or "")[:300]
ref.set({"prefs": prefs, "updated": _t.time()})
return True
except Exception as exc:
print(f"[warn] pref save failed ({exc}).")
return False
def get_prefs() -> dict:
try:
db = _init_firestore()
if db is None:
return {}
doc = db.collection("jarvis_prefs").document("user").get()
if doc.exists:
return (doc.to_dict() or {}).get("prefs") or {}
except Exception as exc:
print(f"[warn] pref load failed ({exc}).")
return {}
def chat_load(limit: int = 8) -> list:
"""Shared BAT<->Telegram conversation (list of {r,t,ts,src}). Never raises."""
try:
db = _init_firestore()
if db is None:
return []
doc = db.collection("jarvis_chat").document(CHAT_DOC).get()
if doc.exists:
msgs = (doc.to_dict() or {}).get("msgs") or []
msgs = sorted(msgs, key=lambda m: m.get("ts", 0))
return msgs[-limit:]
except Exception as exc:
print(f"[warn] chat load failed ({exc}).")
return []
def chat_save(role: str, text: str, src: str = "?") -> bool:
"""Append one exchange line to the shared conversation. Never raises."""
try:
db = _init_firestore()
if db is None or not text:
return False
ref = db.collection("jarvis_chat").document(CHAT_DOC)
doc = ref.get()
msgs = ((doc.to_dict() or {}).get("msgs") or []) if doc.exists else []
msgs.append({"r": role, "t": text[:500], "ts": time.time(), "src": src})
msgs = sorted(msgs, key=lambda m: m.get("ts", 0))[-CHAT_CAP:]
ref.set({"msgs": msgs, "updated": time.time()})
return True
except Exception as exc:
print(f"[warn] chat save failed ({exc}).")
return False
def stats() -> dict:
"""Count memory entries: history (jarvis_memory docs), contacts, chat turns.
Never raises — returns zeros when offline."""
out = {"history": 0, "contacts": 0, "turns": 0}
try:
db = _init_firestore()
if db is None:
return out
try:
out["history"] = len(db.collection("jarvis_memory").get())
except Exception:
pass
try:
out["contacts"] = len(db.collection("jarvis_contacts").get())
except Exception:
pass
try:
doc = db.collection("jarvis_chat").document(CHAT_DOC).get()
if doc.exists:
out["turns"] = len((doc.to_dict() or {}).get("msgs") or [])
except Exception:
pass
except Exception:
pass
return out
def recall(limit: int = 10) -> str:
"""Return the most recent remembered items as a spoken-friendly string."""
db = _init_firestore()
if db is None:
return ""
try:
snaps = (
db.collection("jarvis_memory")
.order_by("ts", direction="DESCENDING")
.limit(limit)
.get()
)
parts = [s.to_dict().get("content", "") for s in snaps]
return "; ".join(p for p in parts if p)
except Exception as exc:
print(f"[warn] Firestore read failed ({exc}).")
return "" |