Updated the brainmap
This commit is contained in:
parent
6067337dc8
commit
c082a9882b
@ -3,19 +3,23 @@ import os
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
CONTEXT_FILE = "data/memory/context.json"
|
||||
CONTEXT_PATH = "data/memory/context.json"
|
||||
MAX_MEMORY = 100
|
||||
|
||||
|
||||
def load_context() -> List[dict]:
|
||||
if os.path.exists(CONTEXT_FILE):
|
||||
with open(CONTEXT_FILE, "r", encoding="utf-8") as f:
|
||||
def load_context():
|
||||
if not os.path.exists(CONTEXT_PATH):
|
||||
return []
|
||||
try:
|
||||
with open(CONTEXT_PATH, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
return []
|
||||
except json.JSONDecodeError:
|
||||
print("[Context] Corrupted context.json. Returning empty list.")
|
||||
return []
|
||||
|
||||
|
||||
def save_context(mem: List[dict]):
|
||||
with open(CONTEXT_FILE, "w", encoding="utf-8") as f:
|
||||
with open(CONTEXT_PATH, "w", encoding="utf-8") as f:
|
||||
json.dump(mem[-MAX_MEMORY:], f, indent=2)
|
||||
|
||||
|
||||
|
@ -9,13 +9,12 @@ from model.journal import read_journal_entries
|
||||
from model.dreams import load_dreams
|
||||
from model.tokenizer import Tokenizer
|
||||
from model.abstraction import cluster_vocab
|
||||
from model.dreams import load_dreams
|
||||
from model.scheduler import get_time_until_next_action, get_next_action_label
|
||||
from context.context import load_context
|
||||
from reader.reader import get_books, load_progress
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
app = Flask(__name__, static_folder="static")
|
||||
tokenizer = Tokenizer()
|
||||
|
||||
next_cycle_time = time.time() + 900 # Example: 15 minutes from now
|
||||
@ -128,7 +127,7 @@ def brainmap():
|
||||
print(f"[Dashboard] Failed to load brainmap cache: {e}")
|
||||
nodes, links = [], []
|
||||
|
||||
return render_template("brainmap.html", nodes=json.dumps(nodes), links=json.dumps(links))
|
||||
return render_template("brainmap.html", nodes=nodes, links=links)
|
||||
|
||||
|
||||
@app.route("/journal")
|
||||
|
@ -2,16 +2,14 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="60">
|
||||
<title>Ruby's Brain Map</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/force-graph"></script>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: #121212;
|
||||
color: #e0e0e0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
font-family: 'Segoe UI', Tahoma, sans-serif;
|
||||
}
|
||||
#graph {
|
||||
width: 100vw;
|
||||
@ -21,21 +19,37 @@
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
background-color: #1e1e1e;
|
||||
padding: 10px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
padding: 8px 14px;
|
||||
border-radius: 8px;
|
||||
z-index: 1000;
|
||||
}
|
||||
.nav a {
|
||||
color: #e0e0e0;
|
||||
margin-right: 20px;
|
||||
margin-right: 15px;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
#searchBox {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 1000;
|
||||
padding: 6px 10px;
|
||||
font-size: 14px;
|
||||
border-radius: 6px;
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: #1e1e1e;
|
||||
color: #fff;
|
||||
width: 200px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="nav">
|
||||
<a href="/">🏠 Home</a>
|
||||
<a href="/">📊 Dashboard</a>
|
||||
<a href="/journal">📓 Journal</a>
|
||||
<a href="/concepts">🧠 Concepts</a>
|
||||
<a href="/brainmap">🕸️ Brain Map</a>
|
||||
@ -44,21 +58,55 @@
|
||||
<a href="/health">❤️ Health</a>
|
||||
</div>
|
||||
|
||||
<input type="text" id="searchBox" placeholder="Search word...">
|
||||
|
||||
<div id="graph"></div>
|
||||
|
||||
<script>
|
||||
const graphData = {
|
||||
nodes: {{ nodes | safe }},
|
||||
links: {{ links | safe }}
|
||||
};
|
||||
const graphData = {
|
||||
nodes: {{ nodes | safe }},
|
||||
links: {{ links | safe }}
|
||||
};
|
||||
|
||||
const Graph = ForceGraph()(document.getElementById('graph'))
|
||||
.graphData(graphData)
|
||||
.nodeAutoColorBy('id')
|
||||
.nodeLabel('id')
|
||||
.linkWidth(link => Math.log(link.value + 1))
|
||||
.linkColor(() => 'rgba(255,255,255,0.2)')
|
||||
.backgroundColor('#121212');
|
||||
const degreeMap = {};
|
||||
graphData.links.forEach(link => {
|
||||
degreeMap[link.source] = (degreeMap[link.source] || 0) + 1;
|
||||
degreeMap[link.target] = (degreeMap[link.target] || 0) + 1;
|
||||
});
|
||||
|
||||
const Graph = ForceGraph()(document.getElementById("graph"))
|
||||
.graphData(graphData)
|
||||
.nodeId("id")
|
||||
.nodeLabel(node => `${node.id} (${degreeMap[node.id] || 0} links)`)
|
||||
.nodeAutoColorBy("group")
|
||||
.nodeVal(node => Math.max(2, degreeMap[node.id] || 1))
|
||||
.linkWidth(link => Math.log(link.value + 1))
|
||||
.linkColor(() => "rgba(255,255,255,0.2)")
|
||||
.backgroundColor("#121212");
|
||||
|
||||
// Search filter
|
||||
document.getElementById("searchBox").addEventListener("input", e => {
|
||||
const search = e.target.value.toLowerCase();
|
||||
Graph.nodeVisibility(node => node.id.toLowerCase().includes(search));
|
||||
});
|
||||
|
||||
// Click-to-highlight
|
||||
Graph.onNodeClick(node => {
|
||||
const neighbors = new Set();
|
||||
graphData.links.forEach(link => {
|
||||
if (link.source === node.id) neighbors.add(link.target);
|
||||
else if (link.target === node.id) neighbors.add(link.source);
|
||||
});
|
||||
|
||||
Graph.nodeColor(n =>
|
||||
n.id === node.id ? "#ffff00" :
|
||||
neighbors.has(n.id) ? "#ffa500" : "#333"
|
||||
);
|
||||
|
||||
Graph.linkColor(l =>
|
||||
l.source === node.id || l.target === node.id ? "#ffffff" : "rgba(255,255,255,0.05)"
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
@ -1,6 +1,9 @@
|
||||
import re
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from sklearn.cluster import KMeans
|
||||
import numpy as np
|
||||
from utils.unicleaner import clean_unicode
|
||||
|
||||
BRAINMAP_PATH = "data/memory/brainmap.json" # actual connection data
|
||||
@ -99,7 +102,12 @@ def get_brainmap():
|
||||
return brainmap
|
||||
|
||||
|
||||
def refresh_brainmap_cache(min_weight=5, max_nodes=300):
|
||||
def refresh_brainmap_cache(min_weight=2, max_nodes=300):
|
||||
"""
|
||||
Generates a clustered brainmap view and writes to:
|
||||
- data/memory/brainmap_cache.json (master copy)
|
||||
- static/brainmap.json (served to frontend)
|
||||
"""
|
||||
map_data = get_brainmap()
|
||||
links = []
|
||||
seen_words = set()
|
||||
@ -108,7 +116,6 @@ def refresh_brainmap_cache(min_weight=5, max_nodes=300):
|
||||
if not isinstance(connections, dict):
|
||||
print(f"[Brainmap] Skipping corrupted entry: {word} => {type(connections)}")
|
||||
continue
|
||||
|
||||
for linked_word, weight in connections.items():
|
||||
if weight >= min_weight:
|
||||
links.append({
|
||||
@ -119,13 +126,44 @@ def refresh_brainmap_cache(min_weight=5, max_nodes=300):
|
||||
seen_words.add(word)
|
||||
seen_words.add(linked_word)
|
||||
|
||||
nodes = [{"id": word} for word in seen_words]
|
||||
|
||||
node_set = {link["source"] for link in links} | {link["target"] for link in links}
|
||||
nodes = sorted(node_set)
|
||||
if len(nodes) > max_nodes:
|
||||
nodes = nodes[:max_nodes]
|
||||
node_set = {n["id"] for n in nodes}
|
||||
node_set = set(nodes)
|
||||
links = [l for l in links if l["source"] in node_set and l["target"] in node_set]
|
||||
|
||||
index_lookup = {word: i for i, word in enumerate(nodes)}
|
||||
word_vectors = []
|
||||
for word in nodes:
|
||||
vec = np.zeros(len(nodes), dtype=np.float32)
|
||||
connections = map_data.get(word, {})
|
||||
for other, strength in connections.items():
|
||||
if other in index_lookup:
|
||||
vec[index_lookup[other]] = strength
|
||||
word_vectors.append(vec)
|
||||
|
||||
if len(word_vectors) < 2:
|
||||
print("[Brainmap] Not enough nodes to cluster.")
|
||||
return
|
||||
|
||||
kmeans = KMeans(n_clusters=min(8, len(nodes)), n_init="auto")
|
||||
labels = kmeans.fit_predict(word_vectors)
|
||||
clustered_nodes = [{"id": word, "group": int(label)} for word, label in zip(nodes, labels)]
|
||||
|
||||
output = {
|
||||
"nodes": clustered_nodes,
|
||||
"links": links
|
||||
}
|
||||
|
||||
os.makedirs("data/memory", exist_ok=True)
|
||||
with open(BRAINMAP_CACHE_PATH, "w", encoding="utf-8") as f:
|
||||
json.dump({"nodes": nodes, "links": links}, f, indent=2)
|
||||
os.makedirs("static", exist_ok=True)
|
||||
|
||||
cache_path = "data/memory/brainmap_cache.json"
|
||||
static_path = "static/brainmap.json"
|
||||
|
||||
with open(cache_path, "w", encoding="utf-8") as f:
|
||||
json.dump(output, f, indent=2)
|
||||
|
||||
shutil.copyfile(cache_path, static_path)
|
||||
# print(f"[Brainmap] Cache written to {cache_path} and copied to {static_path}")
|
||||
|
@ -4,7 +4,7 @@ import os
|
||||
import time
|
||||
from model.tokenizer import VOCAB_PATH
|
||||
from model.dreams import DREAM_LOG_PATH
|
||||
from context.context import CONTEXT_FILE
|
||||
from context.context import CONTEXT_PATH
|
||||
from model.brainmap import load_brainmap, save_brainmap
|
||||
|
||||
CLEANUP_LOG = "data/logs/cleanup.log"
|
||||
@ -59,15 +59,15 @@ def cleanup_dreams():
|
||||
|
||||
|
||||
def cleanup_context():
|
||||
if not os.path.exists(CONTEXT_FILE):
|
||||
if not os.path.exists(CONTEXT_PATH):
|
||||
return
|
||||
|
||||
with open(CONTEXT_FILE, "r", encoding="utf-8") as f:
|
||||
with open(CONTEXT_PATH, "r", encoding="utf-8") as f:
|
||||
context = json.load(f)
|
||||
|
||||
filtered = context[-100:]
|
||||
|
||||
with open(CONTEXT_FILE, "w", encoding="utf-8") as f:
|
||||
with open(CONTEXT_PATH, "w", encoding="utf-8") as f:
|
||||
json.dump(filtered, f, indent=2)
|
||||
|
||||
if len(filtered) < len(context):
|
||||
|
Loading…
x
Reference in New Issue
Block a user