Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e0f5e7e9a | |||
| 2436460b27 | |||
| 2e2cd2d228 |
1
.streamlit/secrets.toml
Normal file
1
.streamlit/secrets.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[default]
|
||||||
@@ -1,174 +1,210 @@
|
|||||||
"""
|
"""
|
||||||
app_dashboard.py - Streamlit-Dashboard für Second Brain.
|
app_dashboard.py - Streamlit-Dashboard für Second Brain.
|
||||||
Seiten: Übersicht, Engramme, Suche, Graph, Stats.
|
Seiten: Übersicht, Engramme, Suche, Graph, Heal-Log, Neural Scorer.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import streamlit as st
|
import streamlit as st
|
||||||
|
|
||||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
_root = Path(__file__).resolve().parent.parent
|
||||||
|
sys.path.insert(0, str(_root))
|
||||||
|
|
||||||
from src.engram import Engram
|
from src.engram import Engram
|
||||||
from src.store import EngramStore
|
from src.store import EngramStore
|
||||||
from src.chroma_store import ChromaStore
|
from src.chroma_store import ChromaStore
|
||||||
from src.retriever import Retriever
|
from src.retriever import Retriever
|
||||||
from src.neural_scorer import NeuralScorer
|
from src.neural_scorer import NeuralScorer
|
||||||
|
from src.graph_view import generate_graph_html
|
||||||
|
from src.loop_detector import LoopDetector
|
||||||
|
from src.error_healer import ErrorHealer
|
||||||
|
|
||||||
_DEFAULT_DB = Path(__file__).resolve().parent.parent / "data" / "brain.sqlite"
|
_DEFAULT_DB = _root / "data" / "brain.sqlite"
|
||||||
_DB_PATH = str(st.secrets.get("db_path", _DEFAULT_DB) if hasattr(st, "secrets") else _DEFAULT_DB)
|
|
||||||
|
|
||||||
|
|
||||||
|
@st.cache_resource
|
||||||
def _store():
|
def _store():
|
||||||
return EngramStore(_DB_PATH)
|
return EngramStore(str(_DEFAULT_DB))
|
||||||
|
|
||||||
|
|
||||||
|
@st.cache_resource
|
||||||
def _chroma():
|
def _chroma():
|
||||||
p = Path(_DB_PATH).parent / "chroma"
|
p = Path(str(_DEFAULT_DB)).parent / "chroma"
|
||||||
return ChromaStore(str(p))
|
return ChromaStore(str(p))
|
||||||
|
|
||||||
|
|
||||||
|
_retriever_cache = None
|
||||||
def _retriever():
|
def _retriever():
|
||||||
return Retriever(_store(), _chroma())
|
global _retriever_cache
|
||||||
|
if _retriever_cache is None:
|
||||||
|
_retriever_cache = Retriever(_store(), _chroma())
|
||||||
|
return _retriever_cache
|
||||||
|
|
||||||
|
|
||||||
|
@st.cache_resource
|
||||||
def _scorer():
|
def _scorer():
|
||||||
return NeuralScorer()
|
return NeuralScorer()
|
||||||
|
|
||||||
|
|
||||||
st.set_page_config(page_title="Second Brain Dashboard", layout="wide")
|
@st.cache_resource
|
||||||
st.title("🧠 Second Brain Dashboard")
|
def _healer():
|
||||||
|
return ErrorHealer(_store())
|
||||||
|
|
||||||
page = st.sidebar.radio("Seite", ["Übersicht", "Engramme", "Suche", "Graph", "Stats", "Neural Scorer"])
|
|
||||||
|
st.set_page_config(page_title="Second Brain Dashboard", layout="wide")
|
||||||
|
st.title("🧠 2.Brain v0.3.1")
|
||||||
|
|
||||||
|
page = st.sidebar.radio("Seite", ["Übersicht", "Engramme", "Suche", "Graph", "Heal-Log", "Neural Scorer"])
|
||||||
|
|
||||||
|
|
||||||
if page == "Übersicht":
|
if page == "Übersicht":
|
||||||
store = _store()
|
store = _store()
|
||||||
engrams = store.get_all()
|
engrams = store.get_all(limit=10000)
|
||||||
confirmed = sum(1 for e in engrams if e.correctness.confirmed)
|
confirmed = sum(1 for e in engrams if e.correctness.confirmed)
|
||||||
unconfirmed = len(engrams) - confirmed
|
unconfirmed = len(engrams) - confirmed
|
||||||
avg_conf = sum(e.compute_confidence() for e in engrams) / max(1, len(engrams))
|
avg_conf = sum(e.compute_confidence() for e in engrams) / max(1, len(engrams))
|
||||||
|
errors = [e for e in engrams if "error" in e.metadata.get("tags", [])]
|
||||||
|
|
||||||
c1, c2, c3, c4 = st.columns(4)
|
c1, c2, c3, c4, c5 = st.columns(5)
|
||||||
c1.metric("Total", len(engrams))
|
c1.metric("Total", len(engrams))
|
||||||
c2.metric("Confirmed", confirmed)
|
c2.metric("Confirmed", confirmed)
|
||||||
c3.metric("Pending", unconfirmed)
|
c3.metric("Pending", unconfirmed)
|
||||||
c4.metric("Avg Confidence", f"{avg_conf:.2f}")
|
c4.metric("Avg Confidence", f"{avg_conf:.2f}")
|
||||||
|
c5.metric("Errors", len(errors))
|
||||||
|
|
||||||
st.subheader("Recent Engramme")
|
st.subheader("Recent Engramme")
|
||||||
for eg in sorted(engrams, key=lambda e: e.metadata.get("modified", ""), reverse=True)[:5]:
|
for eg in sorted(engrams, key=lambda e: e.metadata.get("modified", ""), reverse=True)[:5]:
|
||||||
with st.expander(f"{eg.content[:80]}..."):
|
valid = eg.validate_grounding()
|
||||||
|
marker = "✅" if valid["valid"] else "⚠️"
|
||||||
|
with st.expander(f"{marker} {eg.content[:80]}..."):
|
||||||
|
st.write(f"ID: `{eg.id}`")
|
||||||
st.write(f"Source: {eg.metadata.get('source')}")
|
st.write(f"Source: {eg.metadata.get('source')}")
|
||||||
st.write(f"Confidence: {eg.compute_confidence():.2f}")
|
st.write(f"Confidence: {eg.compute_confidence():.2f}")
|
||||||
st.write(f"Confirmed: {'✅' if eg.correctness.confirmed else '❓'}")
|
st.write(f"Confirmed: {'✅' if eg.correctness.confirmed else '❓'}")
|
||||||
st.write("Tags:", ", ".join(eg.metadata.get("tags", [])))
|
st.write("Tags:", ", ".join(eg.metadata.get("tags", [])))
|
||||||
|
if not valid["valid"]:
|
||||||
|
st.warning(f"Grounding: {valid['issue']}")
|
||||||
|
if st.button("Auto-Fix", key=f"af_{eg.id}"):
|
||||||
|
eg.auto_fix_grounding()
|
||||||
|
store.save(eg)
|
||||||
|
st.experimental_rerun()
|
||||||
|
|
||||||
|
|
||||||
elif page == "Engramme":
|
elif page == "Engramme":
|
||||||
store = _store()
|
store = _store()
|
||||||
st.subheader("Alle Engramme")
|
st.subheader("Alle Engramme (max 1000)")
|
||||||
tag_filter = st.text_input("Filter tags")
|
tag_filter = st.text_input("Filter tags")
|
||||||
source_filter = st.selectbox("Source", ["alle", "user", "agent", "web", "file", "system"])
|
source_filter = st.selectbox("Source", ["alle", "user", "agent", "web", "file", "system"])
|
||||||
for eg in store.get_all():
|
for eg in store.get_all(limit=1000):
|
||||||
tags = eg.metadata.get("tags", [])
|
tags = eg.metadata.get("tags", [])
|
||||||
src = eg.metadata.get("source", "")
|
src = eg.metadata.get("source", "")
|
||||||
if tag_filter and tag_filter not in tags:
|
if tag_filter and tag_filter not in tags:
|
||||||
continue
|
continue
|
||||||
if source_filter != "alle" and source_filter != src:
|
if source_filter != "alle" and source_filter != src:
|
||||||
continue
|
continue
|
||||||
with st.expander(f"{eg.content[:100]}"):
|
col1, col2 = st.columns([4, 1])
|
||||||
st.write("Confidence:", f"{eg.compute_confidence():.2f}")
|
with col1:
|
||||||
st.write("Tags:", ", ".join(tags))
|
conf = eg.compute_confidence()
|
||||||
st.write("Source:", src)
|
marker = "✅" if conf > 0.7 else "⚠️"
|
||||||
c1, c2 = st.columns(2)
|
st.markdown(f"{marker} **{eg.content[:100]}**")
|
||||||
if c1.button("✅ Confirm", key=f"conf_{eg.id}"):
|
st.caption(f"Conf: {conf:.2f} | Tags: {', '.join(tags)} | Source: {src}")
|
||||||
|
with col2:
|
||||||
|
if st.button("✅ Confirm", key=f"conf_{eg.id}"):
|
||||||
eg.correctness.confirm("user")
|
eg.correctness.confirm("user")
|
||||||
store.save(eg)
|
store.save(eg)
|
||||||
st.success("Confirmed!")
|
st.success("Confirmed")
|
||||||
if c2.button("❌ Reject", key=f"rej_{eg.id}"):
|
if st.button("❌ Reject", key=f"rej_{eg.id}"):
|
||||||
eg.correctness.reject("user")
|
eg.correctness.reject("user")
|
||||||
store.save(eg)
|
store.save(eg)
|
||||||
st.warning("Rejected.")
|
st.warning("Rejected")
|
||||||
|
st.divider()
|
||||||
|
|
||||||
|
|
||||||
elif page == "Suche":
|
elif page == "Suche":
|
||||||
st.subheader("Semantic + Keyword 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"])
|
mode = st.radio("Modus", ["Hybrid", "Keyword", "Semantic"], horizontal=True)
|
||||||
if st.button("Suchen") and query:
|
if st.button("Suchen") and query:
|
||||||
ret = _retriever()
|
ret = _retriever()
|
||||||
if mode == "Hybrid":
|
results = ret.hybrid_retrieve(query, limit=10) if mode == "Hybrid" else \
|
||||||
results = ret.hybrid_retrieve(query, limit=10)
|
ret.semantic_retrieve(query, limit=10) if mode == "Semantic" else \
|
||||||
elif mode == "Semantic":
|
ret.retrieve(query, limit=10)
|
||||||
results = ret.semantic_retrieve(query, limit=10)
|
if not results:
|
||||||
else:
|
st.info("Keine Ergebnisse gefunden.")
|
||||||
results = ret.retrieve(query, limit=10)
|
|
||||||
for r in results:
|
for r in results:
|
||||||
eg = r["engram"]
|
eg = r["engram"]
|
||||||
with st.container():
|
with st.container():
|
||||||
st.markdown(f"**{eg.content[:200]}...**")
|
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)
|
c1, c2 = st.columns(2)
|
||||||
if c1.button("✅ Confirm", key=f"sc_{eg.id}"):
|
if c1.button("✅ Confirm", key=f"sc_{eg.id}"):
|
||||||
eg.correctness.confirm("user")
|
eg.correctness.confirm("user")
|
||||||
store = _store()
|
_store().save(eg)
|
||||||
store.save(eg)
|
|
||||||
st.success("Confirmed")
|
st.success("Confirmed")
|
||||||
if c2.button("❌ Reject", key=f"sr_{eg.id}"):
|
if c2.button("❌ Reject", key=f"sr_{eg.id}"):
|
||||||
eg.correctness.reject("user")
|
eg.correctness.reject("user")
|
||||||
store = _store()
|
_store().save(eg)
|
||||||
store.save(eg)
|
|
||||||
st.warning("Rejected")
|
st.warning("Rejected")
|
||||||
|
|
||||||
|
|
||||||
elif page == "Graph":
|
elif page == "Graph":
|
||||||
st.subheader("Graph-Visualisierung")
|
st.subheader("Graph-Visualisierung")
|
||||||
graph_html_path = Path(_DB_PATH).parent / "graph_view.html"
|
graph_html_path = Path(str(_DEFAULT_DB)).parent / "graph_view.html"
|
||||||
|
if st.button("Graph neu generieren"):
|
||||||
|
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():
|
if graph_html_path.exists():
|
||||||
with open(graph_html_path, "r", encoding="utf-8") as f:
|
with open(graph_html_path, "r", encoding="utf-8") as f:
|
||||||
html = f.read()
|
html = f.read()
|
||||||
# iframe
|
st.components.v1.html(html, height=800)
|
||||||
st.components.v1.html(html, height=800, scrolling=True)
|
|
||||||
else:
|
else:
|
||||||
st.info("Graph nicht generiert. Führe `python -m src.cli graph` aus.")
|
st.info("Graph noch nicht generiert. Klicke oben.")
|
||||||
if st.button("Graph generieren"):
|
|
||||||
from src.graph_view import generate_graph_html
|
|
||||||
store = _store()
|
|
||||||
path = generate_graph_html(store, str(Path(_DB_PATH).parent / "graph_view.html"))
|
|
||||||
st.success(f"Graph generiert: {path}")
|
|
||||||
|
|
||||||
|
|
||||||
elif page == "Stats":
|
elif page == "Heal-Log":
|
||||||
store = _store()
|
st.subheader("Error Healing & Loop Detection")
|
||||||
engrams = store.get_all()
|
healer = _healer()
|
||||||
st.json({
|
stats = healer.get_error_stats()
|
||||||
"total": len(engrams),
|
c1, c2, c3 = st.columns(3)
|
||||||
"confirmed": sum(1 for e in engrams if e.correctness.confirmed),
|
c1.metric("Total Errors", stats["total_errors"])
|
||||||
"pending": sum(1 for e in engrams if not e.correctness.confirmed),
|
c2.metric("Repeated", stats["repeated_errors"])
|
||||||
"sources": {s: sum(1 for e in engrams if e.metadata.get("source") == s) for s in {e.metadata.get("source") for e in engrams}},
|
c3.metric("Error Types", len(stats.get("error_types", {})))
|
||||||
"tags": {t: sum(1 for e in engrams for t2 in e.metadata.get("tags", []) if t2 == t) for t in {t for e in engrams for t in e.metadata.get("tags", [])}},
|
|
||||||
"avg_confidence": sum(e.compute_confidence() for e in engrams) / max(1, len(engrams)),
|
st.subheader("Error Types")
|
||||||
})
|
for etype, count in stats.get("error_types", {}).items():
|
||||||
|
st.write(f"- **{etype}**: {count}")
|
||||||
|
|
||||||
|
st.subheader("Loop-Checker")
|
||||||
|
q = st.text_input("Query")
|
||||||
|
r = st.text_input("Response")
|
||||||
|
if st.button("Check Loop") and q and r:
|
||||||
|
detector = LoopDetector()
|
||||||
|
result = detector.check(q, r)
|
||||||
|
st.json(result)
|
||||||
|
if result["loop_detected"]:
|
||||||
|
st.error(result["suggestion"])
|
||||||
|
|
||||||
|
|
||||||
elif page == "Neural Scorer":
|
elif page == "Neural Scorer":
|
||||||
st.subheader("Neural Scorer Training")
|
st.subheader("Neural Scorer Training")
|
||||||
scorer = _scorer()
|
scorer = _scorer()
|
||||||
store = _store()
|
store = _store()
|
||||||
engrams = store.get_all()
|
engrams = store.get_all(limit=10000)
|
||||||
labeled = [e for e in engrams if e.correctness.confirmed or e.correctness.rejections > 0]
|
labeled = [e for e in engrams if e.correctness.confirmed or e.correctness.rejections > 0]
|
||||||
st.write(f"Labelled Engramme: {len(labeled)}")
|
st.write(f"Labelled Engramme: **{len(labeled)}**")
|
||||||
if st.button("Train Neural Scorer"):
|
if st.button("Train Neural Scorer"):
|
||||||
if len(labeled) < 2:
|
if len(labeled) < 2:
|
||||||
st.error("Mindestens 2 labelierte Engramme nötig (confirm + reject).")
|
st.error("Mindestens 2 labelierte Engramme nötig (confirm + reject).")
|
||||||
else:
|
else:
|
||||||
|
with st.spinner("Training läuft..."):
|
||||||
result = scorer.train(labeled, epochs=30)
|
result = scorer.train(labeled, epochs=30)
|
||||||
st.json(result)
|
st.json(result)
|
||||||
st.success("Training abgeschlossen!")
|
st.success("Training abgeschlossen!")
|
||||||
|
|
||||||
if st.button("Predict All"):
|
if st.button("Predict All"):
|
||||||
for eg in engrams[:10]:
|
for eg in engrams[:20]:
|
||||||
pred = scorer.predict(eg)
|
pred = scorer.predict(eg)
|
||||||
st.write(f"{eg.content[:60]}... → {pred:.3f}")
|
st.write(f"{eg.content[:50]}... → **{pred:.3f}**")
|
||||||
|
|||||||
@@ -31,19 +31,21 @@ class ChromaStore:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _build_metadata(self, engram: Engram) -> Dict[str, Any]:
|
def _build_metadata(self, engram: Engram) -> Dict[str, Any]:
|
||||||
"""Serialisierte Metadaten für ChromaDB (nur primitives)."""
|
"""Serialisierte Metadaten für ChromaDB (nur primitiv/scalar/Str)."""
|
||||||
meta = engram.metadata.copy()
|
m = engram.metadata
|
||||||
# ChromaDB akzeptiert nur Listen/Strings/Numbers/Bools
|
safe: Dict[str, Any] = {}
|
||||||
tags = meta.pop("tags", [])
|
# Nur explizit erlaubte Felder übernehmen
|
||||||
if isinstance(tags, list):
|
safe["source"] = str(m.get("source", "agent"))
|
||||||
meta["tags"] = ",".join(str(t) for t in tags)
|
safe["confidence"] = float(m.get("confidence", 0.5))
|
||||||
meta.setdefault("source", "agent")
|
safe["grounding"] = int(m.get("grounding", 1))
|
||||||
meta.setdefault("confidence", 0.5)
|
tags = m.get("tags", [])
|
||||||
meta.setdefault("correctness", "unconfirmed")
|
safe["tags"] = ",".join(str(t) for t in tags) if isinstance(tags, list) else str(tags)
|
||||||
# Hierarchy als JSON-String
|
safe["created"] = str(m.get("created", ""))
|
||||||
if "hierarchy" in meta:
|
safe["modified"] = str(m.get("modified", ""))
|
||||||
meta["hierarchy"] = json.dumps(meta["hierarchy"])
|
safe["access_count"] = int(m.get("access_count", 0))
|
||||||
return meta
|
safe["correctness"] = "confirmed" if engram.correctness.confirmed else "unconfirmed"
|
||||||
|
safe["content"] = str(engram.content)[:500] # Chroma akzeptiert kurze Strings besser
|
||||||
|
return safe
|
||||||
|
|
||||||
def add(self, engram: Engram, embedding: Optional[List[float]] = None) -> None:
|
def add(self, engram: Engram, embedding: Optional[List[float]] = None) -> None:
|
||||||
"""Engramm mit Embedding zur Vektor-DB hinzufügen."""
|
"""Engramm mit Embedding zur Vektor-DB hinzufügen."""
|
||||||
|
|||||||
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