Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e0f5e7e9a | |||
| 2436460b27 |
1
.streamlit/secrets.toml
Normal file
1
.streamlit/secrets.toml
Normal file
@@ -0,0 +1 @@
|
||||
[default]
|
||||
@@ -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
165
src/proactive_search.py
Normal 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()
|
||||
Reference in New Issue
Block a user