// ═══════════════════════════════════════════════════ // REASONING TRACE HUD COMPONENT // ═══════════════════════════════════════════════════ // // Displays a real-time trace of the agent's reasoning // steps during complex task execution. Shows the chain // of thought, decision points, and confidence levels. // // Usage: // ReasoningTrace.init(); // ReasoningTrace.addStep(step); // ReasoningTrace.clear(); // ReasoningTrace.toggle(); // ═══════════════════════════════════════════════════ const ReasoningTrace = (() => { // ── State ───────────────────────────────────────── let _container = null; let _content = null; let _header = null; let _steps = []; let _maxSteps = 20; let _isVisible = true; let _currentTask = null; let _stepCounter = 0; // ── Config ──────────────────────────────────────── const STEP_TYPES = { THINK: { icon: '💭', color: '#4af0c0', label: 'THINK' }, DECIDE: { icon: '⚖️', color: '#ffd700', label: 'DECIDE' }, RECALL: { icon: '🔍', color: '#7b5cff', label: 'RECALL' }, PLAN: { icon: '📋', color: '#ff8c42', label: 'PLAN' }, EXECUTE: { icon: '⚡', color: '#ff4466', label: 'EXECUTE' }, VERIFY: { icon: '✅', color: '#4af0c0', label: 'VERIFY' }, DOUBT: { icon: '❓', color: '#ff8c42', label: 'DOUBT' }, MEMORY: { icon: '💾', color: '#7b5cff', label: 'MEMORY' } }; // ── Helpers ─────────────────────────────────────── function _escapeHtml(s) { return String(s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function _formatTimestamp(timestamp) { const date = new Date(timestamp); return date.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' }); } function _getConfidenceBar(confidence) { if (confidence === undefined || confidence === null) return ''; const percent = Math.max(0, Math.min(100, Math.round(confidence * 100))); const bars = Math.round(percent / 10); const filled = '█'.repeat(bars); const empty = '░'.repeat(10 - bars); return `${filled}${empty}`; } // ── DOM Setup ───────────────────────────────────── function _createDOM() { // Create container if it doesn't exist if (_container) return; _container = document.createElement('div'); _container.id = 'reasoning-trace'; _container.className = 'hud-panel reasoning-trace'; _header = document.createElement('div'); _header.className = 'panel-header'; _header.innerHTML = `🧠 REASONING TRACE`; // Task indicator const taskIndicator = document.createElement('div'); taskIndicator.className = 'trace-task'; taskIndicator.id = 'trace-task'; taskIndicator.textContent = 'No active task'; // Step counter const stepCounter = document.createElement('div'); stepCounter.className = 'trace-counter'; stepCounter.id = 'trace-counter'; stepCounter.textContent = '0 steps'; // Controls const controls = document.createElement('div'); controls.className = 'trace-controls'; controls.innerHTML = ` `; // Header container const headerContainer = document.createElement('div'); headerContainer.className = 'trace-header-container'; headerContainer.appendChild(_header); headerContainer.appendChild(controls); // Content area _content = document.createElement('div'); _content.className = 'panel-content trace-content'; _content.id = 'reasoning-trace-content'; // Assemble _container.appendChild(headerContainer); _container.appendChild(taskIndicator); _container.appendChild(stepCounter); _container.appendChild(_content); // Add to HUD const hud = document.getElementById('hud'); if (hud) { const gofaiHud = hud.querySelector('.gofai-hud'); if (gofaiHud) { gofaiHud.appendChild(_container); } else { hud.appendChild(_container); } } // Add event listeners document.getElementById('trace-clear')?.addEventListener('click', clear); document.getElementById('trace-toggle')?.addEventListener('click', toggle); document.getElementById('trace-export')?.addEventListener('click', exportTrace); } // ── Rendering ───────────────────────────────────── function _renderStep(step, index) { const typeConfig = STEP_TYPES[step.type] || STEP_TYPES.THINK; const timestamp = _formatTimestamp(step.timestamp); const confidence = _getConfidenceBar(step.confidence); const stepEl = document.createElement('div'); stepEl.className = `trace-step trace-step-${step.type.toLowerCase()}`; stepEl.dataset.stepId = step.id; // Step header const header = document.createElement('div'); header.className = 'trace-step-header'; header.innerHTML = ` ${typeConfig.icon} ${typeConfig.label} ${timestamp} ${confidence} `; // Step content const content = document.createElement('div'); content.className = 'trace-step-content'; if (step.thought) { const thought = document.createElement('div'); thought.className = 'step-thought'; thought.textContent = step.thought; content.appendChild(thought); } if (step.reasoning) { const reasoning = document.createElement('div'); reasoning.className = 'step-reasoning'; reasoning.textContent = step.reasoning; content.appendChild(reasoning); } if (step.decision) { const decision = document.createElement('div'); decision.className = 'step-decision'; decision.innerHTML = `Decision: ${_escapeHtml(step.decision)}`; content.appendChild(decision); } if (step.alternatives && step.alternatives.length > 0) { const alternatives = document.createElement('div'); alternatives.className = 'step-alternatives'; alternatives.innerHTML = `Alternatives: ${step.alternatives.map(a => _escapeHtml(a)).join(', ')}`; content.appendChild(alternatives); } if (step.source) { const source = document.createElement('div'); source.className = 'step-source'; source.innerHTML = `Source: ${_escapeHtml(step.source)}`; content.appendChild(source); } stepEl.appendChild(header); stepEl.appendChild(content); return stepEl; } function _render() { if (!_content) return; // Clear content _content.innerHTML = ''; // Update task indicator const taskEl = document.getElementById('trace-task'); if (taskEl) { taskEl.textContent = _currentTask || 'No active task'; taskEl.className = _currentTask ? 'trace-task active' : 'trace-task'; } // Update step counter const counterEl = document.getElementById('trace-counter'); if (counterEl) { counterEl.textContent = `${_steps.length} step${_steps.length !== 1 ? 's' : ''}`; } // Render steps (newest first) const sortedSteps = [..._steps].sort((a, b) => b.timestamp - a.timestamp); for (let i = 0; i < sortedSteps.length; i++) { const stepEl = _renderStep(sortedSteps[i], i); _content.appendChild(stepEl); // Add separator between steps if (i < sortedSteps.length - 1) { const separator = document.createElement('div'); separator.className = 'trace-separator'; _content.appendChild(separator); } } // Show empty state if no steps if (_steps.length === 0) { const empty = document.createElement('div'); empty.className = 'trace-empty'; empty.innerHTML = ` 💭 No reasoning steps yet Start a task to see the trace `; _content.appendChild(empty); } } // ── Public API ──────────────────────────────────── function init() { _createDOM(); _render(); console.info('[ReasoningTrace] Initialized'); } /** * Add a reasoning step to the trace. * @param {Object} step - The reasoning step * @param {string} step.type - Step type (THINK, DECIDE, RECALL, PLAN, EXECUTE, VERIFY, DOUBT, MEMORY) * @param {string} step.thought - The main thought/content * @param {string} [step.reasoning] - Detailed reasoning * @param {string} [step.decision] - Decision made * @param {string[]} [step.alternatives] - Alternative options considered * @param {string} [step.source] - Source of information * @param {number} [step.confidence] - Confidence level (0-1) * @param {string} [step.taskId] - Associated task ID */ function addStep(step) { if (!step || !step.thought) return; // Generate unique ID const id = `step-${++_stepCounter}-${Date.now()}`; // Create step object const newStep = { id, timestamp: Date.now(), type: step.type || 'THINK', thought: step.thought, reasoning: step.reasoning || null, decision: step.decision || null, alternatives: step.alternatives || null, source: step.source || null, confidence: step.confidence !== undefined ? Math.max(0, Math.min(1, step.confidence)) : null, taskId: step.taskId || _currentTask }; // Add to steps array _steps.unshift(newStep); // Limit number of steps if (_steps.length > _maxSteps) { _steps = _steps.slice(0, _maxSteps); } // Update task if provided if (step.taskId && step.taskId !== _currentTask) { setTask(step.taskId); } // Re-render _render(); // Log to console for debugging console.debug(`[ReasoningTrace] ${newStep.type}: ${newStep.thought}`); return newStep.id; } /** * Set the current task being traced. * @param {string} taskId - Task identifier */ function setTask(taskId) { _currentTask = taskId; _render(); console.info(`[ReasoningTrace] Task set: ${taskId}`); } /** * Clear all steps from the trace. */ function clear() { _steps = []; _stepCounter = 0; _render(); console.info('[ReasoningTrace] Cleared'); } /** * Toggle the visibility of the trace panel. */ function toggle() { _isVisible = !_isVisible; if (_container) { _container.style.display = _isVisible ? 'block' : 'none'; } console.info(`[ReasoningTrace] Visibility: ${_isVisible ? 'shown' : 'hidden'}`); } /** * Export the trace as JSON. * @returns {string} JSON string of the trace */ function exportTrace() { const exportData = { task: _currentTask, exportedAt: new Date().toISOString(), steps: _steps.map(step => ({ type: step.type, thought: step.thought, reasoning: step.reasoning, decision: step.decision, alternatives: step.alternatives, source: step.source, confidence: step.confidence, timestamp: new Date(step.timestamp).toISOString() })) }; const json = JSON.stringify(exportData, null, 2); // Copy to clipboard navigator.clipboard.writeText(json).then(() => { console.info('[ReasoningTrace] Copied to clipboard'); // Show feedback const btn = document.getElementById('trace-export'); if (btn) { const original = btn.innerHTML; btn.innerHTML = '✅'; setTimeout(() => { btn.innerHTML = original; }, 1000); } }).catch(err => { console.error('[ReasoningTrace] Failed to copy:', err); }); return json; } /** * Get the current trace data. * @returns {Object} Current trace state */ function getTrace() { return { task: _currentTask, steps: [..._steps], stepCount: _steps.length, isVisible: _isVisible }; } /** * Get steps filtered by type. * @param {string} type - Step type to filter by * @returns {Array} Filtered steps */ function getStepsByType(type) { return _steps.filter(step => step.type === type); } /** * Get steps for a specific task. * @param {string} taskId - Task ID to filter by * @returns {Array} Filtered steps */ function getStepsByTask(taskId) { return _steps.filter(step => step.taskId === taskId); } /** * Mark the current task as complete. * @param {string} [result] - Optional result description */ function completeTask(result) { if (_currentTask) { addStep({ type: 'VERIFY', thought: `Task completed: ${result || 'Success'}`, taskId: _currentTask }); // Clear current task after a delay setTimeout(() => { _currentTask = null; _render(); }, 2000); } } // ── Return Public API ───────────────────────────── return { init, addStep, setTask, clear, toggle, exportTrace, getTrace, getStepsByType, getStepsByTask, completeTask, STEP_TYPES }; })(); export { ReasoningTrace };