diff --git a/proxmox/lxc171-claude-agent/discord-bridge/dashboard/api.py b/proxmox/lxc171-claude-agent/discord-bridge/dashboard/api.py index d0a8916..c825b64 100644 --- a/proxmox/lxc171-claude-agent/discord-bridge/dashboard/api.py +++ b/proxmox/lxc171-claude-agent/discord-bridge/dashboard/api.py @@ -291,6 +291,46 @@ def maria_delete_client(cid: str) -> bool: return True +def _maria_conv_dir(): + return MARIA_STATE_DIR / "conversations" + + +def _maria_cale_fir(jid: str): + # oglinda lui rag/fir.py:_cale — tine-o la fel + if not jid: + raise ValueError("jid lipsa") + return _maria_conv_dir() / (re.sub(r"[^0-9a-zA-Z@._-]", "_", jid) + ".json") + + +def maria_fire() -> list[dict]: + d = _maria_conv_dir() + if not d.is_dir(): + return [] + out = [] + for f in d.glob("*.json"): + try: + fir = json.loads(f.read_text(encoding="utf-8")) + except (OSError, ValueError): + continue + out.append({ + "jid": fir.get("jid"), + "at": fir.get("at"), + "ancora": (fir.get("ancora") or "")[:200], + "schimburi": len(fir.get("schimburi") or []), + "ref": fir.get("ref"), + }) + out.sort(key=lambda x: x.get("at") or 0, reverse=True) + return out + + +def maria_delete_fir(jid: str) -> bool: + path = _maria_cale_fir(jid) + if path.exists(): + path.unlink() + return True + return False + + def _maria_run(script: str, timeout: float = 600.0, extra_args: list[str] | None = None) -> dict: interpreter = str(MARIA_VENV_PY) if MARIA_VENV_PY.exists() else sys.executable args = [interpreter, str(MARIA_RAG_DIR / script), *(extra_args or [])] @@ -805,6 +845,8 @@ class Handler(SimpleHTTPRequestHandler): return self.send_json({"escalations": maria_escalations()}) if path == "/api/maria/clients": return self.send_json({"clients": maria_clients()}) + if path == "/api/maria/fire": + return self.send_json({"fire": maria_fire()}) if path == "/api/maria/logs": try: n = min(max(int(qs.get("lines", ["200"])[0]), 1), 2000) @@ -849,6 +891,8 @@ class Handler(SimpleHTTPRequestHandler): return self.handle_maria_client_write() if path == "/api/maria/clients/delete": return self.handle_maria_client_delete() + if path == "/api/maria/fire/delete": + return self.handle_maria_fire_delete() if path == "/api/maria/reindex": return self.send_json(maria_reindex()) if path == "/api/maria/sync": @@ -988,6 +1032,15 @@ class Handler(SimpleHTTPRequestHandler): ok = maria_delete_client(cid) return self.send_json({"ok": ok, "id": cid}, 200 if ok else 404) + def handle_maria_fire_delete(self): + data = self.read_json() + jid = str(data.get("jid") or "") + try: + ok = maria_delete_fir(jid) + except ValueError as exc: + return self.send_json({"ok": False, "error": str(exc)}, 400) + return self.send_json({"ok": ok, "jid": jid}, 200 if ok else 404) + def main() -> None: bind = config.get("DASHBOARD_BIND") or "127.0.0.1" diff --git a/proxmox/lxc171-claude-agent/discord-bridge/dashboard/index.html b/proxmox/lxc171-claude-agent/discord-bridge/dashboard/index.html index f9b727a..f909bc6 100644 --- a/proxmox/lxc171-claude-agent/discord-bridge/dashboard/index.html +++ b/proxmox/lxc171-claude-agent/discord-bridge/dashboard/index.html @@ -120,6 +120,16 @@
se încarcă…
+
+

Fire de discuție active

+
+ + + +
JIDSubiectUltima activitate
+
+
+

Clienți cunoscuți

@@ -596,6 +606,24 @@ async function mDelClient(id) { await mLoadClients(); } +async function mLoadFire() { + var res = await api('api/maria/fire'); + var fire = res.ok ? (res.data.fire || []) : []; + document.getElementById('mFirCount').textContent = fire.length ? fire.length : ''; + document.getElementById('mFire').innerHTML = fire.length ? fire.map(function (f) { + return '' + esc(f.jid) + '' + + '' + esc(f.ancora) + '' + + '' + (f.at ? new Date(f.at * 1000).toLocaleString('ro-RO') : '—') + '' + + ''; + }).join('') : 'niciun fir activ'; +} + +async function mDelFir(jid) { + if (!confirm('Resetezi firul cu ' + jid + '? Maria va porni o discutie noua la urmatorul mesaj.')) return; + await post('api/maria/fire/delete', { jid: jid }); + await mLoadFire(); +} + var mLogFile = 'bridge'; function mSetLog(which) { mLogFile = which; mRefreshLogs(); } async function mRefreshLogs() { @@ -610,7 +638,7 @@ async function mRefreshLogs() { // ── pornire ─────────────────────────────────────────────────── refresh(); refreshDoctor(); refreshLogs(); -mRefresh(); mRefreshLogs(); mLoadClients(); +mRefresh(); mRefreshLogs(); mLoadClients(); mLoadFire(); setInterval(refresh, 5000); setInterval(refreshDoctor, 30000); setInterval(mRefresh, 4000); diff --git a/proxmox/lxc171-claude-agent/discord-bridge/tests/test_maria_fire.py b/proxmox/lxc171-claude-agent/discord-bridge/tests/test_maria_fire.py new file mode 100644 index 0000000..6fba653 --- /dev/null +++ b/proxmox/lxc171-claude-agent/discord-bridge/tests/test_maria_fire.py @@ -0,0 +1,81 @@ +"""Teste pentru firele de discutie Maria din dashboard/api.py. + +Apeleaza direct functiile, cu MARIA_STATE_DIR mutat in tmp — la fel ca +test_maria_clients.py. +""" + +from __future__ import annotations + +import json +import pathlib +import re +import sys + +import pytest + +ROOT = pathlib.Path(__file__).resolve().parent.parent +sys.path.insert(0, str(ROOT)) +sys.path.insert(0, str(ROOT / "dashboard")) + +api = pytest.importorskip("api", reason="dashboard/api.py") + +JID = "120363409761730101@g.us" + + +def _sanitizat(jid: str) -> str: + # aceeasi transformare ca rag/fir.py:_cale + return re.sub(r"[^0-9a-zA-Z@._-]", "_", jid) + ".json" + + +@pytest.fixture(autouse=True) +def maria_dir(tmp_path, monkeypatch): + d = tmp_path / "maria-bridge" + d.mkdir() + monkeypatch.setattr(api, "MARIA_STATE_DIR", d) + return d + + +def _scrie_fir(d, jid=JID, ancora="ORA-06550 la salvarea facturii", schimburi=None): + conv = d / "conversations" + conv.mkdir(parents=True, exist_ok=True) + fir = {"jid": jid, "at": 1700000000.0, "ancora": ancora, + "schimburi": schimburi if schimburi is not None else [{"rol": "om", "text": "buna"}]} + (conv / _sanitizat(jid)).write_text(json.dumps(fir, ensure_ascii=False), encoding="utf-8") + return fir + + +def test_lista_fire(maria_dir): + _scrie_fir(maria_dir) + fire = api.maria_fire() + assert len(fire) == 1 + assert fire[0]["jid"] == JID + assert fire[0]["ancora"] == "ORA-06550 la salvarea facturii" + assert fire[0]["schimburi"] == 1 + + +def test_sterge_fir(maria_dir): + _scrie_fir(maria_dir) + assert api.maria_delete_fir(JID) is True + assert not (maria_dir / "conversations" / _sanitizat(JID)).exists() + + +def test_sterge_a_doua_oara_esueaza(maria_dir): + _scrie_fir(maria_dir) + api.maria_delete_fir(JID) + assert api.maria_delete_fir(JID) is False + + +def test_fisier_corupt_e_ignorat(maria_dir): + conv = maria_dir / "conversations" + conv.mkdir(parents=True, exist_ok=True) + (conv / "stricat.json").write_text("{nu e json", encoding="utf-8") + assert api.maria_fire() == [] + + +def test_director_lipsa(maria_dir): + assert api.maria_fire() == [] + + +def test_jid_gol_ridica_valueerror(maria_dir): + with pytest.raises(ValueError): + api.maria_delete_fir("")