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
+
+
+
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("")