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"""
+
+
+
{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
+
-
+
-
-
-
- | Status |
- ID |
- Inhalt |
- Grounding |
- Quelle |
- Confidence |
- Feedback |
- Zugriffe |
- Tags |
-
-
-
- {''.join(rows)}
-
-
+
+ {''.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 ""