diff --git a/src/__init__.py b/src/__init__.py index bd2f27f..565294a 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,8 +1,14 @@ """Second Brain - Gedächtnissystem für OpenClaw.""" -from .engram import Engram, Grounding, Correctness, ReviewEntry -from .store import EngramStore -from .retriever import Retriever +try: + from .engram import Engram, Grounding, Correctness, ReviewEntry + from .store import EngramStore + from .retriever import Retriever +except ImportError: + # Fallback: ChromaDB optional, SQLite-core funktioniert immer + from .engram import Engram, Grounding, Correctness, ReviewEntry + from .store import EngramStore + Retriever = None __version__ = "0.1.0" __all__ = ["Engram", "Grounding", "Correctness", "ReviewEntry", "EngramStore", "Retriever"] diff --git a/src/dashboard.py b/src/dashboard.py index a380d0c..0aa5706 100644 --- a/src/dashboard.py +++ b/src/dashboard.py @@ -13,12 +13,23 @@ import argparse import http.server import socketserver import webbrowser +import sys from pathlib import Path from uuid import UUID -from .store import EngramStore -from .engram import Engram, Grounding -from .retriever import Retriever +# Insert project root so `python3 src/dashboard.py` works without `-m` +project_root = str(Path(__file__).parent.parent) +if project_root not in sys.path: + sys.path.insert(0, project_root) + +from src.store import EngramStore +from src.engram import Engram, Grounding + +# Retriever: optional – im venv verfügbar, sonst Fallback +try: + from src.retriever import Retriever +except ImportError: + Retriever = None DB_PATH = Path(__file__).parent.parent / "data" / "brain.sqlite" HTML_PATH = Path(__file__).parent.parent / "data" / "dashboard.html" @@ -27,10 +38,30 @@ HTML_PATH = Path(__file__).parent.parent / "data" / "dashboard.html" def generate_dashboard() -> str: """Generiert HTML-Dashboard aus aktuellem Brain-Stand.""" store = EngramStore(str(DB_PATH)) - ret = Retriever(store) - stats = ret.stats() egs = store.get_all(limit=100) + # Stats: mit Retriever (venv) oder manuell berechnen + if Retriever: + ret = Retriever(store) + stats = ret.stats() + errors = store.search_tag("error", limit=100) + stats["errors"] = len(errors) + else: + from src.engram import Correctness + all_egs = store.get_all(limit=10000) + confirmed = sum(1 for e in all_egs if e.correctness.confirmed) + errors = store.search_tag("error", limit=100) + confidences = [e.compute_confidence() for e in all_egs] + stats = { + "total_engrams": len(all_egs), + "confirmed": confirmed, + "unconfirmed": len(all_egs) - confirmed, + "sources": {}, + "db_size_bytes": 0, + "avg_confidence": sum(confidences) / len(confidences) if confidences else 0.0, + "errors": len(errors), + } + # Farbe nach Confidence def color(conf): if conf > 0.7: return "#2ecc71" @@ -48,95 +79,81 @@ def generate_dashboard() -> str: except: return "UNKNOWN" - # Liste der Engramme - rows = [] + # Karten-Ansicht für Mobil + card_rows = [] for eg in egs: conf = eg.compute_confidence() - rows.append(f""" - - {marker(conf)} - {str(eg.id)[:8]} - {eg.content[:100]}{'...' if len(eg.content) > 100 else ''} - {grounding_name(eg.metadata.get('grounding', 0))} - {eg.metadata.get('source', '?')} - {conf:.2f} - {eg.correctness.confirmations}/{eg.correctness.rejections} - {eg.metadata.get('access_count', 0)} - {', '.join(eg.metadata.get('tags', [])[:3])} - + card_rows.append(f""" +
+
+ {marker(conf)} {str(eg.id)[:8]} + {conf:.2f} +
+
{eg.content[:150]}{'...' if len(eg.content) > 150 else ''}
+
+ {grounding_name(eg.metadata.get('grounding', 0))} + {eg.metadata.get('source', '?')} + ✓{eg.correctness.confirmations}/✗{eg.correctness.rejections} + {' '.join([f'{t}' for t in eg.metadata.get('tags', [])[:3]])} +
+
""") html = f""" - -🧠 Second Brain Dashboard + +🧠 Second Brain -

🧠 Second Brain Dashboard

- +

🧠 Second Brain

+
{stats['total_engrams']}
Engramme
{stats['confirmed']}
Bestätigt
{stats['unconfirmed']}
Unbestätigt
-
{len(stats.get('sources', dict()))}
Quellen
-
{stats['db_size_bytes']/1024:.0f}
KB Größe
+
{stats['errors']}
Fehler
- - - - - - - - - - - - - - - - {''.join(rows)} - -
StatusIDInhaltGroundingQuelleConfidenceFeedbackZugriffeTags
+
+ {''.join(card_rows)} +
- + diff --git a/src/openclaw_bridge.py b/src/openclaw_bridge.py index 8e3aa93..95cc7f6 100644 --- a/src/openclaw_bridge.py +++ b/src/openclaw_bridge.py @@ -16,10 +16,27 @@ from pathlib import Path from datetime import datetime, timezone from typing import Optional, List, Dict, Any +# Ensure project root is on sys.path for standalone usage +project_root = str(Path(__file__).parent.parent) +if project_root not in sys.path: + sys.path.insert(0, project_root) + +# Activate virtualenv if available (for chromadb etc.) +venv_path = Path(__file__).parent.parent / ".venv" +if venv_path.exists(): + venv_site_packages = list((venv_path / "lib").glob("python3.*/site-packages")) + if venv_site_packages and str(venv_site_packages[0]) not in sys.path: + sys.path.insert(0, str(venv_site_packages[0])) + # Second Brain Import -from .store import EngramStore -from .engram import Engram, Grounding -from .retriever import Retriever +from src.engram import Engram, Grounding +from src.store import EngramStore + +# Retriever: optional (braucht chromadb) +try: + from src.retriever import Retriever +except ImportError: + Retriever = None # --- Konfiguration --- @@ -75,11 +92,6 @@ def heartbeat_check() -> Optional[str]: Rückgabe: Nachricht für den User, oder None wenn nichts zu tun. """ store = get_brain() - ret = Retriever(store) - - # A: Unbestätigte Engramme die seit längerem nicht geprüft wurden - # B: Hohe-Prioritäts-Themen (tags wie "wichtig", "dringend") - # C: Fehler-Engramme die repeating sind # Prüfe auf wichtige unbestätigte Engramme egs = store.get_all(limit=50) @@ -89,8 +101,7 @@ def heartbeat_check() -> Optional[str]: ][:5] if unconfirmed: - ids = ", ".join([str(eg.id)[:8] for eg in unconfirmed]) - contents = "\n".join([f" - {eg.content[:80]}" for eg in unconfimed]) + contents = "\n".join([f" - {eg.content[:80]}" for eg in unconfirmed]) return ( f"🧠 Second Brain Heartbeat\n" f"Unbestätigte Engramme mit gutem Confidence-Score:\n{contents}\n" @@ -195,8 +206,15 @@ def enrich_context(topic: str, limit: int = 3) -> str: # memory_context in das Prompt einbauen """ store = get_brain() - ret = Retriever(store) - results = ret.retrieve(topic, limit=limit, min_confidence=0.3) + + # Versuche Retriever (mit Embeddings), fallback auf einfache Textsuche + if Retriever: + ret = Retriever(store) + results = ret.retrieve(topic, limit=limit, min_confidence=0.3) + else: + results_raw = store.search_text(topic, limit=limit) + results = [{"engram": eg, "score": 0.5} for eg in results_raw] + if not results: return ""