diff --git a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/README.md b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/README.md index 3f0df5a..a5bf00f 100644 --- a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/README.md +++ b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/README.md @@ -60,7 +60,7 @@ embeddings. WhatsApp (self-chat, sau numarul legat) | v -whatsapp/index.js (Baileys) -- API HTTP :8099 (/status /send /messages /react /qr /pair) +whatsapp/index.js (Baileys) -- API HTTP :8099 (/status /send /messages /react /qr /pair /groups) | descarca imaginile primite in ~/.maria-bridge/media/ | v @@ -312,8 +312,11 @@ Iar cand chiar reindexeaza, **refoloseste vectorii chunk-urilor nemodificate** d indexul precedent (`rag/indexer.py:_vectori_existenti`). Un embedding costa ~7 secunde pe CPU: fara refolosire, adaugarea unui singur document la 170 de chunk-uri insemna 20 de minute de reconstruit tot. Cheia e chiar textul chunk-ului — daca nu -s-a schimbat niciun caracter, vectorul e acelasi. Logul spune de fiecare data cate -au fost calculate si cate refolosite. +s-a schimbat niciun caracter, vectorul e acelasi — dar numai pentru **acelasi +`EMBED_MODEL`**: fiecare intrare din index poarta modelul cu care a fost calculata, +iar la schimbarea modelului indexul se reface intreg. Altfel ar ramane un amestec de +vectori din doua modele, iar cautarea ar da rezultate aiurea fara nici o eroare. +Logul spune de fiecare data cate au fost calculate si cate refolosite. ## Teste @@ -386,7 +389,15 @@ suport, cu rezumatul ca legenda — de aceea captura se sterge abia dupa ce mesa complet tratat, nu imediat dupa OCR. Destinatia e `SUPPORT_JID` din `env`: `@s.whatsapp.net` pentru o persoana sau -`@g.us` pentru un grup. **Nesetat = nimeni nu e anuntat**, dar escaladarea tot se +`@g.us` pentru un grup. **JID-ul unui grup nu se vede nicaieri in WhatsApp**; se +citeste din punte, care le listeaza pe toate cu numele lor: + +```bash +curl -s localhost:8099/groups | python3 -m json.tool | grep -B1 'ROMFAST' +``` + +Grupul trebuie sa contina si numarul Mariei (`40723197939`) — altfel JID-ul nici nu +apare in lista, iar trimiterea ar esua. **Nesetat = nimeni nu e anuntat**, dar escaladarea tot se inregistreaza in `~/.maria-bridge/escalations/` si apare in dashboard, sectiunea „Trimise la suport". Jurnalul se scrie intotdeauna, si cand notificarea esueaza: altfel exact intrebarile fara raspuns ar disparea fara urma. diff --git a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/indexer.py b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/indexer.py index e4d3ac8..56c387d 100644 --- a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/indexer.py +++ b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/rag/indexer.py @@ -141,12 +141,22 @@ def _vectori_existenti() -> dict[str, list[float]]: Sursa e chiar indexul precedent, nu un al doilea fisier de cache: nu are ce sa se desincronizeze, si daca indexul lipseste se recalculeaza tot, ca inainte. + + Se refolosesc doar intrarile scrise cu modelul curent. Doua modele dau vectori + diferiti (adesea si de alta lungime) pentru acelasi text, iar un index amestecat + ar strica ordonarea fara nici un mesaj de eroare. Intrarile vechi, dinainte de + campul `model`, se trateaza ca necunoscute: se recalculeaza o singura data. """ try: vechi = json.loads(config.INDEX_FILE.read_text(encoding="utf-8")) except (OSError, ValueError): return {} - return {e["text"]: e["embedding"] for e in vechi if e.get("text") and e.get("embedding")} + model = config.get("EMBED_MODEL") + return { + e["text"]: e["embedding"] + for e in vechi + if e.get("text") and e.get("embedding") and e.get("model") == model + } def build() -> dict: @@ -154,6 +164,7 @@ def build() -> dict: warnings: list[str] = [] docs = store.documents_for_index() refolosite = _vectori_existenti() + model = config.get("EMBED_MODEL") reutilizari = 0 for doc in docs: text = store.read_document(doc["name"]) @@ -165,7 +176,8 @@ def build() -> dict: vec = embed(chunk) else: reutilizari += 1 - entries.append({"source": doc["name"], "chunk": i, "text": chunk, "embedding": vec}) + entries.append({"source": doc["name"], "chunk": i, "text": chunk, + "embedding": vec, "model": model}) config.STATE_DIR.mkdir(parents=True, exist_ok=True) # Scriere atomica: consumer-ul reciteste fisierul la 30s si ar putea prinde # un JSON pe jumatate scris daca am scrie direct peste el. diff --git a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/tests/test_store_si_chunking.py b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/tests/test_store_si_chunking.py index ec17477..11fb642 100644 --- a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/tests/test_store_si_chunking.py +++ b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/tests/test_store_si_chunking.py @@ -291,3 +291,44 @@ def test_stergerea_gaseste_documentul_in_ambele_directoare(write): assert store.delete_document("local.md") is True assert store.delete_document("drive.md") is True assert store.delete_document("inexistent.md") is False + + +def test_schimbarea_modelului_reface_embeddings(monkeypatch, write): + """Alt EMBED_MODEL = alti vectori; refolosirea lor ar amesteca tacut doua modele.""" + import config + import indexer + + write("doc.md", "acelasi text, alt model") + monkeypatch.setattr(indexer, "embed", lambda chunk: [1.0]) + indexer.build() + + calculate = [] + monkeypatch.setattr(indexer, "embed", lambda chunk: calculate.append(chunk) or [2.0]) + monkeypatch.setattr(config, "get", lambda k, d=None: "alt-model" + if k == "EMBED_MODEL" else config.DEFAULTS.get(k, d)) + + rezultat = indexer.build() + assert calculate == ["acelasi text, alt model"] + assert rezultat["embeddings_refolosite"] == 0 + + +def test_indexul_fara_camp_model_se_recalculeaza_o_data(monkeypatch, write): + """Intrarile scrise inainte de campul `model` nu se pot atribui unui model.""" + import json + import config + import indexer + + write("doc.md", "text vechi") + config.STATE_DIR.mkdir(parents=True, exist_ok=True) + config.INDEX_FILE.write_text(json.dumps( + [{"source": "doc.md", "chunk": 0, "text": "text vechi", "embedding": [9.0]}]), + encoding="utf-8") + + calculate = [] + monkeypatch.setattr(indexer, "embed", lambda chunk: calculate.append(chunk) or [1.0]) + assert indexer.build()["embeddings_refolosite"] == 0 + assert calculate == ["text vechi"] + + calculate.clear() + assert indexer.build()["embeddings_refolosite"] == 1 + assert calculate == [] diff --git a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/whatsapp/index.js b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/whatsapp/index.js index 306e145..cef4041 100644 --- a/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/whatsapp/index.js +++ b/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/whatsapp/index.js @@ -418,6 +418,25 @@ app.post('/react', async (req, res) => { } }); +// Grupurile din care face parte contul, cu JID-ul lor. WhatsApp nu arata nicaieri +// JID-ul unui grup ("120363...@g.us"), iar el e singurul mod de a-l scrie in +// SUPPORT_JID: de aici se citeste, o data, cand se leaga escaladarile de un grup. +app.get('/groups', async (_req, res) => { + if (!connected || !sock) { + return res.status(503).json({ ok: false, error: 'not connected to WhatsApp' }); + } + try { + const all = await sock.groupFetchAllParticipating(); + const groups = Object.values(all) + .map((g) => ({ jid: g.id, subject: g.subject, participants: g.participants?.length || 0 })) + .sort((a, b) => (a.subject || '').localeCompare(b.subject || '')); + res.json({ ok: true, groups }); + } catch (err) { + console.error('[whatsapp] Group fetch failed:', err.message); + res.status(500).json({ ok: false, error: err.message }); + } +}); + app.get('/messages', (_req, res) => { const messages = messageQueue.splice(0); res.json({ messages });