// Global state let allGraphData = null; let cy = null; let debugPanes = []; let debugPaneCounter = 0; let currentAgentId = null; // Global agent context let dataGraphs = { world: null, agent: null, opinions: null }; let dataCache = { world: null, agent: null, opinions: null }; let currentDataSubTab = 'world'; // Main tab switching (Data, Debug, Think, Benchmark) window.switchMainTab = function(tabName) { // Remove active class from all main tabs and buttons document.querySelectorAll('.tab-content').forEach(tab => { tab.classList.remove('active'); }); document.querySelectorAll('.tab-button').forEach(btn => { btn.classList.remove('active'); }); // Activate the selected tab const tabElement = document.getElementById(`${tabName}-tab`); if (tabElement) { tabElement.classList.add('active'); } // Find and activate the corresponding button const buttons = document.querySelectorAll('.tab-button'); buttons.forEach(btn => { const btnText = btn.textContent.toLowerCase(); if ( (tabName === 'data' && btnText.includes('data')) || (tabName === 'debug' && btnText.includes('debug')) || (tabName === 'think' && btnText.includes('think')) || (tabName === 'benchmark' && btnText.includes('benchmark')) ) { btn.classList.add('active'); } }); // Tab-specific logic if (tabName === 'data') { // Resize current data graph if exists const factType = currentDataSubTab; if (dataGraphs[factType]) { setTimeout(() => dataGraphs[factType].resize(), 10); } } else if (tabName === 'debug') { if (debugPanes.length === 0) { addDebugPane(); } debugPanes.forEach(pane => { if (pane.cy) { pane.cy.resize(); } }); } else if (tabName === 'think') { // Think tab uses global agent selector } } // Data subtab switching (World, Agent, Opinions) window.switchDataSubTab = function(subTab) { currentDataSubTab = subTab; // Remove active class from all subtab buttons and content document.querySelectorAll('.data-sub-tab-button').forEach(btn => { btn.classList.remove('active'); }); document.querySelectorAll('.data-subtab-content').forEach(content => { content.classList.remove('active'); }); // Activate selected subtab const buttons = document.querySelectorAll('.data-sub-tab-button'); buttons.forEach(btn => { if (btn.textContent.toLowerCase().includes(subTab.toLowerCase())) { btn.classList.add('active'); } }); const subtabElement = document.getElementById(`${subTab}-subtab`); if (subtabElement) { subtabElement.classList.add('active'); } // Resize graph if exists if (dataGraphs[subTab]) { setTimeout(() => dataGraphs[subTab].resize(), 10); } } // Switch between graph and table view for a fact type window.switchDataView = function(factType, viewType) { const graphView = document.getElementById(`${factType}-graph-view`); const tableView = document.getElementById(`${factType}-table-view`); const buttons = document.querySelectorAll(`#${factType}-subtab .view-toggle-button`); // Update button states buttons.forEach(btn => { btn.classList.remove('active'); if ((viewType === 'graph' && btn.textContent.includes('Graph')) || (viewType === 'table' && btn.textContent.includes('Table'))) { btn.classList.add('active'); } }); // Show/hide views if (viewType === 'graph') { graphView.style.display = 'block'; tableView.style.display = 'none'; if (dataGraphs[factType]) { setTimeout(() => dataGraphs[factType].resize(), 10); } } else { graphView.style.display = 'none'; tableView.style.display = 'block'; } } // Load data for a specific fact type window.loadDataView = async function(factType) { if (!currentAgentId) { alert('Please select an agent first'); return; } try { // Build URL with agent filter and fact_type filter let url = `api/graph?agent_id=${encodeURIComponent(currentAgentId)}`; if (factType !== 'all') { url += `&fact_type=${factType}`; } const response = await fetch(url); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || `HTTP ${response.status}`); } const data = await response.json(); // Validate response structure if (!data || !data.nodes || !data.edges) { throw new Error('Invalid response format from server'); } // Cache the data dataCache[factType] = data; // Update table updateDataTable(factType, data); // Update graph if in graph view const graphView = document.getElementById(`${factType}-graph-view`); if (graphView && graphView.style.display !== 'none') { reloadDataGraph(factType); } return data; } catch (e) { console.error(`Error loading ${factType} data:`, e); alert(`Error loading ${factType} data: ` + e.message); } } // Reload graph for a specific fact type window.reloadDataGraph = function(factType) { const data = dataCache[factType]; if (!data) return; const nodeLimit = parseInt(document.getElementById(`${factType}-node-limit`).value) || 50; const layoutName = document.getElementById(`${factType}-layout-select`).value; // Filter nodes to limit const limitedNodes = data.nodes.slice(0, nodeLimit); const nodeIds = new Set(limitedNodes.map(n => n.data.id)); // Filter edges to only include those between visible nodes const limitedEdges = data.edges.filter(e => nodeIds.has(e.data.source) && nodeIds.has(e.data.target) ); // Update count display document.getElementById(`${factType}-node-count`).textContent = `Showing ${limitedNodes.length} of ${data.nodes.length} nodes`; // Destroy existing graph if any if (dataGraphs[factType]) { dataGraphs[factType].destroy(); } // Layout configurations const layouts = { 'circle': { name: 'circle', animate: false, radius: 300, spacingFactor: 1.5 }, 'grid': { name: 'grid', animate: false, rows: Math.ceil(Math.sqrt(limitedNodes.length)), cols: Math.ceil(Math.sqrt(limitedNodes.length)), spacingFactor: 2 }, 'cose': { name: 'cose', animate: false, nodeRepulsion: 15000, idealEdgeLength: 150, edgeElasticity: 100, nestingFactor: 1.2, gravity: 1, numIter: 1000, initialTemp: 200, coolingFactor: 0.95, minTemp: 1.0 } }; // Initialize Cytoscape dataGraphs[factType] = cytoscape({ container: document.getElementById(`${factType}-cy`), elements: [ ...limitedNodes.map(n => ({ data: n.data })), ...limitedEdges.map(e => ({ data: e.data })) ], style: [ { selector: 'node', style: { 'background-color': 'data(color)', 'label': 'data(label)', 'text-valign': 'center', 'text-halign': 'center', 'font-size': '10px', 'font-weight': 'bold', 'text-wrap': 'wrap', 'text-max-width': '100px', 'width': 40, 'height': 40, 'border-width': 2, 'border-color': '#333' } }, { selector: 'edge', style: { 'width': 1, 'line-color': 'data(color)', 'line-style': 'data(lineStyle)', 'target-arrow-shape': 'triangle', 'target-arrow-color': 'data(color)', 'curve-style': 'bezier', 'opacity': 0.7 } }, { selector: 'node:selected', style: { 'border-width': 4, 'border-color': '#000' } } ], layout: layouts[layoutName] || layouts['circle'] }); // Add tooltip on hover let tooltip = null; dataGraphs[factType].on('mouseover', 'node', function(evt) { const node = evt.target; const data = node.data(); const renderedPosition = node.renderedPosition(); tooltip = document.createElement('div'); tooltip.className = 'tooltip'; tooltip.innerHTML = ` Text: ${data.text}
Context: ${data.context}
Date: ${data.date}
Entities: ${data.entities} `; tooltip.style.left = renderedPosition.x + 20 + 'px'; tooltip.style.top = renderedPosition.y + 'px'; document.body.appendChild(tooltip); }); dataGraphs[factType].on('mouseout', 'node', function(evt) { if (tooltip) { tooltip.remove(); tooltip = null; } }); } // Update table for a specific fact type function updateDataTable(factType, data) { if (!data) return; const tbody = document.getElementById(`${factType}-table-body`); const countSpan = document.getElementById(`${factType}-table-count`); if (countSpan) { countSpan.textContent = `(${data.total_units})`; } if (tbody) { tbody.innerHTML = data.table_rows.map(row => ` ${row.id} ${row.text} ${row.context} ${row.date} ${row.entities} `).join(''); } // Setup table filter const filterInput = document.getElementById(`${factType}-table-filter`); if (filterInput) { filterInput.removeEventListener('input', filterInput._filterHandler); filterInput._filterHandler = function() { const filterValue = this.value.toLowerCase(); const rows = document.querySelectorAll(`#${factType}-table-body tr`); rows.forEach(row => { const text = row.textContent.toLowerCase(); if (text.includes(filterValue)) { row.style.display = ''; } else { row.style.display = 'none'; } }); }; filterInput.addEventListener('input', filterInput._filterHandler); } } // Load data from API (old function - kept for backward compatibility) async function loadGraphData() { try { // Require agent selection if (!currentAgentId) { alert('Please select an agent first'); return; } // Build URL with agent filter let url = `api/graph?agent_id=${encodeURIComponent(currentAgentId)}`; const response = await fetch(url); 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 = `

Failed to load graph data

${e.message}

`; } } } // Refresh data from server function refreshData() { loadGraphData(); } // Update table with current data function updateTable() { if (!allGraphData) return; const tbody = document.getElementById('table-body'); const countSpan = document.getElementById('table-count'); countSpan.textContent = `(${allGraphData.total_units})`; tbody.innerHTML = allGraphData.table_rows.map(row => ` ${row.id} ${row.text} ${row.context} ${row.date} ${row.entities} `).join(''); } // Initialize graph with filtering function initGraph(nodeLimit, layoutName) { if (!allGraphData) return; // Filter nodes to limit const limitedNodes = allGraphData.nodes.slice(0, nodeLimit); const nodeIds = new Set(limitedNodes.map(n => n.data.id)); // Filter edges to only include those between visible nodes const limitedEdges = allGraphData.edges.filter(e => nodeIds.has(e.data.source) && nodeIds.has(e.data.target) ); // Update count display document.getElementById('node-count').textContent = `Showing ${limitedNodes.length} of ${allGraphData.nodes.length} nodes`; // Destroy existing graph if any if (cy) { cy.destroy(); } // Layout configurations const layouts = { 'circle': { name: 'circle', animate: false, radius: 300, spacingFactor: 1.5 }, 'grid': { name: 'grid', animate: false, rows: Math.ceil(Math.sqrt(limitedNodes.length)), cols: Math.ceil(Math.sqrt(limitedNodes.length)), spacingFactor: 2 }, 'cose': { name: 'cose', animate: false, nodeRepulsion: 15000, idealEdgeLength: 150, edgeElasticity: 100, nestingFactor: 1.2, gravity: 1, numIter: 1000, initialTemp: 200, coolingFactor: 0.95, minTemp: 1.0 } }; // Initialize Cytoscape cy = cytoscape({ container: document.getElementById('cy'), elements: [ ...limitedNodes.map(n => ({ data: n.data })), ...limitedEdges.map(e => ({ data: e.data })) ], style: [ { selector: 'node', style: { 'background-color': 'data(color)', 'label': 'data(label)', 'text-valign': 'center', 'text-halign': 'center', 'font-size': '10px', 'font-weight': 'bold', 'text-wrap': 'wrap', 'text-max-width': '100px', 'width': 40, 'height': 40, 'border-width': 2, 'border-color': '#333' } }, { selector: 'edge', style: { 'width': 1, 'line-color': 'data(color)', 'line-style': 'data(lineStyle)', 'target-arrow-shape': 'triangle', 'target-arrow-color': 'data(color)', 'curve-style': 'bezier', 'opacity': 0.7 } }, { selector: 'node:selected', style: { 'border-width': 4, 'border-color': '#000' } } ], layout: layouts[layoutName] || layouts['circle'] }); // Simple tooltip on hover let tooltip = null; cy.on('mouseover', 'node', function(evt) { const node = evt.target; const data = node.data(); const renderedPosition = node.renderedPosition(); // Create tooltip tooltip = document.createElement('div'); tooltip.className = 'tooltip'; tooltip.innerHTML = ` Text: ${data.text}
Context: ${data.context}
Date: ${data.date}
Entities: ${data.entities} `; tooltip.style.left = renderedPosition.x + 20 + 'px'; tooltip.style.top = renderedPosition.y + 'px'; document.body.appendChild(tooltip); }); cy.on('mouseout', 'node', function(evt) { if (tooltip) { tooltip.remove(); tooltip = null; } }); } // Reload graph with current settings function reloadGraph() { const nodeLimit = parseInt(document.getElementById('node-limit').value) || 50; const layoutName = document.getElementById('layout-select').value; initGraph(nodeLimit, layoutName); } // Load available agents let agentsLoaded = false; async function loadAgents() { if (agentsLoaded) return; try { const response = await fetch('api/agents'); const data = await response.json(); const select = document.getElementById('search-agent-id'); select.innerHTML = ''; if (data.agents && data.agents.length > 0) { data.agents.forEach(agent => { const option = document.createElement('option'); option.value = agent; option.textContent = agent; select.appendChild(option); }); agentsLoaded = true; } else { select.innerHTML = ''; } } catch (e) { console.error('Error loading agents:', e); const select = document.getElementById('search-agent-id'); select.innerHTML = ''; } } // Debug pane management function addDebugPane() { const paneId = debugPaneCounter++; const container = document.getElementById('debug-panes-container'); const paneDiv = document.createElement('div'); paneDiv.className = 'debug-pane'; paneDiv.id = `debug-pane-${paneId}`; paneDiv.innerHTML = `
Search Trace #${paneId + 1}
Ready to search
`; container.appendChild(paneDiv); // Add event listeners for view mode toggle document.getElementById(`debug-mode-graph-${paneId}`).addEventListener('change', function() { if (this.checked) { document.getElementById(`debug-cy-${paneId}`).style.display = 'block'; document.getElementById(`decision-log-${paneId}`).style.display = 'none'; document.getElementById(`results-table-${paneId}`).style.display = 'none'; document.getElementById(`graph-controls-${paneId}`).style.display = 'inline'; const pane = debugPanes.find(p => p.id === paneId); if (pane && pane.cy) { setTimeout(() => pane.cy.resize(), 10); } } }); document.getElementById(`debug-mode-log-${paneId}`).addEventListener('change', function() { if (this.checked) { document.getElementById(`debug-cy-${paneId}`).style.display = 'none'; document.getElementById(`decision-log-${paneId}`).style.display = 'block'; document.getElementById(`results-table-${paneId}`).style.display = 'none'; document.getElementById(`graph-controls-${paneId}`).style.display = 'none'; } }); document.getElementById(`debug-mode-table-${paneId}`).addEventListener('change', function() { if (this.checked) { document.getElementById(`debug-cy-${paneId}`).style.display = 'none'; document.getElementById(`decision-log-${paneId}`).style.display = 'none'; document.getElementById(`results-table-${paneId}`).style.display = 'block'; document.getElementById(`graph-controls-${paneId}`).style.display = 'none'; } }); // Add event listeners for graph controls document.getElementById(`debug-show-pruned-${paneId}`).addEventListener('change', function() { const pane = debugPanes.find(p => p.id === paneId); if (pane && pane.trace) { visualizeTrace(paneId, pane.trace); } }); document.getElementById(`debug-highlight-path-${paneId}`).addEventListener('change', function() { const pane = debugPanes.find(p => p.id === paneId); if (pane && pane.trace) { visualizeTrace(paneId, pane.trace); } }); // Add search input listener for finding nodes document.getElementById(`graph-search-${paneId}`).addEventListener('input', function(e) { const pane = debugPanes.find(p => p.id === paneId); if (pane && pane.cy) { highlightMatchingNodes(paneId, e.target.value); } }); debugPanes.push({ id: paneId, element: paneDiv, cy: null, trace: null }); // Load agents for this pane after DOM is ready setTimeout(() => loadAgentsForPane(paneId), 10); } async function loadAgentsForPane(paneId) { const select = document.getElementById(`search-agent-${paneId}`); if (!select) { console.error(`Could not find select element for pane ${paneId}`); return; } try { const response = await fetch('api/agents'); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); select.innerHTML = ''; if (data.agents && data.agents.length > 0) { data.agents.forEach(agent => { const option = document.createElement('option'); option.value = agent; option.textContent = agent; select.appendChild(option); }); } else { select.innerHTML = ''; } } catch (e) { console.error('Error loading agents for pane:', paneId, e); select.innerHTML = ''; } } window.runSearchInPane = async function(paneId) { const pane = debugPanes.find(p => p.id === paneId); if (!pane) return; const query = document.getElementById(`search-query-${paneId}`).value; const searchType = document.getElementById(`search-type-${paneId}`).value; const agentId = document.getElementById(`search-agent-${paneId}`).value; const thinkingBudget = parseInt(document.getElementById(`search-budget-${paneId}`).value); const topK = parseInt(document.getElementById(`search-top-k-${paneId}`).value); const mmrLambda = parseFloat(document.getElementById(`search-mmr-lambda-${paneId}`).value); const statusBar = document.getElementById(`debug-status-${paneId}`); if (!query) { alert('Please enter a query'); return; } try { // Determine endpoint based on search type let endpoint = 'api/search'; if (searchType === 'world') { endpoint = 'api/world_search'; } else if (searchType === 'agent') { endpoint = 'api/agent_search'; } statusBar.innerHTML = '🔄 Searching...'; const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: query, agent_id: agentId, thinking_budget: thinkingBudget, top_k: topK, mmr_lambda: mmrLambda, trace: true }) }); const data = await response.json(); if (data.detail) { statusBar.innerHTML = `❌ Error: ${data.detail}`; return; } // Update status bar with stats const summary = data.trace.summary; statusBar.innerHTML = ` ✓ Search complete | Nodes visited: ${summary.total_nodes_visited} | Entry points: ${summary.entry_points_found} | Budget used: ${summary.budget_used} / ${summary.budget_used + summary.budget_remaining} | Results: ${summary.results_returned} | Duration: ${summary.total_duration_seconds.toFixed(2)}s `; // Visualize the trace and results pane.trace = data.trace; pane.results = data.results; visualizeTrace(paneId, data.trace); renderDecisionLog(paneId, data.trace); renderResultsTable(paneId, data.results, data.trace); } catch (e) { statusBar.innerHTML = `❌ Error: ${e.message}`; console.error('Error running search:', e); } } window.removeSpecificDebugPane = function(paneId) { const paneIndex = debugPanes.findIndex(p => p.id === paneId); if (paneIndex === -1) return; const pane = debugPanes[paneIndex]; if (pane.cy) { pane.cy.destroy(); } pane.element.remove(); debugPanes.splice(paneIndex, 1); } function removeDebugPane() { if (debugPanes.length === 0) return; const pane = debugPanes.pop(); if (pane.cy) { pane.cy.destroy(); } pane.element.remove(); } function renderResultsTable(paneId, results, trace) { const tableDiv = document.getElementById(`results-table-${paneId}`); if (!tableDiv) return; if (!results || results.length === 0) { tableDiv.innerHTML = '
No results returned
'; return; } // Check if MMR was used const mmrUsed = results.some(r => r.mmr_score !== null && r.mmr_score !== undefined); let html = `

Search Results (${results.length} memories)

Query: "${trace.query.query_text}"

${mmrUsed ? `
MMR Diversification Active: đŸŽ¯ = Diversified pick (selected for variety) | Orange background = Rank changed by MMR | Orig Rank shows position before MMR reranking
` : ''} `; results.forEach((result, idx) => { // Find corresponding visit in trace const visit = trace.visits.find(v => v.node_id === result.id); // Get scores from visit weights const finalScore = visit ? visit.weights.final_weight : (result.score || 0); const activation = visit ? visit.weights.activation : 0; const similarity = visit ? visit.weights.semantic_similarity : 0; const recency = visit ? (visit.weights.recency || 0) : 0; const frequency = visit ? (visit.weights.frequency || 0) : 0; // Get MMR information const originalRank = result.original_rank || (idx + 1); const mmrScore = result.mmr_score; const mmrRelevance = result.mmr_relevance; const mmrMaxSim = result.mmr_max_similarity; const isDiversified = result.mmr_diversified || false; // Highlight diversified results const rowBg = isDiversified ? '#fff3e0' : 'white'; const rankDisplay = originalRank !== (idx + 1) ? `↑${originalRank}` : originalRank; html += ` `; }); html += `
Rank Orig Rank Text Context Date Final Score Activation Similarity Recency Frequency MMR Score MMR Rel MMR Sim
#${idx + 1}${isDiversified ? ' đŸŽ¯' : ''} ${rankDisplay} ${result.text} ${result.context || 'N/A'} ${result.event_date ? new Date(result.event_date).toLocaleDateString() : 'N/A'} ${finalScore.toFixed(4)} ${activation.toFixed(4)} ${similarity.toFixed(4)} ${recency.toFixed(4)} ${frequency.toFixed(4)} ${mmrScore !== null && mmrScore !== undefined ? mmrScore.toFixed(4) : '-'} ${mmrRelevance !== null && mmrRelevance !== undefined ? mmrRelevance.toFixed(4) : '-'} ${mmrMaxSim !== null && mmrMaxSim !== undefined ? mmrMaxSim.toFixed(4) : '-'}
`; tableDiv.innerHTML = html; } function renderDecisionLog(paneId, trace) { const logDiv = document.getElementById(`decision-log-${paneId}`); if (!logDiv || !trace) return; // Group visits by step const stepGroups = {}; trace.visits.forEach(visit => { if (!stepGroups[visit.step]) { stepGroups[visit.step] = []; } stepGroups[visit.step].push(visit); }); // Build HTML for decision log let html = `

Search Execution Trace

Query: "${trace.query.query_text}"

This log shows the step-by-step decision process of the spreading activation search algorithm. The search starts from entry points (semantically similar memories) and spreads through connected memories, following temporal, semantic, and entity links to find relevant results.

`; const maxStep = Math.max(...Object.keys(stepGroups).map(k => parseInt(k))); for (let step = 0; step <= maxStep; step++) { const visits = stepGroups[step] || []; if (visits.length === 0) continue; html += `
`; html += `
Step ${step}
`; 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 += `
Entry Point ${isResult ? `Rank #${visit.final_rank}` : ''}
`; } else if (hasParent) { const linkTypeIcon = { 'temporal': 'âąī¸', 'semantic': '🔗', 'entity': '👤' }; const icon = linkTypeIcon[visit.link_type] || 'âžĄī¸'; html += `
${icon} ${visit.link_type} link ${isResult ? `Rank #${visit.final_rank}` : ''}
`; } // 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 += `
Pruned Nodes
`; html += `
âœ‚ī¸ Budget Limit Reached: These nodes were not explored to conserve computational resources.
`; trace.pruned.forEach(pruned => { html += `
`; html += `
Pruned
`; 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 = `

🔍 Graph Legend

Node Colors:

Entry points - semantically similar to query
Results - returned as answers
Visited - explored but not in results

Link Types:

Temporal - memories close in time
Semantic - similar content/meaning
Entity - same person/place/thing

Layout: Rows represent search depth from entry points

`; cyDiv.appendChild(legend); // Create new graph pane.cy = cytoscape({ container: cyDiv, elements: [...nodes, ...edges], style: [ { selector: 'node', style: { 'background-color': 'data(color)', 'label': 'data(label)', 'text-valign': 'center', 'text-halign': 'center', 'font-size': '10px', 'font-weight': 'bold', 'text-wrap': 'wrap', 'text-max-width': '100px', 'width': 50, 'height': 50, 'border-width': 2, 'border-color': '#333' } }, { selector: 'edge', style: { 'width': 2, 'line-color': 'data(color)', 'target-arrow-shape': 'triangle', 'target-arrow-color': 'data(color)', 'curve-style': 'bezier', 'opacity': 0.8 } }, { selector: 'node:selected', style: { 'border-width': 4, 'border-color': '#000' } } ], layout: { name: 'breadthfirst', directed: true, spacingFactor: 1.5, animate: false } }); // Highlight path to top result if requested if (highlightPath && trace.visits.length > 0) { const topResult = trace.visits.find(v => v.final_rank === 1); if (topResult) { const pathNodes = []; let current = topResult; while (current) { pathNodes.push(current.node_id); current = trace.visits.find(v => v.node_id === current.parent_node_id); } pathNodes.forEach(nodeId => { pane.cy.$(`#${nodeId}`).style({ 'border-width': 5, 'border-color': '#ff5722' }); }); } } // Add tooltips let tooltip = null; pane.cy.on('mouseover', 'node', function(evt) { const node = evt.target; const data = node.data(); const pos = node.renderedPosition(); tooltip = document.createElement('div'); tooltip.className = 'tooltip'; tooltip.innerHTML = ` Text: ${data.text}
${data.step ? `Step: ${data.step}
` : ''} ${data.rank !== null && data.rank !== undefined ? `Rank: ${data.rank}
` : ''} ${data.activation ? `Activation: ${data.activation}
` : ''} ${data.similarity ? `Similarity: ${data.similarity}
` : ''} ${data.finalWeight ? `Final Weight: ${data.finalWeight}` : ''} `; // Get container bounds const container = cyDiv.getBoundingClientRect(); // Position tooltip relative to container let left = pos.x + 20; let top = pos.y; // Append to body temporarily to get dimensions document.body.appendChild(tooltip); const tooltipRect = tooltip.getBoundingClientRect(); // Adjust if tooltip would go off right edge if (container.left + left + tooltipRect.width > window.innerWidth) { left = pos.x - tooltipRect.width - 20; } // Adjust if tooltip would go off bottom edge if (container.top + top + tooltipRect.height > window.innerHeight) { top = pos.y - tooltipRect.height; } // Adjust if tooltip would go off left edge if (container.left + left < 0) { left = 20; } // Adjust if tooltip would go off top edge if (container.top + top < 0) { top = 20; } tooltip.style.left = (container.left + left) + 'px'; tooltip.style.top = (container.top + top) + 'px'; }); pane.cy.on('mouseout', 'node', function() { if (tooltip) { tooltip.remove(); tooltip = null; } }); } catch (e) { console.error('Error visualizing trace:', e); // Show error in status bar const statusBar = document.getElementById(`debug-status-${paneId}`); if (statusBar) { statusBar.innerHTML = `❌ Error visualizing trace: ${e.message}`; } } } function highlightMatchingNodes(paneId, searchText) { const pane = debugPanes.find(p => p.id === paneId); if (!pane || !pane.cy) return; const countSpan = document.getElementById(`graph-search-count-${paneId}`); // If search is empty, reset all styles if (!searchText || searchText.trim() === '') { pane.cy.nodes().style({ 'opacity': 1, 'border-width': 2, 'border-color': '#333' }); pane.cy.edges().style({ 'opacity': 0.8 }); if (countSpan) countSpan.textContent = ''; return; } const searchLower = searchText.toLowerCase(); let matchCount = 0; const totalNodes = pane.cy.nodes().length; // Check each node for matches pane.cy.nodes().forEach(node => { const data = node.data(); const text = (data.text || '').toLowerCase(); const label = (data.label || '').toLowerCase(); const matches = text.includes(searchLower) || label.includes(searchLower); if (matches) { matchCount++; // Highlight matching nodes - full opacity with thicker orange border node.style({ 'opacity': 1, 'border-width': 4, 'border-color': '#ff6f00' }); } else { // Dim non-matching nodes node.style({ 'opacity': 0.2, 'border-width': 2, 'border-color': '#333' }); } }); // Dim all edges pane.cy.edges().style({ 'opacity': 0.2 }); // Update counter if (countSpan) { if (matchCount === 0) { countSpan.textContent = '(no matches)'; countSpan.style.color = '#d32f2f'; } else { countSpan.textContent = `(${matchCount} of ${totalNodes})`; countSpan.style.color = '#43a047'; } } } // Load agents into global selector async function loadGlobalAgents() { try { console.log('Loading global agents...'); // Debug const select = document.getElementById('global-agent-selector'); if (!select) { console.error('global-agent-selector element not found!'); return; } console.log('Fetching api/agents...'); // Debug const response = await fetch('api/agents'); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); console.log('Agents data received:', data); // Debug // Start with placeholder select.innerHTML = ''; if (data.agents && data.agents.length > 0) { console.log(`Adding ${data.agents.length} agents to dropdown`); // Debug data.agents.forEach(agent => { const option = document.createElement('option'); option.value = agent; option.textContent = agent; select.appendChild(option); }); // Auto-select first agent select.value = data.agents[0]; currentAgentId = data.agents[0]; console.log('Auto-selected agent:', currentAgentId); // Debug // Update UI to show agent is selected updateUIForAgentSelection(); } else { console.warn('No agents found in response'); } } catch (e) { console.error('Error loading global agents:', e); alert('Failed to load agents: ' + e.message); } } // Handle global agent selection change function onGlobalAgentChange() { const select = document.getElementById('global-agent-selector'); currentAgentId = select.value || null; // Show/hide UI elements based on agent selection updateUIForAgentSelection(); // Refresh all tabs if agent is selected if (currentAgentId) { refreshAllTabs(); } } // Update UI visibility based on agent selection function updateUIForAgentSelection() { const hasAgent = !!currentAgentId; // Update each data subtab ['world', 'agent', 'opinions'].forEach(factType => { const noAgentMsg = document.getElementById(`${factType}-no-agent-message`); const graphView = document.getElementById(`${factType}-graph-view`); const tableView = document.getElementById(`${factType}-table-view`); if (noAgentMsg) noAgentMsg.style.display = hasAgent ? 'none' : 'block'; if (graphView) graphView.style.display = hasAgent ? 'none' : 'none'; // Start hidden, load on demand if (tableView) tableView.style.display = hasAgent ? 'none' : 'none'; // Start hidden, load on demand }); } // Refresh all tabs with new agent context async function refreshAllTabs() { // Clear existing data dataCache = { world: null, agent: null, opinions: null }; // Destroy existing graphs ['world', 'agent', 'opinions'].forEach(factType => { if (dataGraphs[factType]) { dataGraphs[factType].destroy(); dataGraphs[factType] = null; } }); // Update active debug panes with new agent debugPanes.forEach(pane => { const agentSelect = document.getElementById(`search-agent-${pane.id}`); if (agentSelect && currentAgentId) { agentSelect.value = currentAgentId; } }); } // Run Think query window.runThink = async function() { console.log('runThink called'); // Debug log const query = document.getElementById('think-query').value; const agentSelect = document.getElementById('global-agent-selector'); const agentId = agentSelect ? agentSelect.value : null; const thinkingBudget = parseInt(document.getElementById('think-budget').value); const topK = parseInt(document.getElementById('think-top-k').value); console.log('Query:', query, 'Agent:', agentId); // Debug log if (!query || query.trim() === '') { alert('Please enter a question'); return; } if (!agentId) { alert('Please select an agent from the breadcrumb'); return; } const resultDiv = document.getElementById('think-result'); const loadingDiv = document.getElementById('think-loading'); if (!resultDiv || !loadingDiv) { console.error('Think result divs not found'); return; } try { // Show loading resultDiv.style.display = 'none'; loadingDiv.style.display = 'block'; console.log('Calling api/think with', { query, agentId, thinkingBudget, topK }); // Debug log const response = await fetch('api/think', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: query, agent_id: agentId, thinking_budget: thinkingBudget, top_k: topK }) }); console.log('Response status:', response.status); // Debug log const data = await response.json(); console.log('Response data:', data); // Debug log if (data.detail) { alert('Error: ' + data.detail); loadingDiv.style.display = 'none'; return; } // Display answer document.getElementById('think-answer-text').textContent = data.text; // Display world facts const worldFactsDiv = document.getElementById('think-world-facts'); if (data.based_on.world && data.based_on.world.length > 0) { worldFactsDiv.innerHTML = data.based_on.world.map((fact, idx) => `
${fact.text}
Score: ${fact.score ? fact.score.toFixed(4) : 'N/A'} | ${fact.context ? 'Context: ' + fact.context : ''}
`).join(''); } else { worldFactsDiv.innerHTML = '
No world facts used
'; } // Display agent facts const agentFactsDiv = document.getElementById('think-agent-facts'); if (data.based_on.agent && data.based_on.agent.length > 0) { agentFactsDiv.innerHTML = data.based_on.agent.map((fact, idx) => `
${fact.text}
Score: ${fact.score ? fact.score.toFixed(4) : 'N/A'} | ${fact.context ? 'Context: ' + fact.context : ''}
`).join(''); } else { agentFactsDiv.innerHTML = '
No agent facts used
'; } // Display opinions const opinionsDiv = document.getElementById('think-opinions'); if (data.based_on.opinion && data.based_on.opinion.length > 0) { opinionsDiv.innerHTML = data.based_on.opinion.map((fact, idx) => `
${fact.text}
Score: ${fact.score ? fact.score.toFixed(4) : 'N/A'} | Confidence: ${fact.confidence_score ? (fact.confidence_score * 100).toFixed(1) + '%' : 'N/A'} | ${fact.context ? 'Context: ' + fact.context : ''}
`).join(''); } else { opinionsDiv.innerHTML = '
No opinions used
'; } // Display new opinions const newOpinionsDiv = document.getElementById('think-new-opinions'); const newOpinionsListDiv = document.getElementById('think-new-opinions-list'); if (data.new_opinions && data.new_opinions.length > 0) { newOpinionsListDiv.innerHTML = data.new_opinions.map((opinion, idx) => `
NEW #${idx + 1}
${(opinion.confidence * 100).toFixed(0)}% confidence
${opinion.text}
`).join(''); newOpinionsDiv.style.display = 'block'; } else { newOpinionsDiv.style.display = 'none'; } // Show result loadingDiv.style.display = 'none'; resultDiv.style.display = 'block'; } catch (e) { console.error('Error running Think:', e); alert('Error: ' + e.message); loadingDiv.style.display = 'none'; } }; // Initialize global agent selector on page load document.addEventListener('DOMContentLoaded', function() { console.log('DOM Content Loaded - initializing...'); // Debug // Add change listener to global agent selector const agentSelector = document.getElementById('global-agent-selector'); if (agentSelector) { console.log('Found global-agent-selector, adding change listener'); // Debug agentSelector.addEventListener('change', onGlobalAgentChange); } else { console.error('global-agent-selector not found in DOM!'); // Debug } // Add Enter key listener for Think query input const thinkQuery = document.getElementById('think-query'); if (thinkQuery) { thinkQuery.addEventListener('keypress', function(e) { if (e.key === 'Enter') { runThink(); } }); } // Initialize UI visibility updateUIForAgentSelection(); // Load agents loadGlobalAgents(); }); // Don't auto-load data on page load - wait for user to click load button