feat(maria): depozit local, in afara oglinzii Drive
Dictionarul de erori Oracle pus in `documents/` ar fi disparut in 10 minute: `rclone sync` sterge de acolo tot ce nu exista in Drive. Aceeasi problema o avea si butonul de adaugare document din dashboard — documentul traia pana la urmatorul tur de sincronizare, ceea ce README-ul mentiona ca pe o ciudatenie, nu ca pe un bug. Acum sunt doua directoare cu un singur spatiu de nume: `documents/` (oglinda Drive) si `documents-local/` (ce nu vine din Drive). La acelasi nume castiga Drive-ul — e sursa comuna a echipei, iar copia locala poate fi o versiune veche uitata acolo. Dashboard-ul scrie in cel local si marcheaza documentele „local". Recalibrat cu dictionarul indexat (169 chunk-uri): 19/19, cele patru intrebari Oracle noi ies la 0,73-0,75. Reindexarea a durat cat cele 29 de chunk-uri noi, nu cat toate 169 — refolosirea vectorilor isi face treaba (log: "29 embeddings noi, 140 refolosite"). Verificat pe canalul viu: o escaladare cu captura chiar pleaca pe WhatsApp, imagine + rezumat + referinta, si apare in dashboard. 73 pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
This commit is contained in:
@@ -17,6 +17,10 @@ _DEFAULT_DIR = pathlib.Path.home() / ".maria-bridge"
|
||||
|
||||
STATE_DIR: pathlib.Path = pathlib.Path(os.environ.get("MARIA_BRIDGE_DIR") or _DEFAULT_DIR)
|
||||
DOCS_DIR: pathlib.Path = STATE_DIR / "documents"
|
||||
# Documente care NU vin din Drive. `rclone sync` sterge din DOCS_DIR tot ce nu
|
||||
# exista in Drive, deci orice fisier pus acolo de mana dispare la urmatorul tur.
|
||||
# Ce sta aici e in afara oglinzii si supravietuieste.
|
||||
DOCS_LOCAL_DIR: pathlib.Path = STATE_DIR / "documents-local"
|
||||
INDEX_FILE: pathlib.Path = STATE_DIR / "rag_index.json"
|
||||
LOCK_FILE: pathlib.Path = STATE_DIR / ".rag.lock"
|
||||
LOG_DIR: pathlib.Path = STATE_DIR / "logs"
|
||||
@@ -87,11 +91,12 @@ def parse_env(text: str) -> dict[str, str]:
|
||||
|
||||
def reload(base_dir: str | os.PathLike | None = None) -> dict[str, str]:
|
||||
"""Recalculeaza caile si reciteste env-ul. Returneaza dictionarul incarcat."""
|
||||
global STATE_DIR, DOCS_DIR, INDEX_FILE, LOCK_FILE, LOG_DIR, ENV_FILE, AUTH_DIR, _env
|
||||
global STATE_DIR, DOCS_DIR, DOCS_LOCAL_DIR, INDEX_FILE, LOCK_FILE, LOG_DIR, ENV_FILE, AUTH_DIR, _env
|
||||
if base_dir is None:
|
||||
base_dir = os.environ.get("MARIA_BRIDGE_DIR") or _DEFAULT_DIR
|
||||
STATE_DIR = pathlib.Path(base_dir)
|
||||
DOCS_DIR = STATE_DIR / "documents"
|
||||
DOCS_LOCAL_DIR = STATE_DIR / "documents-local"
|
||||
INDEX_FILE = STATE_DIR / "rag_index.json"
|
||||
LOCK_FILE = STATE_DIR / ".rag.lock"
|
||||
LOG_DIR = STATE_DIR / "logs"
|
||||
|
||||
@@ -4,6 +4,14 @@ Fisiere text (.txt/.md/.xml) sub STATE_DIR/documents/, un singur nivel (fara
|
||||
subdirectoare), ca numele afisat in dashboard sa fie neambiguu si sa poata servi
|
||||
direct ca parametru de request fara riscuri de traversare de cale.
|
||||
|
||||
DOUA DIRECTOARE, un singur spatiu de nume:
|
||||
documents/ oglinda Google Drive. `rclone sync` STERGE de aici tot ce nu
|
||||
exista in Drive — nu pune nimic de mana.
|
||||
documents-local/ documente care nu vin din Drive (dictionare proprii, ce se
|
||||
adauga din dashboard). Sincronizarea nu se atinge de ele.
|
||||
Cand acelasi nume apare in ambele, castiga cel din Drive: e sursa comuna a
|
||||
echipei, iar copia locala ar putea fi o versiune veche uitata acolo.
|
||||
|
||||
Cand acelasi document exista in mai multe formate (`d406_saft_knowledge.xml` si
|
||||
`d406_saft_knowledge.md`), la indexare intra UNUL SINGUR, cel cu formatul cel mai
|
||||
bogat: .xml > .md > .txt. Sursele .xml sunt structurate pe probleme
|
||||
@@ -38,13 +46,32 @@ def validate_name(name: str) -> str:
|
||||
return name
|
||||
|
||||
|
||||
def _all_files() -> list[pathlib.Path]:
|
||||
"""Fisierele din ambele directoare. La acelasi nume, cel din Drive castiga."""
|
||||
vazute: dict[str, pathlib.Path] = {}
|
||||
for d in (config.DOCS_LOCAL_DIR, config.DOCS_DIR): # Drive ultimul = suprascrie
|
||||
if not d.is_dir():
|
||||
continue
|
||||
for f in sorted(d.glob("*")):
|
||||
if f.is_file() and f.suffix in DOC_EXTENSIONS:
|
||||
vazute[f.name] = f
|
||||
return [vazute[n] for n in sorted(vazute)]
|
||||
|
||||
|
||||
def path_for(name: str) -> pathlib.Path | None:
|
||||
"""Unde e documentul, in oricare din cele doua directoare."""
|
||||
name = validate_name(name)
|
||||
for d in (config.DOCS_DIR, config.DOCS_LOCAL_DIR):
|
||||
p = d / name
|
||||
if p.is_file():
|
||||
return p
|
||||
return None
|
||||
|
||||
|
||||
def list_documents() -> list[dict]:
|
||||
"""Tot ce e in depozit. `shadowed_by` spune de ce un fisier nu ajunge in index."""
|
||||
config.DOCS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
files = [
|
||||
f for f in sorted(config.DOCS_DIR.glob("*"))
|
||||
if f.is_file() and f.suffix in DOC_EXTENSIONS
|
||||
]
|
||||
files = _all_files()
|
||||
# castigatorul per nume de baza: formatul cel mai bogat
|
||||
winners: dict[str, str] = {}
|
||||
for f in files:
|
||||
@@ -60,6 +87,7 @@ def list_documents() -> list[dict]:
|
||||
"name": f.name,
|
||||
"size": st.st_size,
|
||||
"mtime": st.st_mtime,
|
||||
"local": f.parent == config.DOCS_LOCAL_DIR,
|
||||
"shadowed_by": None if winner == f.name else winner,
|
||||
})
|
||||
return out
|
||||
@@ -71,20 +99,27 @@ def documents_for_index() -> list[dict]:
|
||||
|
||||
|
||||
def read_document(name: str) -> str:
|
||||
name = validate_name(name)
|
||||
return (config.DOCS_DIR / name).read_text(encoding="utf-8")
|
||||
path = path_for(name)
|
||||
if path is None:
|
||||
raise FileNotFoundError(name)
|
||||
return path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def write_document(name: str, content: str) -> None:
|
||||
"""Scrie in documents-local/, NU in oglinda Drive.
|
||||
|
||||
Un document scris in `documents/` dispare la urmatorul `rclone sync` — asta
|
||||
se intampla pana acum cu tot ce se adauga din dashboard.
|
||||
"""
|
||||
name = validate_name(name)
|
||||
config.DOCS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
(config.DOCS_DIR / name).write_text(content, encoding="utf-8")
|
||||
config.DOCS_LOCAL_DIR.mkdir(parents=True, exist_ok=True)
|
||||
(config.DOCS_LOCAL_DIR / name).write_text(content, encoding="utf-8")
|
||||
|
||||
|
||||
def delete_document(name: str) -> bool:
|
||||
name = validate_name(name)
|
||||
path = config.DOCS_DIR / name
|
||||
if not path.exists():
|
||||
"""Sterge documentul de unde se afla. Cel din Drive revine la sincronizare."""
|
||||
path = path_for(name)
|
||||
if path is None:
|
||||
return False
|
||||
path.unlink()
|
||||
return True
|
||||
|
||||
@@ -32,10 +32,11 @@ def _fingerprint() -> str:
|
||||
doar cand s-a schimbat efectiv ceva, nu la fiecare tur de sincronizare."""
|
||||
config.DOCS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
h = hashlib.sha256()
|
||||
for f in sorted(config.DOCS_DIR.glob("*")):
|
||||
if f.is_file() and f.suffix in store.DOC_EXTENSIONS:
|
||||
st = f.stat()
|
||||
h.update(f"{f.name}:{st.st_mtime_ns}:{st.st_size}\n".encode())
|
||||
# amandoua directoarele: si un document local adaugat din dashboard trebuie sa
|
||||
# declanseze reindexarea, nu doar ce vine din Drive
|
||||
for f in store._all_files():
|
||||
st = f.stat()
|
||||
h.update(f"{f.name}:{st.st_mtime_ns}:{st.st_size}\n".encode())
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user