From d09ac617560efe9b728dcad08a4f34c84aefe760 Mon Sep 17 00:00:00 2001 From: rushdarshan Date: Sat, 4 Jul 2026 01:48:14 +0530 Subject: [PATCH 1/4] fix: add fastembed + python-dotenv to CI/CD deps --- .github/workflows/projectbrain-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/projectbrain-review.yml b/.github/workflows/projectbrain-review.yml index b76096b..aa38503 100644 --- a/.github/workflows/projectbrain-review.yml +++ b/.github/workflows/projectbrain-review.yml @@ -22,7 +22,7 @@ jobs: env: COGNEE_API_KEY: ${{ secrets.COGNEE_API_KEY }} run: | - pip install git+https://github.com/topoteretes/cognee litellm + pip install git+https://github.com/topoteretes/cognee litellm fastembed python-dotenv - name: Generate PR Diff run: | From f0af05bd39cffa28f2eccf25a704887814803c23 Mon Sep 17 00:00:00 2001 From: rushdarshan Date: Sat, 4 Jul 2026 01:49:30 +0530 Subject: [PATCH 2/4] feat: add /api/improve, week-filtered graph, week endpoint, metrics history --- api.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/api.py b/api.py index a50ded6..5ada55d 100644 --- a/api.py +++ b/api.py @@ -22,11 +22,12 @@ STATE: dict = {"nodes": [], "links": []} -async def load_graph_from_cognee(): +async def load_graph_from_cognee(week: int | None = None): """Build graph state from seed items (Cognee Cloud has no local node enumerator).""" from seed import seed_items + items = [i for i in seed_items if week is None or i["week"] <= week] nodes, links, file_ids, node_ids = [], [], {}, {} - for item in seed_items: + for item in items: nid = f"decision:{item['title'].lower().replace(' ', '-')}" node_ids[item['title']] = nid group = "incident" if "incident" in item.get("tags", []) else "decision" @@ -37,13 +38,14 @@ async def load_graph_from_cognee(): file_ids[fid] = True nodes.append({"id": fid, "name": f, "group": "file", "val": 10}) links.append({"source": nid, "target": fid, "name": "LINKS_TO"}) - for item in seed_items: + for item in items: if item.get("supersedes") and item["supersedes"] in node_ids: links.append({"source": node_ids[item["supersedes"]], "target": node_ids[item["title"]], "name": "SUPERSEDES"}) return {"nodes": nodes, "links": links} search_latencies: deque = deque(maxlen=10) search_total: int = 0 search_with_results: int = 0 +metrics_history: deque = deque(maxlen=10) async def notify_clients(): data = json.dumps(STATE) @@ -72,9 +74,17 @@ async def serve_spa(path: str): print(f"Serving static dashboard from {static_dir}") @app.get("/api/graph") -async def get_graph(): +async def get_graph(week: int | None = Query(None)): + if week is not None: + return await load_graph_from_cognee(week=week) return STATE +@app.get("/api/graph/weeks") +async def graph_weeks(): + from seed import seed_items + weeks = sorted(set(i["week"] for i in seed_items)) + return {"weeks": weeks, "current": max(weeks)} + @app.get("/api/stream") async def sse_stream(request: Request): q = asyncio.Queue() @@ -185,6 +195,17 @@ async def metrics(): "recall_precision": recall_precision, } +@app.post("/api/improve") +async def improve(): + await cognee.improve(dataset=DATASET, build_truth_subspace=True) + snapshot = await metrics() + metrics_history.append({"t": time.time(), "metrics": snapshot}) + return snapshot + +@app.get("/api/metrics/history") +async def metrics_history_endpoint(): + return list(metrics_history) + class ForgetPreview(BaseModel): query: str From 9108647d4e964aeb3cebd5069123a9e174d3bb2e Mon Sep 17 00:00:00 2001 From: rushdarshan Date: Sat, 4 Jul 2026 01:51:36 +0530 Subject: [PATCH 3/4] feat: dashboard improve button, week slider, control panel with tabs --- dashboard/src/components/GraphView.tsx | 269 ++++++++++++++++++++++--- 1 file changed, 236 insertions(+), 33 deletions(-) diff --git a/dashboard/src/components/GraphView.tsx b/dashboard/src/components/GraphView.tsx index 1977101..c640d06 100644 --- a/dashboard/src/components/GraphView.tsx +++ b/dashboard/src/components/GraphView.tsx @@ -28,6 +28,20 @@ export default function GraphView() { const [metrics, setMetrics] = useState(null); const [edgeFilter, setEdgeFilter] = useState('ALL'); const [selectedNode, setSelectedNode] = useState(null); + const [week, setWeek] = useState(null); + const [weekOptions, setWeekOptions] = useState<{weeks: number[], current: number} | null>(null); + const [improveLoading, setImproveLoading] = useState(false); + const [improveDelta, setImproveDelta] = useState(null); + const [historyData, setHistoryData] = useState([]); + const [activePanel, setActivePanel] = useState<'add' | 'activity' | 'metrics'>('metrics'); + const [addTitle, setAddTitle] = useState(''); + const [addRationale, setAddRationale] = useState(''); + const [addFiles, setAddFiles] = useState(''); + const [addTags, setAddTags] = useState(''); + const [addSupersedes, setAddSupersedes] = useState(''); + const [addLoading, setAddLoading] = useState(false); + const [addMsg, setAddMsg] = useState(null); + const [activityLog, setActivityLog] = useState<{type: string; text: string; time: number}[]>([]); useEffect(() => { const es = new EventSource(`${API}/api/stream`); @@ -60,6 +74,82 @@ export default function GraphView() { fetchMetrics(); }, [fetchMetrics]); + const logActivity = useCallback((type: string, text: string) => { + setActivityLog(prev => [{type, text, time: Date.now()}, ...prev].slice(0, 20)); + }, []); + + useEffect(() => { + fetch(`${API}/api/graph/weeks`) + .then(r => r.json()) + .then(d => { setWeekOptions(d); setWeek(d.current); }) + .catch(() => {}); + }, []); + + useEffect(() => { + fetch(`${API}/api/metrics/history`) + .then(r => r.json()) + .then(setHistoryData) + .catch(() => {}); + }, []); + + useEffect(() => { + if (week == null) return; + if (week === (weekOptions?.current ?? 8)) { + setGraphData(prev => prev); + return; + } + fetch(`${API}/api/graph?week=${week}`) + .then(r => r.json()) + .then(d => setGraphData(d)) + .catch(() => {}); + }, [week, weekOptions?.current]); + + const doImprove = useCallback(async () => { + setImproveLoading(true); + const oldRecall = metrics?.recall_precision; + try { + const r = await fetch(`${API}/api/improve`, { method: 'POST' }); + const m = await r.json(); + setMetrics(m); + if (oldRecall != null && m.recall_precision != null) { + const delta = ((m.recall_precision - oldRecall) * 100).toFixed(1); + setImproveDelta(delta.startsWith('-') ? delta : `+${delta}`); + setTimeout(() => setImproveDelta(null), 5000); + } + logActivity('improve', 'Memory strengthened'); + fetch(`${API}/api/metrics/history`).then(r => r.json()).then(setHistoryData).catch(() => {}); + } catch {} + setImproveLoading(false); + }, [metrics, logActivity]); + + const doSubmitDecision = useCallback(async () => { + if (!addTitle.trim()) return; + setAddLoading(true); + setAddMsg(null); + try { + const r = await fetch(`${API}/api/webhook/remember`, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + title: addTitle, + rationale: addRationale, + files: addFiles.split(',').map(s => s.trim()).filter(Boolean), + tags: addTags.split(',').map(s => s.trim()).filter(Boolean), + supersedes: addSupersedes.trim() || null, + }), + }); + if (r.ok) { + setAddMsg('Decision remembered!'); + setAddTitle(''); setAddRationale(''); setAddFiles(''); setAddTags(''); setAddSupersedes(''); + logActivity('add', addTitle); + setTimeout(() => setAddMsg(null), 3000); + } else { + setAddMsg('Failed to remember. Check console.'); + } + } catch { setAddMsg('Network error.'); } + setAddLoading(false); + }, [addTitle, addRationale, addFiles, addTags, addSupersedes, logActivity]); + const doSearch = useCallback(async (q: string, mode: string) => { if (!q.trim()) { setSearchResults([]); return; } setIsSearching(true); @@ -67,11 +157,12 @@ export default function GraphView() { const r = await fetch(`${API}/api/search?q=${encodeURIComponent(q)}&mode=${mode}`); const d = await r.json(); setSearchResults(d.results); + logActivity('search', `${q} (${mode})`); } catch { setSearchResults([]); } setIsSearching(false); - }, []); + }, [logActivity]); const modeRef = useRef(searchMode); modeRef.current = searchMode; @@ -197,7 +288,7 @@ export default function GraphView() { {/* Metrics row */} {metrics && ( -
+
{metrics.nodes} nodes {metrics.edges} edges recall: {(metrics.recall_precision * 100).toFixed(0)}% @@ -205,6 +296,16 @@ export default function GraphView() { {metrics.memory_composition && (Object.entries(metrics.memory_composition) as [string, number][]).map(([g, c]) => ( {g}: {c} ))} + + {improveDelta && ( + {improveDelta}pp recall + )}
)} @@ -229,6 +330,22 @@ export default function GraphView() {
+ {/* Week slider */} + {weekOptions && week != null && ( +
+ Timeline + setWeek(Number(e.target.value))} + className="flex-1 h-1.5 bg-gray-800 rounded-full appearance-none cursor-pointer accent-indigo-500" + /> + Week {week}/{weekOptions.current} +
+ )} + {/* Graph + SSE warning + Results */}
@@ -301,42 +418,128 @@ export default function GraphView() { )}
- {/* Node detail panel */} - {selectedNode && ( -
-
-

{selectedNode.name}

- -
-
-
- - {selectedNode.group || 'unknown'} + {/* Right panel — control tabs or node detail */} +
+ {selectedNode ? ( +
+
+

{selectedNode.name}

+
-
{selectedNode.id}
- - {nodeConnections.length > 0 && ( -
-

Connections ({nodeConnections.length})

-
- {nodeConnections.map((c, i) => ( -
- {c.dir === 'out' ? '→' : '←'} - {c.node?.name || 'unknown'} - {c.relation} -
- ))} +
+
+ + {selectedNode.group || 'unknown'} +
+
{selectedNode.id}
+ + {nodeConnections.length > 0 && ( +
+

Connections ({nodeConnections.length})

+
+ {nodeConnections.map((c, i) => ( +
+ {c.dir === 'out' ? '→' : '←'} + {c.node?.name || 'unknown'} + {c.relation} +
+ ))} +
+ )} +
+
+ ) : ( +
+ {/* Tabs */} +
+ {(['metrics', 'add', 'activity'] as const).map(tab => ( + + ))} +
+ + {/* Add tab */} + {activePanel === 'add' && ( +
+ setAddTitle(e.target.value)} + className="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1.5 text-gray-200 placeholder-gray-600 focus:outline-none focus:border-gray-500" /> +