2 Commits

3 changed files with 198 additions and 39 deletions

1
.streamlit/secrets.toml Normal file
View File

@@ -0,0 +1 @@
[default]

View File

@@ -5,6 +5,7 @@ Seiten: Übersicht, Engramme, Suche, Graph, Heal-Log, Neural Scorer.
import json
import sys
import os
from pathlib import Path
import streamlit as st
@@ -25,33 +26,22 @@ _DEFAULT_DB = _root / "data" / "brain.sqlite"
@st.cache_resource
class _LazyDB:
"""Lazy-Initialisierung damit st.secrets erst bei Bedarf gelesen wird."""
_store = None
_chroma = None
@staticmethod
def store():
if _LazyDB._store is None:
db = str(_DEFAULT_DB)
try:
db = st.secrets.get("db_path", str(_DEFAULT_DB))
except Exception:
pass
_LazyDB._store = EngramStore(db)
return _LazyDB._store
@staticmethod
def chroma():
if _LazyDB._chroma is None:
p = Path(str(_DEFAULT_DB)).parent / "chroma"
_LazyDB._chroma = ChromaStore(str(p))
return _LazyDB._chroma
def _store():
return EngramStore(str(_DEFAULT_DB))
@st.cache_resource
def _chroma():
p = Path(str(_DEFAULT_DB)).parent / "chroma"
return ChromaStore(str(p))
_retriever_cache = None
def _retriever():
return Retriever(_LazyDB.store(), _LazyDB.chroma())
global _retriever_cache
if _retriever_cache is None:
_retriever_cache = Retriever(_store(), _chroma())
return _retriever_cache
@st.cache_resource
@@ -61,18 +51,18 @@ def _scorer():
@st.cache_resource
def _healer():
return ErrorHealer(_LazyDB.store())
return ErrorHealer(_store())
st.set_page_config(page_title="Second Brain Dashboard", layout="wide")
st.title("🧠 2.Brain v0.3.0")
st.title("🧠 2.Brain v0.3.1")
page = st.sidebar.radio("Seite", ["Übersicht", "Engramme", "Suche", "Graph", "Heal-Log", "Neural Scorer"])
if page == "Übersicht":
store = _LazyDB.store()
engrams = store.get_all(limit=1000)
store = _store()
engrams = store.get_all(limit=10000)
confirmed = sum(1 for e in engrams if e.correctness.confirmed)
unconfirmed = len(engrams) - confirmed
avg_conf = sum(e.compute_confidence() for e in engrams) / max(1, len(engrams))
@@ -100,12 +90,12 @@ if page == "Übersicht":
if st.button("Auto-Fix", key=f"af_{eg.id}"):
eg.auto_fix_grounding()
store.save(eg)
st.success("Fixed!")
st.experimental_rerun()
elif page == "Engramme":
store = _LazyDB.store()
st.subheader("Alle Engramme")
store = _store()
st.subheader("Alle Engramme (max 1000)")
tag_filter = st.text_input("Filter tags")
source_filter = st.selectbox("Source", ["alle", "user", "agent", "web", "file", "system"])
for eg in store.get_all(limit=1000):
@@ -135,34 +125,37 @@ elif page == "Engramme":
elif page == "Suche":
st.subheader("Hybrid Search (Semantic + Keyword)")
query = st.text_input("Query")
query = st.text_input("Query", placeholder="Suchbegriff eingeben...")
mode = st.radio("Modus", ["Hybrid", "Keyword", "Semantic"], horizontal=True)
if st.button("Suchen") and query:
ret = _retriever()
results = ret.hybrid_retrieve(query, limit=10) if mode == "Hybrid" else \
ret.semantic_retrieve(query, limit=10) if mode == "Semantic" else \
ret.retrieve(query, limit=10)
if not results:
st.info("Keine Ergebnisse gefunden.")
for r in results:
eg = r["engram"]
with st.container():
st.markdown(f"**{eg.content[:200]}...**")
st.write(f"Score: {r['score']:.3f} | Match: {r['match_type']} | Conf: {eg.compute_confidence():.2f}")
st.write(f"Score: `{r['score']:.3f}` | Match: `{r['match_type']}` | Conf: `{eg.compute_confidence():.2f}`")
c1, c2 = st.columns(2)
if c1.button("✅ Confirm", key=f"sc_{eg.id}"):
eg.correctness.confirm("user")
_LazyDB.store().save(eg)
c1.success("Confirmed")
_store().save(eg)
st.success("Confirmed")
if c2.button("❌ Reject", key=f"sr_{eg.id}"):
eg.correctness.reject("user")
_LazyDB.store().save(eg)
c2.warning("Rejected")
_store().save(eg)
st.warning("Rejected")
elif page == "Graph":
st.subheader("Graph-Visualisierung")
graph_html_path = Path(str(_DEFAULT_DB)).parent / "graph_view.html"
if st.button("Graph neu generieren"):
path = generate_graph_html(_LazyDB.store(), str(graph_html_path))
with st.spinner("Generiere Graph..."):
path = generate_graph_html(_store(), str(graph_html_path))
st.success(f"Graph generiert: {path}")
if graph_html_path.exists():
with open(graph_html_path, "r", encoding="utf-8") as f:
@@ -199,7 +192,7 @@ elif page == "Heal-Log":
elif page == "Neural Scorer":
st.subheader("Neural Scorer Training")
scorer = _scorer()
store = _LazyDB.store()
store = _store()
engrams = store.get_all(limit=10000)
labeled = [e for e in engrams if e.correctness.confirmed or e.correctness.rejections > 0]
st.write(f"Labelled Engramme: **{len(labeled)}**")
@@ -207,7 +200,7 @@ elif page == "Neural Scorer":
if len(labeled) < 2:
st.error("Mindestens 2 labelierte Engramme nötig (confirm + reject).")
else:
with st.spinner("Training..."):
with st.spinner("Training läuft..."):
result = scorer.train(labeled, epochs=30)
st.json(result)
st.success("Training abgeschlossen!")

165
src/proactive_search.py Normal file
View File

@@ -0,0 +1,165 @@
#!/usr/bin/env python3
"""
proactive_search.py - Proaktive Websuche für Second Brain.
Sucht relevante Themen, speichert Ergebnisse als Engramme.
Stoppt wenn neue Aufgaben erkannt werden.
"""
import sys
import json
from pathlib import Path
from datetime import datetime, timezone, timedelta
from typing import List, Dict, Any, Optional
sys.path.insert(0, str(Path(__file__).resolve().parent))
from src.store import EngramStore
from src.engram import Engram, Grounding
from src.retriever import Retriever
from src.embedder import encode
from src.chroma_store import ChromaStore
DB_PATH = Path(__file__).resolve().parent.parent / "data" / "brain.sqlite"
CHROMA_PATH = Path(__file__).resolve().parent.parent / "data" / "chroma"
# Themen die relevant sind für den Benutzer
INTEREST_TOPICS = [
"OpenClaw AI Agent",
"Künstliche Intelligenz Trends 2025",
"Second Brain Memory System",
"Automation DIY Projects",
"Smart Home IoT",
"Raspberry Pi Projects",
"Deutschland Tech News",
"AI Agent Frameworks",
"Workflow Automation",
]
def get_store():
return EngramStore(str(DB_PATH))
def load_state() -> Dict[str, Any]:
"""Lädt den Such-Zustand."""
state_path = Path(__file__).resolve().parent.parent / "data" / "search_state.json"
if state_path.exists():
with open(state_path, "r", encoding="utf-8") as f:
return json.load(f)
return {
"last_search": None,
"searched_topics": [],
"new_tasks_detected": False,
"paused_until": None,
}
def save_state(state: Dict[str, Any]):
state_path = Path(__file__).resolve().parent.parent / "data" / "search_state.json"
with open(state_path, "w", encoding="utf-8") as f:
json.dump(state, f, ensure_ascii=False)
def check_for_new_tasks(store: EngramStore) -> bool:
"""Prüft ob in letzten 2h neue Aufgaben-Artige Engramme erstellt wurden."""
now = datetime.now(timezone.utc)
recent = now - timedelta(hours=2)
egs = store.get_all(limit=1000)
for eg in egs:
created_str = eg.metadata.get("created", "")
if not created_str:
continue
try:
eg_time = datetime.fromisoformat(created_str)
if eg_time.tzinfo is None:
eg_time = eg_time.replace(tzinfo=timezone.utc)
if eg_time > recent:
tags = eg.metadata.get("tags", [])
if "task" in tags or "aufgabe" in tags or "todo" in tags:
return True
except Exception:
pass
return False
def try_web_search(topic: str) -> Optional[List[Dict[str, str]]]:
"""Web-Suche via OpenClaw."""
try:
import subprocess
result = subprocess.run(
["python3", "-c", f"""
import sys
sys.path.insert(0, '/root/.openclaw/workspace/second-brain/src')
from src.retriever import Retriever
from src.store import EngramStore
store = EngramStore('data/brain.sqlite')
ret = Retriever(store)
results = ret.retrieve('{topic}')
print('FOUND ' + str(len(results)))
"""],
capture_output=True,
text=True,
timeout=30,
cwd="/root/.openclaw/workspace/second-brain",
)
# Actually do web search
print(f"[search] Would search: {topic}")
return None # Placeholder: real search would be here
except Exception as e:
print(f"[search] Error: {e}")
return None
def run_proactive_search():
"""Haupt-Funktion für proaktive Suche."""
store = get_store()
state = load_state()
now = datetime.now(timezone.utc)
# Check: Neue Aufgaben?
if check_for_new_tasks(store):
state["new_tasks_detected"] = True
state["paused_until"] = (now + timedelta(hours=4)).isoformat()
save_state(state)
print("🛑 Neue Aufgaben erkannt. Suche pausiert für 4h.")
return
# Check: Pausiert?
if state.get("paused_until"):
paused = datetime.fromisoformat(state["paused_until"])
if now < paused:
print(f"⏸️ Suche pausiert bis {state['paused_until']}")
return
else:
state["paused_until"] = None
state["new_tasks_detected"] = False
# Thema auswählen (Round-Robin)
searched = set(state.get("searched_topics", []))
remaining = [t for t in INTEREST_TOPICS if t not in searched]
if not remaining:
remaining = INTEREST_TOPICS
searched = set()
topic = remaining[0]
print(f"🔍 Suche: {topic}")
# Als Engramm speichern (als "Suchanfrage", nicht als Faktum)
eg = Engram.create(
content=f"Proaktive Web-Suche: {topic}\nStatus: Geplant",
source="agent",
tags=["proactive", "search", "planned"],
confidence=0.3,
grounding=Grounding.ASSUMPTION,
)
store.save(eg)
state["last_search"] = now.isoformat()
state["searched_topics"] = list(searched | {topic})
save_state(state)
print(f"✅ Such-Engramm gespeichert: {eg.id}")
if __name__ == "__main__":
run_proactive_search()