// CLI History Component // Displays execution history with filtering, search, and delete // ========== CLI History State ========== let cliExecutionHistory = []; let cliHistoryFilter = null; // Filter by tool let cliHistorySearch = ''; // Search query let cliHistoryLimit = 50; // ========== Data Loading ========== async function loadCliHistory(options = {}) { try { const { limit = cliHistoryLimit, tool = cliHistoryFilter, status = null } = options; let url = `/api/cli/history?path=${encodeURIComponent(projectPath)}&limit=${limit}`; if (tool) url += `&tool=${tool}`; if (status) url += `&status=${status}`; const response = await fetch(url); if (!response.ok) throw new Error('Failed to load CLI history'); const data = await response.json(); cliExecutionHistory = data.executions || []; return data; } catch (err) { console.error('Failed to load CLI history:', err); return { executions: [], total: 0, count: 0 }; } } async function loadExecutionDetail(executionId) { try { const url = `/api/cli/execution?path=${encodeURIComponent(projectPath)}&id=${encodeURIComponent(executionId)}`; const response = await fetch(url); if (!response.ok) throw new Error('Execution not found'); return await response.json(); } catch (err) { console.error('Failed to load execution detail:', err); return null; } } // ========== Rendering ========== function renderCliHistory() { const container = document.getElementById('cli-history-panel'); if (!container) return; // Filter by search query const filteredHistory = cliHistorySearch ? cliExecutionHistory.filter(exec => exec.prompt_preview.toLowerCase().includes(cliHistorySearch.toLowerCase()) || exec.tool.toLowerCase().includes(cliHistorySearch.toLowerCase()) ) : cliExecutionHistory; if (cliExecutionHistory.length === 0) { container.innerHTML = `

Execution History

${renderHistorySearch()} ${renderToolFilter()}

No executions yet

`; if (window.lucide) lucide.createIcons(); return; } const historyHtml = filteredHistory.length === 0 ? `

No matching results

` : filteredHistory.map(exec => { const statusIcon = exec.status === 'success' ? 'check-circle' : exec.status === 'timeout' ? 'clock' : 'x-circle'; const statusClass = exec.status === 'success' ? 'text-success' : exec.status === 'timeout' ? 'text-warning' : 'text-destructive'; const duration = formatDuration(exec.duration_ms); const timeAgo = getTimeAgo(new Date(exec.timestamp)); return `
${exec.tool} ${timeAgo}
${escapeHtml(exec.prompt_preview)}
${duration} ${exec.mode || 'analysis'}
`; }).join(''); container.innerHTML = `

Execution History

${renderHistorySearch()} ${renderToolFilter()}
${historyHtml}
`; if (window.lucide) lucide.createIcons(); } function renderHistorySearch() { return ` `; } function renderToolFilter() { const tools = ['all', 'gemini', 'qwen', 'codex']; return ` `; } // ========== Execution Detail Modal ========== async function showExecutionDetail(executionId) { const detail = await loadExecutionDetail(executionId); if (!detail) { showRefreshToast('Execution not found', 'error'); return; } const modalContent = `
${detail.tool} ${detail.status} ${formatDuration(detail.duration_ms)}
${detail.model || 'default'} ${detail.mode} ${new Date(detail.timestamp).toLocaleString()}

Prompt

${escapeHtml(detail.prompt)}
${detail.output.stdout ? `

Output

${escapeHtml(detail.output.stdout)}
` : ''} ${detail.output.stderr ? `

Errors

${escapeHtml(detail.output.stderr)}
` : ''} ${detail.output.truncated ? `

Output was truncated due to size.

` : ''}
`; showModal('Execution Detail', modalContent); } // ========== Actions ========== async function filterCliHistory(tool) { cliHistoryFilter = tool || null; await loadCliHistory(); renderCliHistory(); } function searchCliHistory(query) { cliHistorySearch = query; renderCliHistory(); // Preserve focus and cursor position const searchInput = document.querySelector('.cli-history-search'); if (searchInput) { searchInput.focus(); searchInput.setSelectionRange(query.length, query.length); } } async function refreshCliHistory() { await loadCliHistory(); renderCliHistory(); showRefreshToast('History refreshed', 'success'); } // ========== Delete Execution ========== function confirmDeleteExecution(executionId) { if (confirm('Delete this execution record? This action cannot be undone.')) { deleteExecution(executionId); } } async function deleteExecution(executionId) { try { const response = await fetch(`/api/cli/execution?path=${encodeURIComponent(projectPath)}&id=${encodeURIComponent(executionId)}`, { method: 'DELETE' }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to delete'); } // Remove from local state cliExecutionHistory = cliExecutionHistory.filter(exec => exec.id !== executionId); renderCliHistory(); showRefreshToast('Execution deleted', 'success'); } catch (err) { console.error('Failed to delete execution:', err); showRefreshToast('Delete failed: ' + err.message, 'error'); } } // ========== Copy Prompt ========== async function copyExecutionPrompt(executionId) { const detail = await loadExecutionDetail(executionId); if (!detail) { showRefreshToast('Execution not found', 'error'); return; } if (navigator.clipboard) { try { await navigator.clipboard.writeText(detail.prompt); showRefreshToast('Prompt copied to clipboard', 'success'); } catch (err) { showRefreshToast('Failed to copy', 'error'); } } } // ========== Helpers ========== function formatDuration(ms) { if (ms >= 60000) { const mins = Math.floor(ms / 60000); const secs = Math.round((ms % 60000) / 1000); return `${mins}m ${secs}s`; } else if (ms >= 1000) { return `${(ms / 1000).toFixed(1)}s`; } return `${ms}ms`; } function getTimeAgo(date) { const seconds = Math.floor((new Date() - date) / 1000); if (seconds < 60) return 'just now'; if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`; if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`; if (seconds < 604800) return `${Math.floor(seconds / 86400)}d ago`; return date.toLocaleDateString(); }