diff --git a/cron_tasks/import_context_buffer.py b/cron_tasks/import_context_buffer.py index c3aeef5..32e97f5 100644 --- a/cron_tasks/import_context_buffer.py +++ b/cron_tasks/import_context_buffer.py @@ -15,34 +15,35 @@ from pathlib import Path BRAIN_DIR = Path("/root/.openclaw/workspace/second-brain") WORKSPACE = Path("/root/.openclaw/workspace") -HANDLER = WORKSPACE / "context-buffer" / "handler.py" +CURRENT_DIR = WORKSPACE / "context-buffer" / "current" def run(): - # Hole alle Topics mit status done/completed via handler + # Lese context-buffer index.json direkt + index_path = WORKSPACE / "context-buffer" / "index.json" try: - result = subprocess.run( - ["python3", str(HANDLER), "search", "--status", "done"], - capture_output=True, text=True, timeout=30 - ) - if result.returncode != 0: - raise Exception(f"Handler error: {result.stderr}") - topics = json.loads(result.stdout) + with open(index_path) as f: + idx = json.load(f) + topics = [] + for tid, t in idx.get("topics", {}).items(): + status = t.get("status", "active") + if status in ("done", "completed"): + # Lade den vollen Inhalt aus der topic-Datei + topic_file = CURRENT_DIR / f"topic-{tid}.md" + if topic_file.exists(): + content = topic_file.read_text(encoding="utf-8") + # Entferne Frontmatter für reinen Content + if content.startswith("---"): + parts = content.split("---", 2) + if len(parts) >= 3: + content = parts[2].strip() + t["content"] = content + else: + t["content"] = "" + topics.append(t) except Exception as e: print(json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)) return - # Alternative: auch 'completed' suchen - try: - result2 = subprocess.run( - ["python3", str(HANDLER), "search", "--status", "completed"], - capture_output=True, text=True, timeout=30 - ) - if result2.returncode == 0: - topics_completed = json.loads(result2.stdout) - topics.extend(topics_completed) - except Exception: - pass - if not topics: print(json.dumps({"success": True, "imported": 0, "message": "No completed topics found"}, indent=2, ensure_ascii=False)) return diff --git a/fastapi_app.py b/fastapi_app.py index 275efc5..a09ed0c 100644 --- a/fastapi_app.py +++ b/fastapi_app.py @@ -63,7 +63,7 @@ def parse_engram(row: sqlite3.Row) -> dict: verdict = "confirmed_false" else: verdict = "unknown" - return { + result = { "id": row["id"], "content": row["content"], "confidence": meta.get("confidence", 0.0), @@ -81,6 +81,12 @@ def parse_engram(row: sqlite3.Row) -> dict: "access_count": meta.get("access_count", 0), "grounding": meta.get("grounding", 0), } + # Vorschläge aus metadata + if "link_suggestions" in meta: + result["link_suggestions"] = meta["link_suggestions"] + if "predictive_links" in meta: + result["predictive_links"] = meta["predictive_links"] + return result def _now_iso() -> str: @@ -902,6 +908,31 @@ def api_refresh(engram_id: str): return {"success": True, "new_confidence": round(conf, 2)} +@app.post("/api/links/accept") +def api_accept_link(from_id: str = Form(...), to_id: str = Form(...)): + """Erstelle einen Link zwischen zwei Engrammen (aus Vorschlag).""" + conn = get_db() + c = conn.cursor() + # Prüfe Existenz beider Engramme + for eid in (from_id, to_id): + if not c.execute("SELECT 1 FROM engrams WHERE id = ?", (eid,)).fetchone(): + conn.close() + return JSONResponse({"error": f"Engram {eid} not found"}, status_code=404) + # Vermeide Duplikate + c.execute("SELECT 1 FROM engrams_links WHERE from_id = ? AND to_id = ?", (from_id, to_id)) + if c.fetchone(): + conn.close() + return {"ok": True, "message": "link already exists"} + # Link erstellen + c.execute( + "INSERT INTO engrams_links (from_id, to_id) VALUES (?, ?)", + (from_id, to_id) + ) + conn.commit() + conn.close() + return {"ok": True} + + @app.post("/api/engrams") def api_create_engram(content: str = Form(...), tags: str = Form(""), source: str = Form("web")): engram_id = f"web-{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S-%f')[:20]}" diff --git a/systemd/openclaw-secondbrain-ingest-memory.path b/systemd/openclaw-secondbrain-ingest-memory.path index 5679b37..d61a188 100644 --- a/systemd/openclaw-secondbrain-ingest-memory.path +++ b/systemd/openclaw-secondbrain-ingest-memory.path @@ -4,7 +4,6 @@ PartOf=openclaw-secondbrain.target [Path] PathModified=/root/.openclaw/workspace/memory -DirectoryNotEmpty=true [Install] WantedBy=multi-user.target \ No newline at end of file diff --git a/templates/dashboard.html b/templates/dashboard.html index daea77d..aad5d7a 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -170,7 +170,7 @@ async function loadCards() { const data = await api(url); state.items = data.items; - renderCards(); + renderCardsWithSuggestions(); document.getElementById('pageNum').textContent = Math.floor(state.offset / state.limit) + 1; document.getElementById('btnPrev').disabled = state.offset === 0; document.getElementById('btnNext').disabled = data.items.length < state.limit; @@ -1114,6 +1114,53 @@ setInterval(() => { // ─── Init ─────────────────────────────────────────────────────────────────── loadStats(); loadCards(); +function renderCardsWithSuggestions() { + const el = document.getElementById('cards'); + el.innerHTML = state.items.map(item => { + const suggestions = (item.link_suggestions || []).concat(item.predictive_links || []); + const suggHtml = suggestions.length ? suggestions.map(s => ` +