diff --git a/app.js b/app.js index 90ec33c0..ac5e752a 100644 --- a/app.js +++ b/app.js @@ -9,6 +9,7 @@ import { MemoryBirth } from './nexus/components/memory-birth.js'; import { MemoryOptimizer } from './nexus/components/memory-optimizer.js'; import { MemoryInspect } from './nexus/components/memory-inspect.js'; import { MemoryPulse } from './nexus/components/memory-pulse.js'; +import { ReasoningTrace } from './nexus/components/reasoning-trace.js'; // ═══════════════════════════════════════════ // NEXUS v1.1 — Portal System Update @@ -758,6 +759,7 @@ async function init() { SpatialAudio.bindSpatialMemory(SpatialMemory); MemoryInspect.init({ onNavigate: _navigateToMemory }); MemoryPulse.init(SpatialMemory); + ReasoningTrace.init(); updateLoad(90); loadSession(); diff --git a/index.html b/index.html index 60999bad..0f487554 100644 --- a/index.html +++ b/index.html @@ -101,6 +101,19 @@
ADAPTIVE CALIBRATOR
+
+
+
🧠 REASONING TRACE
+
+ + + +
+
+
No active task
+
0 steps
+
+
diff --git a/nexus/components/reasoning-trace.js b/nexus/components/reasoning-trace.js new file mode 100644 index 00000000..9a6881d3 --- /dev/null +++ b/nexus/components/reasoning-trace.js @@ -0,0 +1,451 @@ +// ═══════════════════════════════════════════════════ +// 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 }; \ No newline at end of file diff --git a/style.css b/style.css index ff8f34fb..961e1a9a 100644 --- a/style.css +++ b/style.css @@ -2685,3 +2685,252 @@ body.operator-mode #mode-label { color: #ffd700; } +/* ═══ REASONING TRACE COMPONENT ═══ */ + +.reasoning-trace { + width: 320px; + max-height: 400px; + display: flex; + flex-direction: column; +} + +.trace-header-container { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 4px; +} + +.trace-header-container .panel-header { + margin-bottom: 0; + border-bottom: none; + padding-bottom: 0; +} + +.trace-icon { + margin-right: 4px; +} + +.trace-controls { + display: flex; + gap: 4px; +} + +.trace-btn { + background: rgba(74, 240, 192, 0.1); + border: 1px solid rgba(74, 240, 192, 0.2); + color: #4af0c0; + padding: 2px 6px; + font-size: 10px; + cursor: pointer; + border-radius: 2px; + transition: all 0.2s ease; +} + +.trace-btn:hover { + background: rgba(74, 240, 192, 0.2); + border-color: #4af0c0; +} + +.trace-task { + font-size: 9px; + color: #8899aa; + margin-bottom: 4px; + padding: 2px 6px; + background: rgba(0, 0, 0, 0.2); + border-radius: 2px; + font-family: 'JetBrains Mono', monospace; +} + +.trace-task.active { + color: #4af0c0; + background: rgba(74, 240, 192, 0.1); + border-left: 2px solid #4af0c0; +} + +.trace-counter { + font-size: 9px; + color: #667788; + margin-bottom: 6px; + font-family: 'JetBrains Mono', monospace; +} + +.trace-content { + flex: 1; + overflow-y: auto; + max-height: 300px; +} + +.trace-step { + margin-bottom: 8px; + padding: 6px; + background: rgba(0, 0, 0, 0.2); + border-radius: 3px; + border-left: 3px solid #4af0c0; + transition: all 0.2s ease; +} + +.trace-step-think { + border-left-color: #4af0c0; +} + +.trace-step-decide { + border-left-color: #ffd700; +} + +.trace-step-recall { + border-left-color: #7b5cff; +} + +.trace-step-plan { + border-left-color: #ff8c42; +} + +.trace-step-execute { + border-left-color: #ff4466; +} + +.trace-step-verify { + border-left-color: #4af0c0; +} + +.trace-step-doubt { + border-left-color: #ff8c42; +} + +.trace-step-memory { + border-left-color: #7b5cff; +} + +.trace-step-header { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 4px; + font-size: 10px; +} + +.step-icon { + font-size: 12px; +} + +.step-type { + font-weight: 700; + letter-spacing: 0.5px; + font-family: 'JetBrains Mono', monospace; +} + +.step-time { + color: #667788; + font-size: 9px; + margin-left: auto; + font-family: 'JetBrains Mono', monospace; +} + +.confidence-bar { + font-family: 'JetBrains Mono', monospace; + font-size: 9px; + color: #4af0c0; + letter-spacing: -1px; + margin-left: 4px; +} + +.trace-step-content { + font-size: 11px; + line-height: 1.4; + color: #d9f7ff; +} + +.step-thought { + margin-bottom: 4px; + font-style: italic; + color: #e0f0ff; +} + +.step-reasoning { + margin-bottom: 4px; + color: #aabbcc; + font-size: 10px; + padding-left: 8px; + border-left: 1px solid rgba(74, 240, 192, 0.2); +} + +.step-decision { + margin-bottom: 4px; + color: #ffd700; + font-size: 10px; +} + +.step-alternatives { + margin-bottom: 4px; + color: #8899aa; + font-size: 10px; +} + +.step-source { + margin-bottom: 4px; + color: #7b5cff; + font-size: 10px; +} + +.trace-separator { + height: 1px; + background: linear-gradient(90deg, transparent, rgba(74, 240, 192, 0.2), transparent); + margin: 6px 0; +} + +.trace-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 20px; + color: #667788; + text-align: center; +} + +.empty-icon { + font-size: 24px; + margin-bottom: 8px; + opacity: 0.5; +} + +.empty-text { + font-size: 11px; + margin-bottom: 4px; + font-family: 'JetBrains Mono', monospace; +} + +.empty-hint { + font-size: 9px; + color: #445566; + font-family: 'JetBrains Mono', monospace; +} + +/* Animation for new steps */ +@keyframes trace-step-in { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.trace-step { + animation: trace-step-in 0.3s ease-out; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .reasoning-trace { + width: 280px; + } + + .trace-content { + max-height: 200px; + } +} +