// 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, opinion: null }; let dataCache = { world: null, agent: null, opinion: 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); } } // Load documents view window.loadDocumentsView = async function() { if (!currentAgentId) { alert('Please select an agent first'); return; } await loadDocumentsTable(); } // Load documents table from API async function loadDocumentsTable(searchQuery = '', limit = 100, offset = 0) { if (!currentAgentId) return; const tbody = document.getElementById('documents-table-body'); const countSpan = document.getElementById('documents-count'); if (!tbody) return; try { // Build URL with filters let url = `api/documents?agent_id=${encodeURIComponent(currentAgentId)}&limit=${limit}&offset=${offset}`; if (searchQuery) { url += `&q=${encodeURIComponent(searchQuery)}`; } const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); if (countSpan) { countSpan.textContent = `(${data.total})`; } if (data.items.length === 0) { tbody.innerHTML = 'No documents found'; return; } tbody.innerHTML = data.items.map(doc => ` ${doc.id.length > 30 ? doc.id.substring(0, 30) + '...' : doc.id} ${doc.created_at ? new Date(doc.created_at).toLocaleString() : 'N/A'} ${doc.updated_at ? new Date(doc.updated_at).toLocaleString() : 'N/A'} ${doc.text_length.toLocaleString()} chars ${doc.memory_unit_count} ${Object.keys(doc.metadata).length > 0 ? JSON.stringify(doc.metadata).substring(0, 50) + '...' : 'None'} `).join(''); // Setup table filter with debounced API calls const filterInput = document.getElementById('documents-filter'); if (filterInput) { filterInput.removeEventListener('input', filterInput._filterHandler); filterInput._filterHandler = debounce(async function() { const filterValue = this.value.trim(); await loadDocumentsTable(filterValue); }, 500); filterInput.addEventListener('input', filterInput._filterHandler); } } catch (error) { console.error('Error loading documents:', error); tbody.innerHTML = 'Error loading documents'; } } // View document text in a modal window.viewDocumentText = async function(documentId, agentId) { try { const url = `api/documents/${encodeURIComponent(documentId)}?agent_id=${encodeURIComponent(agentId)}`; const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const doc = await response.json(); // Create modal overlay const modal = document.createElement('div'); modal.style.cssText = 'position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 10000; display: flex; align-items: center; justify-content: center; padding: 20px;'; const modalContent = document.createElement('div'); modalContent.style.cssText = 'background: white; border-radius: 8px; max-width: 900px; max-height: 90vh; overflow: auto; padding: 30px; box-shadow: 0 4px 20px rgba(0,0,0,0.3);'; modalContent.innerHTML = `

Document: ${doc.id}

Created: ${new Date(doc.created_at).toLocaleString()}
Memory Units: ${doc.memory_unit_count}
${Object.keys(doc.metadata).length > 0 ? `
Metadata: ${JSON.stringify(doc.metadata, null, 2)}
` : ''}
${doc.original_text}
`; modal.appendChild(modalContent); document.body.appendChild(modal); // Close on background click modal.addEventListener('click', function(e) { if (e.target === modal) { modal.remove(); } }); } catch (error) { console.error('Error loading document:', error); alert('Error loading document: ' + error.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 async function updateDataTable(factType, data) { if (!data) return; const countSpan = document.getElementById(`${factType}-table-count`); if (countSpan) { countSpan.textContent = `(${data.total_units})`; } // Load initial table data from the new /api/list endpoint await loadTableData(factType); // Setup table filter with debounced API calls const filterInput = document.getElementById(`${factType}-table-filter`); if (filterInput) { filterInput.removeEventListener('input', filterInput._filterHandler); filterInput._filterHandler = debounce(async function() { const filterValue = this.value.trim(); await loadTableData(factType, filterValue); }, 500); // 500ms debounce filterInput.addEventListener('input', filterInput._filterHandler); } } // Load table data from /api/list endpoint async function loadTableData(factType, searchQuery = '', limit = 100, offset = 0) { if (!currentAgentId) return; const tbody = document.getElementById(`${factType}-table-body`); if (!tbody) return; try { // Build URL with filters let url = `api/list?agent_id=${encodeURIComponent(currentAgentId)}&fact_type=${factType}&limit=${limit}&offset=${offset}`; if (searchQuery) { url += `&q=${encodeURIComponent(searchQuery)}`; } const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); if (data.items.length === 0) { tbody.innerHTML = 'No results found'; return; } tbody.innerHTML = data.items.map(row => ` ${row.id.substring(0, 8)}... ${row.text} ${row.context || 'N/A'} ${row.date ? new Date(row.date).toLocaleString() : 'N/A'} ${row.entities || 'None'} `).join(''); } catch (error) { console.error('Error loading table data:', error); tbody.innerHTML = 'Error loading data'; } } // Debounce function to limit API calls function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func.apply(this, args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // Delete a record and all its links window.deleteRecord = async function(factType, recordId) { if (!confirm('Are you sure you want to delete this record and all its links? This action cannot be undone.')) { return; } try { const response = await fetch(`api/memory/${encodeURIComponent(recordId)}`, { method: 'DELETE' }); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || `HTTP ${response.status}`); } const result = await response.json(); alert(result.message || 'Record deleted successfully'); // Reload the table data await loadDataView(factType); } catch (e) { console.error('Error deleting record:', e); alert('Error deleting record: ' + e.message); } } // 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 phase view toggle document.getElementById(`debug-mode-retrieval-${paneId}`).addEventListener('change', function() { if (this.checked) { document.getElementById(`debug-retrieval-${paneId}`).style.display = 'block'; document.getElementById(`debug-rrf-${paneId}`).style.display = 'none'; document.getElementById(`debug-rerank-${paneId}`).style.display = 'none'; document.getElementById(`debug-final-${paneId}`).style.display = 'none'; } }); document.getElementById(`debug-mode-rrf-${paneId}`).addEventListener('change', function() { if (this.checked) { document.getElementById(`debug-retrieval-${paneId}`).style.display = 'none'; document.getElementById(`debug-rrf-${paneId}`).style.display = 'block'; document.getElementById(`debug-rerank-${paneId}`).style.display = 'none'; document.getElementById(`debug-final-${paneId}`).style.display = 'none'; } }); document.getElementById(`debug-mode-rerank-${paneId}`).addEventListener('change', function() { if (this.checked) { document.getElementById(`debug-retrieval-${paneId}`).style.display = 'none'; document.getElementById(`debug-rrf-${paneId}`).style.display = 'none'; document.getElementById(`debug-rerank-${paneId}`).style.display = 'block'; document.getElementById(`debug-final-${paneId}`).style.display = 'none'; } }); document.getElementById(`debug-mode-final-${paneId}`).addEventListener('change', function() { if (this.checked) { document.getElementById(`debug-retrieval-${paneId}`).style.display = 'none'; document.getElementById(`debug-rrf-${paneId}`).style.display = 'none'; document.getElementById(`debug-rerank-${paneId}`).style.display = 'none'; document.getElementById(`debug-final-${paneId}`).style.display = 'block'; } }); debugPanes.push({ id: paneId, element: paneDiv, cy: null, trace: null }); } // Switch between retrieval method tabs window.switchRetrievalTab = function(paneId, method) { // Update button states const buttons = document.querySelectorAll(`#debug-pane-${paneId} .retrieval-tab-btn`); buttons.forEach(btn => { btn.classList.remove('active'); if (btn.textContent.toLowerCase() === method) { btn.classList.add('active'); } }); // Show/hide content ['semantic', 'bm25', 'graph', 'temporal'].forEach(m => { const content = document.getElementById(`retrieval-${m}-${paneId}`); if (content) { content.style.display = m === method ? 'block' : 'none'; } }); } // Render retrieval results for all methods function renderRetrievalResults(paneId, trace) { if (!trace || !trace.retrieval_results) return; trace.retrieval_results.forEach(method => { const methodName = method.method_name; const contentDiv = document.getElementById(`retrieval-${methodName}-${paneId}`); if (!contentDiv) return; if (!method.results || method.results.length === 0) { contentDiv.innerHTML = '
No results from this retrieval method
'; return; } let html = `

${methodName.toUpperCase()} Retrieval (${method.results.length} results, ${method.duration_seconds.toFixed(3)}s)

`; method.results.forEach(result => { html += ` `; }); html += `
Rank Text Score
#${result.rank} ${result.text} ${result.score.toFixed(4)}
`; contentDiv.innerHTML = html; }); } // Render RRF merge results function renderRRFMerge(paneId, trace) { const contentDiv = document.getElementById(`debug-rrf-${paneId}`); if (!contentDiv || !trace || !trace.rrf_merged) return; if (trace.rrf_merged.length === 0) { contentDiv.innerHTML = '
No RRF merge data available
'; return; } let html = `

RRF Merge Results (${trace.rrf_merged.length} candidates)

Reciprocal Rank Fusion combines rankings from different retrieval methods.

`; trace.rrf_merged.forEach(result => { const sourceRanks = Object.entries(result.source_ranks) .map(([method, rank]) => `${method}: #${rank}`) .join(', '); html += ` `; }); html += `
RRF Rank Text RRF Score Source Ranks
#${result.final_rrf_rank} ${result.text} ${result.rrf_score.toFixed(4)} ${sourceRanks}
`; contentDiv.innerHTML = html; } // Render reranking results function renderReranking(paneId, trace) { const contentDiv = document.getElementById(`debug-rerank-${paneId}`); if (!contentDiv || !trace || !trace.reranked) return; if (trace.reranked.length === 0) { contentDiv.innerHTML = '
No reranking data available
'; return; } let html = `

Reranking Results (${trace.reranked.length} results)

Reranker adjusts scores based on semantic similarity, BM25, recency, and frequency. Blue highlight = rank improved vs RRF

`; trace.reranked.forEach(result => { const improved = result.rank_change > 0; const rowBg = improved ? '#e3f2fd' : 'white'; const changeDisplay = result.rank_change > 0 ? `↑${result.rank_change}` : result.rank_change < 0 ? `↓${Math.abs(result.rank_change)}` : '='; const changeColor = result.rank_change > 0 ? '#2e7d32' : result.rank_change < 0 ? '#d32f2f' : '#666'; const components = Object.entries(result.score_components) .map(([key, val]) => `${key.replace('_', ' ')}: ${val.toFixed(3)}`) .join('
'); html += ` `; }); html += `
Rerank RRF Rank Change Text Score Components
#${result.rerank_rank} #${result.rrf_rank} ${changeDisplay} ${result.text} ${result.rerank_score.toFixed(4)} ${components}
`; contentDiv.innerHTML = html; } // Render final MMR results function renderFinalResults(paneId, results, trace) { const contentDiv = document.getElementById(`debug-final-${paneId}`); if (!contentDiv) return; if (!results || results.length === 0) { contentDiv.innerHTML = '
No final results
'; return; } // Reuse the existing results table rendering but update it contentDiv.innerHTML = ''; const tableDiv = document.createElement('div'); tableDiv.style.padding = '15px'; contentDiv.appendChild(tableDiv); // Call the existing renderResultsTable logic but inline here renderResultsTableInline(tableDiv, results, trace); } // Inline version of results table rendering function renderResultsTableInline(tableDiv, results, trace) { 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 = `

Final 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
` : ''} `; // Calculate ranks for each metric const calculateRanks = (values) => { const indexed = values.map((val, idx) => ({ idx, val })); indexed.sort((a, b) => b.val - a.val); const ranks = new Map(); indexed.forEach((item, rank) => { ranks.set(item.idx, rank + 1); }); return ranks; }; // Extract all metric values for ranking const activations = results.map((result, idx) => { const visit = trace.visits?.find(v => v.node_id === result.id); return visit ? visit.weights.activation : 0; }); const similarities = results.map((result, idx) => { const visit = trace.visits?.find(v => v.node_id === result.id); return visit ? visit.weights.semantic_similarity : 0; }); const recencies = results.map((result, idx) => { const visit = trace.visits?.find(v => v.node_id === result.id); return visit ? (visit.weights.recency || 0) : 0; }); const frequencies = results.map((result, idx) => { const visit = trace.visits?.find(v => v.node_id === result.id); return visit ? (visit.weights.frequency || 0) : 0; }); // Calculate ranks const activationRanks = calculateRanks(activations); const similarityRanks = calculateRanks(similarities); const recencyRanks = calculateRanks(recencies); const frequencyRanks = calculateRanks(frequencies); 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 ranks const activationRank = activationRanks.get(idx); const similarityRank = similarityRanks.get(idx); const recencyRank = recencyRanks.get(idx); const frequencyRank = frequencyRanks.get(idx); // 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)} (#${activationRank}) ${similarity.toFixed(4)} (#${similarityRank}) ${recency.toFixed(4)} (#${recencyRank}) ${frequency.toFixed(4)} (#${frequencyRank}) ${mmrScore !== null && mmrScore !== undefined ? mmrScore.toFixed(4) : '-'} ${mmrRelevance !== null && mmrRelevance !== undefined ? mmrRelevance.toFixed(4) : '-'} ${mmrMaxSim !== null && mmrMaxSim !== undefined ? mmrMaxSim.toFixed(4) : '-'}
`; tableDiv.innerHTML = html; } 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 reranker = document.getElementById(`search-reranker-${paneId}`).value; const thinkingBudget = parseInt(document.getElementById(`search-budget-${paneId}`).value); const maxTokens = parseInt(document.getElementById(`search-max-tokens-${paneId}`).value); const statusBar = document.getElementById(`debug-status-${paneId}`); // Get agent from global selector const agentSelect = document.getElementById('global-agent-selector'); const agentId = agentSelect ? agentSelect.value : null; if (!query) { alert('Please enter a query'); return; } if (!agentId) { alert('Please select an agent from the breadcrumb'); return; } try { // Prepare request body with optional fact_type const requestBody = { query: query, agent_id: agentId, thinking_budget: thinkingBudget, max_tokens: maxTokens, reranker: reranker, trace: true }; // Add fact_type if not 'all' if (searchType !== 'all') { requestBody.fact_type = searchType; } statusBar.innerHTML = '🔄 Searching...'; const response = await fetch('api/search', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestBody) }); 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 `; // Store trace and results pane.trace = data.trace; pane.results = data.results; // Render all views renderRetrievalResults(paneId, data.trace); renderRRFMerge(paneId, data.trace); renderReranking(paneId, data.trace); renderFinalResults(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
` : ''} `; // Calculate ranks for each metric const calculateRanks = (values) => { // Create array of {index, value} pairs const indexed = values.map((val, idx) => ({ idx, val })); // Sort by value descending (highest first) indexed.sort((a, b) => b.val - a.val); // Create rank map const ranks = new Map(); indexed.forEach((item, rank) => { ranks.set(item.idx, rank + 1); }); return ranks; }; // Extract all metric values for ranking const activations = results.map((result, idx) => { const visit = trace.visits.find(v => v.node_id === result.id); return visit ? visit.weights.activation : 0; }); const similarities = results.map((result, idx) => { const visit = trace.visits.find(v => v.node_id === result.id); return visit ? visit.weights.semantic_similarity : 0; }); const recencies = results.map((result, idx) => { const visit = trace.visits.find(v => v.node_id === result.id); return visit ? (visit.weights.recency || 0) : 0; }); const frequencies = results.map((result, idx) => { const visit = trace.visits.find(v => v.node_id === result.id); return visit ? (visit.weights.frequency || 0) : 0; }); // Calculate ranks const activationRanks = calculateRanks(activations); const similarityRanks = calculateRanks(similarities); const recencyRanks = calculateRanks(recencies); const frequencyRanks = calculateRanks(frequencies); 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 ranks const activationRank = activationRanks.get(idx); const similarityRank = similarityRanks.get(idx); const recencyRank = recencyRanks.get(idx); const frequencyRank = frequencyRanks.get(idx); // 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)} (#${activationRank}) ${similarity.toFixed(4)} (#${similarityRank}) ${recency.toFixed(4)} (#${recencyRank}) ${frequency.toFixed(4)} (#${frequencyRank}) ${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 stats section const statsSection = document.getElementById('stats-section'); if (statsSection) { statsSection.style.display = hasAgent ? 'block' : 'none'; } // Update each data subtab ['world', 'agent', 'opinion', 'documents'].forEach(factType => { const noAgentMsg = document.getElementById(`${factType}-no-agent-message`); const content = document.getElementById(`${factType}-content`); if (noAgentMsg) noAgentMsg.style.display = hasAgent ? 'none' : 'block'; if (content) content.style.display = hasAgent ? 'block' : 'none'; }); } // Load statistics for current agent async function loadStats() { if (!currentAgentId) return; try { const response = await fetch(`./api/stats/${encodeURIComponent(currentAgentId)}`); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const stats = await response.json(); // Update total stats document.getElementById('stat-total-nodes').textContent = stats.total_nodes.toLocaleString(); document.getElementById('stat-total-links').textContent = stats.total_links.toLocaleString(); // Update nodes by type document.getElementById('stat-world-nodes').textContent = (stats.nodes_by_type.world || 0).toLocaleString(); document.getElementById('stat-agent-nodes').textContent = (stats.nodes_by_type.agent || 0).toLocaleString(); document.getElementById('stat-opinion-nodes').textContent = (stats.nodes_by_type.opinion || 0).toLocaleString(); // Update links by type document.getElementById('stat-temporal-links').textContent = (stats.links_by_type.temporal || 0).toLocaleString(); document.getElementById('stat-semantic-links').textContent = (stats.links_by_type.semantic || 0).toLocaleString(); document.getElementById('stat-entity-links').textContent = (stats.links_by_type.entity || 0).toLocaleString(); // Update documents count document.getElementById('stat-documents').textContent = (stats.total_documents || 0).toLocaleString(); } catch (e) { console.error('Error loading stats:', e); // Reset to dashes on error ['stat-total-nodes', 'stat-world-nodes', 'stat-agent-nodes', 'stat-opinion-nodes', 'stat-total-links', 'stat-temporal-links', 'stat-semantic-links', 'stat-entity-links', 'stat-documents'].forEach(id => { document.getElementById(id).textContent = '-'; }); } } // Refresh all tabs with new agent context async function refreshAllTabs() { // Clear existing data dataCache = { world: null, agent: null, opinion: null }; // Destroy existing graphs ['world', 'agent', 'opinion'].forEach(factType => { if (dataGraphs[factType]) { dataGraphs[factType].destroy(); dataGraphs[factType] = null; } }); // Load statistics await loadStats(); } // 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); 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 }); // 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 }) }); 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-opinion-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