// Global state
let allGraphData = null;
let cy = null;
let debugPanes = [];
let debugPaneCounter = 0;
// Load data from API
async function loadGraphData() {
try {
const response = await fetch('/api/graph');
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || `HTTP ${response.status}`);
}
allGraphData = await response.json();
// Validate response structure
if (!allGraphData || !allGraphData.nodes || !allGraphData.edges) {
throw new Error('Invalid response format from server');
}
// Update table
updateTable();
// Initialize graph
if (document.getElementById('graph-tab').classList.contains('active')) {
reloadGraph();
}
return allGraphData;
} catch (e) {
console.error('Error loading graph data:', e);
alert('Error loading graph data: ' + e.message);
// Show error in the UI
const cyDiv = document.getElementById('cy');
if (cyDiv) {
cyDiv.innerHTML = `
`;
html += ``;
if (step === 0) {
html += `
đ¯ Finding Entry Points: Searching for memories semantically similar to the query.
These are the starting points for spreading activation.
`;
} else {
html += `
đ Spreading Activation: Following links from previously activated memories.
The algorithm explores connected memories and calculates their relevance.
`;
}
visits.forEach((visit, idx) => {
const isEntry = visit.is_entry_point;
const isResult = visit.final_rank !== null;
const hasParent = visit.parent_node_id !== null;
let cardClass = 'log-card';
if (isEntry) cardClass += ' log-card-entry';
if (isResult) cardClass += ' log-card-result';
html += `
`;
// Header
if (isEntry) {
html += ``;
} else if (hasParent) {
const linkTypeIcon = {
'temporal': 'âąī¸',
'semantic': 'đ',
'entity': 'đ¤'
};
const icon = linkTypeIcon[visit.link_type] || 'âĄī¸';
html += ``;
}
// Memory text
html += `
"${visit.text}"
`;
// Decision details
html += `
`;
if (isEntry) {
html += `
Why selected:
Semantic similarity to query = ${visit.weights.semantic_similarity.toFixed(3)}
`;
} else if (hasParent) {
html += `
Activated from:
Node ${visit.parent_node_id.substring(0, 8)}... via ${visit.link_type} link
Link weight:
${visit.link_weight.toFixed(3)}
`;
}
html += `
Activation:
${visit.weights.activation.toFixed(3)}
âšī¸
Semantic similarity:
${visit.weights.semantic_similarity.toFixed(3)}
âšī¸
Final weight:
${visit.weights.final_weight.toFixed(3)}
âšī¸
`;
html += `
`; // log-details
html += `
`; // log-card
});
html += `
`; // log-step
}
// Add pruned nodes section if any
if (trace.pruned && trace.pruned.length > 0) {
html += ``;
html += ``;
html += `
âī¸ Budget Limit Reached: These nodes were not explored to conserve computational resources.
`;
trace.pruned.forEach(pruned => {
html += `
`;
html += ``;
html += `
`;
html += `
Reason:
${pruned.reason}
`;
html += `
Activation:
${pruned.activation.toFixed(3)}
`;
html += `
`;
});
html += `
`;
}
logDiv.innerHTML = html;
}
function visualizeTrace(paneId, trace) {
const pane = debugPanes.find(p => p.id === paneId);
if (!pane || !trace) return;
const cyDiv = document.getElementById(`debug-cy-${paneId}`);
if (!cyDiv) return;
const showPruned = document.getElementById(`debug-show-pruned-${paneId}`).checked;
const highlightPath = document.getElementById(`debug-highlight-path-${paneId}`).checked;
try {
// Build graph from trace
const nodes = [];
const edges = [];
const visitedNodeIds = new Set();
// Add all visited nodes
trace.visits.forEach((visit, idx) => {
visitedNodeIds.add(visit.node_id);
// Determine node color based on properties
let color = '#90caf9'; // default
if (visit.is_entry_point) {
color = '#66bb6a'; // green for entry points
} else if (visit.final_rank !== null) {
color = '#ffd54f'; // yellow for results
}
nodes.push({
data: {
id: visit.node_id,
label: `${visit.text.substring(0, 30)}...`,
text: visit.text,
step: visit.step,
rank: visit.final_rank,
activation: visit.weights.activation.toFixed(3),
similarity: visit.weights.semantic_similarity.toFixed(3),
finalWeight: visit.weights.final_weight.toFixed(3),
isEntry: visit.is_entry_point,
color: color
}
});
// Add edge from parent if exists
if (visit.parent_node_id) {
let edgeColor = '#999';
if (visit.link_type === 'temporal') edgeColor = '#00bcd4';
else if (visit.link_type === 'semantic') edgeColor = '#ff69b4';
else if (visit.link_type === 'entity') edgeColor = '#ffd700';
edges.push({
data: {
id: `${visit.parent_node_id}-${visit.node_id}`,
source: visit.parent_node_id,
target: visit.node_id,
linkType: visit.link_type,
linkWeight: visit.link_weight,
color: edgeColor
}
});
}
});
// Add pruned nodes if requested
if (showPruned && trace.pruned) {
trace.pruned.forEach(pruned => {
if (!visitedNodeIds.has(pruned.node_id)) {
nodes.push({
data: {
id: pruned.node_id,
label: 'Pruned',
text: `Pruned: ${pruned.reason}`,
activation: pruned.activation.toFixed(3),
color: '#ef5350' // red for pruned
}
});
}
});
}
// Destroy existing graph
if (pane.cy) {
pane.cy.destroy();
}
// Add legend/explanation to the graph view
const existingLegend = cyDiv.querySelector('.search-graph-legend');
if (existingLegend) {
existingLegend.remove();
}
const legend = document.createElement('div');
legend.className = 'search-graph-legend';
legend.innerHTML = `