// CLI Status Component // Displays CLI tool availability status and allows setting default tool // ========== CLI State ========== let cliToolStatus = { gemini: {}, qwen: {}, codex: {}, claude: {} }; let codexLensStatus = { ready: false }; let semanticStatus = { available: false }; let defaultCliTool = 'gemini'; let promptConcatFormat = localStorage.getItem('ccw-prompt-format') || 'plain'; // plain, yaml, json // Smart Context settings let smartContextEnabled = localStorage.getItem('ccw-smart-context') === 'true'; let smartContextMaxFiles = parseInt(localStorage.getItem('ccw-smart-context-max-files') || '10', 10); // Native Resume settings let nativeResumeEnabled = localStorage.getItem('ccw-native-resume') !== 'false'; // default true // LLM Enhancement settings for Semantic Search let llmEnhancementSettings = { enabled: localStorage.getItem('ccw-llm-enhancement-enabled') === 'true', tool: localStorage.getItem('ccw-llm-enhancement-tool') || 'gemini', fallbackTool: localStorage.getItem('ccw-llm-enhancement-fallback') || 'qwen', batchSize: parseInt(localStorage.getItem('ccw-llm-enhancement-batch-size') || '5', 10), timeoutMs: parseInt(localStorage.getItem('ccw-llm-enhancement-timeout') || '300000', 10) }; // ========== Initialization ========== function initCliStatus() { // Load CLI status on init loadCliToolStatus(); loadCodexLensStatus(); } // ========== Data Loading ========== async function loadCliToolStatus() { try { const response = await fetch('/api/cli/status'); if (!response.ok) throw new Error('Failed to load CLI status'); const data = await response.json(); cliToolStatus = data; // Update badge updateCliBadge(); return data; } catch (err) { console.error('Failed to load CLI status:', err); return null; } } async function loadCodexLensStatus() { try { const response = await fetch('/api/codexlens/status'); if (!response.ok) throw new Error('Failed to load CodexLens status'); const data = await response.json(); codexLensStatus = data; // Update CodexLens badge updateCodexLensBadge(); // If CodexLens is ready, also check semantic status if (data.ready) { await loadSemanticStatus(); } return data; } catch (err) { console.error('Failed to load CodexLens status:', err); return null; } } async function loadSemanticStatus() { try { const response = await fetch('/api/codexlens/semantic/status'); if (!response.ok) throw new Error('Failed to load semantic status'); const data = await response.json(); semanticStatus = data; return data; } catch (err) { console.error('Failed to load semantic status:', err); return null; } } // ========== Badge Update ========== function updateCliBadge() { const badge = document.getElementById('badgeCliTools'); if (badge) { const available = Object.values(cliToolStatus).filter(t => t.available).length; const total = Object.keys(cliToolStatus).length; badge.textContent = `${available}/${total}`; badge.classList.toggle('text-success', available === total); badge.classList.toggle('text-warning', available > 0 && available < total); badge.classList.toggle('text-destructive', available === 0); } } function updateCodexLensBadge() { const badge = document.getElementById('badgeCodexLens'); if (badge) { badge.textContent = codexLensStatus.ready ? 'Ready' : 'Not Installed'; badge.classList.toggle('text-success', codexLensStatus.ready); badge.classList.toggle('text-muted-foreground', !codexLensStatus.ready); } } // ========== Rendering ========== function renderCliStatus() { const container = document.getElementById('cli-status-panel'); if (!container) return; const toolDescriptions = { gemini: 'Google AI for code analysis', qwen: 'Alibaba AI assistant', codex: 'OpenAI code generation', claude: 'Anthropic AI assistant' }; const toolIcons = { gemini: 'sparkle', qwen: 'bot', codex: 'code-2', claude: 'brain' }; const tools = ['gemini', 'qwen', 'codex', 'claude']; const toolsHtml = tools.map(tool => { const status = cliToolStatus[tool] || {}; const isAvailable = status.available; const isDefault = defaultCliTool === tool; return `
${tool.charAt(0).toUpperCase() + tool.slice(1)} ${isDefault ? 'Default' : ''}
${toolDescriptions[tool]}
${isAvailable ? ` Ready` : ` Not Installed` }
${isAvailable && !isDefault ? `` : '' }
`; }).join(''); // CodexLens card with semantic search info const codexLensHtml = `
CodexLens Index
${codexLensStatus.ready ? 'Code indexing & FTS search' : 'Full-text code search engine'}
${codexLensStatus.ready ? ` v${codexLensStatus.version || 'installed'}` : ` Not Installed` }
${!codexLensStatus.ready ? `` : ` ` }
`; // Semantic Search card (only show if CodexLens is installed) const llmStatusBadge = llmEnhancementSettings.enabled ? `LLM` : ''; const semanticHtml = codexLensStatus.ready ? `
Semantic Search AI ${llmStatusBadge}
${semanticStatus.available ? 'AI-powered code understanding' : 'Natural language code search'}
${semanticStatus.available ? ` ${semanticStatus.backend || 'Ready'}` : ` Not Installed` }
${!semanticStatus.available ? `
~500MB
` : `
bge-small-en-v1.5
`}
` : ''; // CLI Settings section const settingsHtml = `

Settings

Format for multi-turn conversation concatenation

SQLite

CLI history stored in SQLite with FTS search

Auto-analyze prompt and add relevant file paths

Use native tool resume (gemini -r, qwen --resume, codex resume, claude --resume)

Maximum files to include in smart context

`; container.innerHTML = `

CLI Tools

${toolsHtml} ${codexLensHtml} ${semanticHtml}
${settingsHtml} `; // Initialize Lucide icons if (window.lucide) { lucide.createIcons(); } } // ========== Actions ========== function setDefaultCliTool(tool) { defaultCliTool = tool; renderCliStatus(); showRefreshToast(`Default CLI tool set to ${tool}`, 'success'); } function setPromptFormat(format) { promptConcatFormat = format; localStorage.setItem('ccw-prompt-format', format); showRefreshToast(`Prompt format set to ${format.toUpperCase()}`, 'success'); } function setSmartContextEnabled(enabled) { smartContextEnabled = enabled; localStorage.setItem('ccw-smart-context', enabled.toString()); // Re-render the appropriate settings panel if (typeof renderCliSettingsSection === 'function') { renderCliSettingsSection(); } else { renderCliStatus(); } showRefreshToast(`Smart Context ${enabled ? 'enabled' : 'disabled'}`, 'success'); } function setSmartContextMaxFiles(max) { smartContextMaxFiles = parseInt(max, 10); localStorage.setItem('ccw-smart-context-max-files', max); showRefreshToast(`Smart Context max files set to ${max}`, 'success'); } function setNativeResumeEnabled(enabled) { nativeResumeEnabled = enabled; localStorage.setItem('ccw-native-resume', enabled.toString()); showRefreshToast(`Native Resume ${enabled ? 'enabled' : 'disabled'}`, 'success'); } async function refreshAllCliStatus() { await Promise.all([loadCliToolStatus(), loadCodexLensStatus()]); renderCliStatus(); } function installCodexLens() { openCodexLensInstallWizard(); } function openCodexLensInstallWizard() { const modal = document.createElement('div'); modal.id = 'codexlensInstallModal'; modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50'; modal.innerHTML = `

Install CodexLens

Python-based code indexing engine

What will be installed:

  • Python virtual environment - Isolated Python environment
  • CodexLens package - Code indexing and search engine
  • SQLite FTS5 - Full-text search database

Installation Location

~/.codexlens/venv

First installation may take 2-3 minutes to download and setup Python packages.

`; document.body.appendChild(modal); if (window.lucide) { lucide.createIcons(); } } function closeCodexLensInstallWizard() { const modal = document.getElementById('codexlensInstallModal'); if (modal) { modal.remove(); } } async function startCodexLensInstall() { const progressDiv = document.getElementById('codexlensInstallProgress'); const installBtn = document.getElementById('codexlensInstallBtn'); const statusText = document.getElementById('codexlensInstallStatus'); const progressBar = document.getElementById('codexlensProgressBar'); // Show progress, disable button progressDiv.classList.remove('hidden'); installBtn.disabled = true; installBtn.innerHTML = 'Installing...'; // Simulate progress stages const stages = [ { progress: 10, text: 'Creating virtual environment...' }, { progress: 30, text: 'Installing pip packages...' }, { progress: 50, text: 'Installing CodexLens package...' }, { progress: 70, text: 'Setting up Python dependencies...' }, { progress: 90, text: 'Finalizing installation...' } ]; let currentStage = 0; const progressInterval = setInterval(() => { if (currentStage < stages.length) { statusText.textContent = stages[currentStage].text; progressBar.style.width = `${stages[currentStage].progress}%`; currentStage++; } }, 1500); try { const response = await fetch('/api/codexlens/bootstrap', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) }); clearInterval(progressInterval); const result = await response.json(); if (result.success) { progressBar.style.width = '100%'; statusText.textContent = 'Installation complete!'; setTimeout(() => { closeCodexLensInstallWizard(); showRefreshToast('CodexLens installed successfully!', 'success'); loadCodexLensStatus().then(() => renderCliStatus()); }, 1000); } else { statusText.textContent = `Error: ${result.error}`; progressBar.classList.add('bg-destructive'); installBtn.disabled = false; installBtn.innerHTML = ' Retry'; if (window.lucide) lucide.createIcons(); } } catch (err) { clearInterval(progressInterval); statusText.textContent = `Error: ${err.message}`; progressBar.classList.add('bg-destructive'); installBtn.disabled = false; installBtn.innerHTML = ' Retry'; if (window.lucide) lucide.createIcons(); } } function uninstallCodexLens() { openCodexLensUninstallWizard(); } function openCodexLensUninstallWizard() { const modal = document.createElement('div'); modal.id = 'codexlensUninstallModal'; modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50'; modal.innerHTML = `

Uninstall CodexLens

Remove CodexLens and all data

What will be removed:

  • Virtual environment at ~/.codexlens/venv
  • All CodexLens indexed data and databases
  • Configuration and semantic search models

Warning

This action cannot be undone. All indexed code data will be permanently deleted.

`; document.body.appendChild(modal); if (window.lucide) { lucide.createIcons(); } } function closeCodexLensUninstallWizard() { const modal = document.getElementById('codexlensUninstallModal'); if (modal) { modal.remove(); } } async function startCodexLensUninstall() { const progressDiv = document.getElementById('codexlensUninstallProgress'); const uninstallBtn = document.getElementById('codexlensUninstallBtn'); const statusText = document.getElementById('codexlensUninstallStatus'); const progressBar = document.getElementById('codexlensUninstallProgressBar'); // Show progress, disable button progressDiv.classList.remove('hidden'); uninstallBtn.disabled = true; uninstallBtn.innerHTML = 'Uninstalling...'; // Simulate progress stages const stages = [ { progress: 25, text: 'Removing virtual environment...' }, { progress: 50, text: 'Deleting indexed data...' }, { progress: 75, text: 'Cleaning up configuration...' }, { progress: 90, text: 'Finalizing removal...' } ]; let currentStage = 0; const progressInterval = setInterval(() => { if (currentStage < stages.length) { statusText.textContent = stages[currentStage].text; progressBar.style.width = `${stages[currentStage].progress}%`; currentStage++; } }, 500); try { const response = await fetch('/api/codexlens/uninstall', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) }); clearInterval(progressInterval); const result = await response.json(); if (result.success) { progressBar.style.width = '100%'; statusText.textContent = 'Uninstallation complete!'; setTimeout(() => { closeCodexLensUninstallWizard(); showRefreshToast('CodexLens uninstalled successfully!', 'success'); loadCodexLensStatus().then(() => renderCliStatus()); }, 1000); } else { statusText.textContent = `Error: ${result.error}`; progressBar.classList.remove('bg-destructive'); progressBar.classList.add('bg-destructive'); uninstallBtn.disabled = false; uninstallBtn.innerHTML = ' Retry'; if (window.lucide) lucide.createIcons(); } } catch (err) { clearInterval(progressInterval); statusText.textContent = `Error: ${err.message}`; progressBar.classList.remove('bg-destructive'); progressBar.classList.add('bg-destructive'); uninstallBtn.disabled = false; uninstallBtn.innerHTML = ' Retry'; if (window.lucide) lucide.createIcons(); } } async function initCodexLensIndex() { // Get current workspace path from multiple sources let targetPath = null; // Helper function to check if path is valid const isValidPath = (path) => { return path && typeof path === 'string' && path.length > 0 && (path.includes('/') || path.includes('\\')) && !path.startsWith('{{') && !path.endsWith('}}'); }; console.log('[CodexLens] Attempting to get project path...'); // Try 1: Global projectPath variable if (isValidPath(projectPath)) { targetPath = projectPath; console.log('[CodexLens] ✓ Using global projectPath:', targetPath); } // Try 2: Get from workflowData if (!targetPath && typeof workflowData !== 'undefined' && workflowData && isValidPath(workflowData.projectPath)) { targetPath = workflowData.projectPath; console.log('[CodexLens] ✓ Using workflowData.projectPath:', targetPath); } // Try 3: Get from current path display element if (!targetPath) { const currentPathEl = document.getElementById('currentPath'); if (currentPathEl && currentPathEl.textContent) { const pathText = currentPathEl.textContent.trim(); if (isValidPath(pathText)) { targetPath = pathText; console.log('[CodexLens] ✓ Using currentPath element text:', targetPath); } } } // Final validation if (!targetPath) { showRefreshToast('Error: No workspace loaded. Please open a workspace first.', 'error'); console.error('[CodexLens] No valid project path available'); console.error('[CodexLens] Attempted sources: projectPath:', projectPath, 'workflowData:', workflowData); return; } showRefreshToast('Initializing CodexLens index...', 'info'); console.log('[CodexLens] Initializing index for path:', targetPath); try { const response = await fetch('/api/codexlens/init', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: targetPath }) }); const result = await response.json(); console.log('[CodexLens] Init result:', result); if (result.success) { let data = null; // Try to parse nested JSON in output field if (result.output && typeof result.output === 'string') { try { // Extract JSON from output (it may contain other text before the JSON) const jsonMatch = result.output.match(/\{[\s\S]*\}/); if (jsonMatch) { const parsed = JSON.parse(jsonMatch[0]); data = parsed.result || parsed; console.log('[CodexLens] Parsed from output:', data); } } catch (e) { console.warn('[CodexLens] Failed to parse output as JSON:', e); } } // Fallback to direct result field if (!data) { data = result.result?.result || result.result || result; } const files = data.files_indexed || 0; const dirs = data.dirs_indexed || 0; const symbols = data.symbols_indexed || 0; console.log('[CodexLens] Parsed data:', { files, dirs, symbols }); if (files === 0 && dirs === 0) { showRefreshToast(`Warning: No files indexed. Path: ${targetPath}`, 'warning'); console.warn('[CodexLens] No files indexed. Full data:', data); } else { showRefreshToast(`Index created: ${files} files, ${dirs} directories`, 'success'); console.log('[CodexLens] Index created successfully'); } } else { showRefreshToast(`Init failed: ${result.error}`, 'error'); console.error('[CodexLens] Init error:', result.error); } } catch (err) { showRefreshToast(`Init error: ${err.message}`, 'error'); console.error('[CodexLens] Exception:', err); } } // ========== Semantic Search Installation Wizard ========== function openSemanticInstallWizard() { const modal = document.createElement('div'); modal.id = 'semanticInstallModal'; modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50'; modal.innerHTML = `

Install Semantic Search

AI-powered code understanding

What will be installed:

  • sentence-transformers - ML framework
  • bge-small-en-v1.5 - Embedding model (~130MB)
  • PyTorch - Deep learning backend (~300MB)

Large Download

Total size: ~500MB. First-time model loading may take a few minutes.

`; document.body.appendChild(modal); // Initialize Lucide icons in modal if (window.lucide) { lucide.createIcons(); } } function closeSemanticInstallWizard() { const modal = document.getElementById('semanticInstallModal'); if (modal) { modal.remove(); } } async function startSemanticInstall() { const progressDiv = document.getElementById('semanticInstallProgress'); const installBtn = document.getElementById('semanticInstallBtn'); const statusText = document.getElementById('semanticInstallStatus'); const progressBar = document.getElementById('semanticProgressBar'); // Show progress, disable button progressDiv.classList.remove('hidden'); installBtn.disabled = true; installBtn.innerHTML = 'Installing...'; // Simulate progress stages const stages = [ { progress: 10, text: 'Installing numpy...' }, { progress: 30, text: 'Installing sentence-transformers...' }, { progress: 50, text: 'Installing PyTorch dependencies...' }, { progress: 70, text: 'Downloading embedding model...' }, { progress: 90, text: 'Finalizing installation...' } ]; let currentStage = 0; const progressInterval = setInterval(() => { if (currentStage < stages.length) { statusText.textContent = stages[currentStage].text; progressBar.style.width = `${stages[currentStage].progress}%`; currentStage++; } }, 2000); try { const response = await fetch('/api/codexlens/semantic/install', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) }); clearInterval(progressInterval); const result = await response.json(); if (result.success) { progressBar.style.width = '100%'; statusText.textContent = 'Installation complete!'; setTimeout(() => { closeSemanticInstallWizard(); showRefreshToast('Semantic search installed successfully!', 'success'); loadSemanticStatus().then(() => renderCliStatus()); }, 1000); } else { statusText.textContent = `Error: ${result.error}`; progressBar.classList.add('bg-destructive'); installBtn.disabled = false; installBtn.innerHTML = ' Retry'; if (window.lucide) lucide.createIcons(); } } catch (err) { clearInterval(progressInterval); statusText.textContent = `Error: ${err.message}`; progressBar.classList.add('bg-destructive'); installBtn.disabled = false; installBtn.innerHTML = ' Retry'; if (window.lucide) lucide.createIcons(); } } // ========== Semantic Search Settings Modal ========== function openSemanticSettingsModal() { const availableTools = Object.entries(cliToolStatus) .filter(function(entry) { return entry[1].available; }) .map(function(entry) { return entry[0]; }); const modal = document.createElement('div'); modal.id = 'semanticSettingsModal'; modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50'; modal.onclick = function(e) { if (e.target === modal) closeSemanticSettingsModal(); }; const toolOptions = availableTools.map(function(tool) { return ''; }).join(''); const fallbackOptions = '' + availableTools.map(function(tool) { return ''; }).join(''); const disabled = !llmEnhancementSettings.enabled ? 'disabled' : ''; const opacityClass = !llmEnhancementSettings.enabled ? 'opacity-50' : ''; modal.innerHTML = '
' + '
' + '
' + '
' + '' + '
' + '
' + '

' + t('semantic.settings') + '

' + '

' + t('semantic.configDesc') + '

' + '
' + '
' + '
' + '
' + '
' + '

' + '' + t('semantic.llmEnhancement') + '

' + '

' + t('semantic.llmDesc') + '

' + '
' + '' + '
' + '
' + '
' + '
' + '' + '' + '
' + '
' + '' + '' + '
' + '
' + '
' + '
' + '' + '' + '
' + '
' + '' + '' + '
' + '
' + '
' + '
' + '
' + '' + '
' + '

' + t('semantic.enhanceInfo') + '

' + '

' + t('semantic.enhanceCommand') + ' codex-lens enhance ' + t('semantic.enhanceAfterEnable') + '

' + '
' + '
' + '
' + '
' + '' + '' + '
' + '
' + '
' + '

' + '' + t('semantic.testSearch') + '

' + '
' + '
' + '' + '
' + '
' + '' + '
' + '' + '
' + '
' + '
' + '
' + '
' + '' + '
' + '
'; document.body.appendChild(modal); // Add semantic search button handler setTimeout(function() { var runSemanticSearchBtn = document.getElementById('runSemanticSearchBtn'); if (runSemanticSearchBtn) { runSemanticSearchBtn.onclick = async function() { var query = document.getElementById('semanticSearchInput').value.trim(); var resultsDiv = document.getElementById('semanticSearchResults'); var resultCount = document.getElementById('semanticResultCount'); var resultContent = document.getElementById('semanticResultContent'); if (!query) { showRefreshToast(t('codexlens.enterQuery'), 'warning'); return; } runSemanticSearchBtn.disabled = true; runSemanticSearchBtn.innerHTML = '' + t('codexlens.searching') + ''; resultsDiv.classList.add('hidden'); try { var params = new URLSearchParams({ query: query, mode: 'semantic', limit: '10' }); var response = await fetch('/api/codexlens/search?' + params.toString()); var result = await response.json(); console.log('[Semantic Search Test] Result:', result); if (result.success) { var results = result.results || []; resultCount.textContent = results.length + ' ' + t('codexlens.resultsCount'); resultContent.textContent = JSON.stringify(results, null, 2); resultsDiv.classList.remove('hidden'); showRefreshToast(t('codexlens.searchCompleted') + ': ' + results.length + ' ' + t('codexlens.resultsCount'), 'success'); } else { resultContent.textContent = t('common.error') + ': ' + (result.error || t('common.unknownError')); resultsDiv.classList.remove('hidden'); showRefreshToast(t('codexlens.searchFailed') + ': ' + result.error, 'error'); } runSemanticSearchBtn.disabled = false; runSemanticSearchBtn.innerHTML = ' ' + t('semantic.runSearch'); if (window.lucide) lucide.createIcons(); } catch (err) { console.error('[Semantic Search Test] Error:', err); resultContent.textContent = t('common.exception') + ': ' + err.message; resultsDiv.classList.remove('hidden'); showRefreshToast(t('common.error') + ': ' + err.message, 'error'); runSemanticSearchBtn.disabled = false; runSemanticSearchBtn.innerHTML = ' ' + t('semantic.runSearch'); if (window.lucide) lucide.createIcons(); } }; } }, 100); var handleEscape = function(e) { if (e.key === 'Escape') { closeSemanticSettingsModal(); document.removeEventListener('keydown', handleEscape); } }; document.addEventListener('keydown', handleEscape); if (window.lucide) { lucide.createIcons(); } } function closeSemanticSettingsModal() { var modal = document.getElementById('semanticSettingsModal'); if (modal) modal.remove(); } function toggleLlmEnhancement(enabled) { llmEnhancementSettings.enabled = enabled; localStorage.setItem('ccw-llm-enhancement-enabled', enabled.toString()); var settingsSection = document.getElementById('llmSettingsSection'); if (settingsSection) { settingsSection.classList.toggle('opacity-50', !enabled); settingsSection.querySelectorAll('select').forEach(function(el) { el.disabled = !enabled; }); } renderCliStatus(); showRefreshToast(t('semantic.llmEnhancement') + ' ' + (enabled ? t('semantic.enabled') : t('semantic.disabled')), 'success'); } function updateLlmTool(tool) { llmEnhancementSettings.tool = tool; localStorage.setItem('ccw-llm-enhancement-tool', tool); showRefreshToast(t('semantic.toolSetTo') + ' ' + tool, 'success'); } function updateLlmFallback(tool) { llmEnhancementSettings.fallbackTool = tool; localStorage.setItem('ccw-llm-enhancement-fallback', tool); showRefreshToast(t('semantic.fallbackSetTo') + ' ' + (tool || t('semantic.none')), 'success'); } function updateLlmBatchSize(size) { llmEnhancementSettings.batchSize = parseInt(size, 10); localStorage.setItem('ccw-llm-enhancement-batch-size', size); showRefreshToast(t('semantic.batchSetTo') + ' ' + size + ' ' + t('semantic.files'), 'success'); } function updateLlmTimeout(ms) { llmEnhancementSettings.timeoutMs = parseInt(ms, 10); localStorage.setItem('ccw-llm-enhancement-timeout', ms); var mins = parseInt(ms, 10) / 60000; showRefreshToast(t('semantic.timeoutSetTo') + ' ' + mins + ' ' + (mins > 1 ? t('semantic.minutes') : t('semantic.minute')), 'success'); } async function runEnhanceCommand() { if (!llmEnhancementSettings.enabled) { showRefreshToast(t('semantic.enableFirst'), 'warning'); return; } showRefreshToast('Starting LLM enhancement...', 'info'); closeSemanticSettingsModal(); try { var response = await fetch('/api/codexlens/enhance', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: projectPath, tool: llmEnhancementSettings.tool, batchSize: llmEnhancementSettings.batchSize, timeoutMs: llmEnhancementSettings.timeoutMs }) }); var result = await response.json(); if (result.success) { var enhanced = result.result?.enhanced || 0; showRefreshToast('Enhanced ' + enhanced + ' files with LLM', 'success'); } else { showRefreshToast('Enhance failed: ' + result.error, 'error'); } } catch (err) { showRefreshToast('Enhance error: ' + err.message, 'error'); } } function viewEnhanceStatus() { openSemanticMetadataViewer(); } // ========== Semantic Metadata Viewer ========== var semanticMetadataCache = { entries: [], total: 0, offset: 0, limit: 50, loading: false }; async function openSemanticMetadataViewer() { closeSemanticSettingsModal(); var modal = document.createElement('div'); modal.id = 'semanticMetadataModal'; modal.className = 'generic-modal-overlay'; modal.onclick = function(e) { if (e.target === modal) closeSemanticMetadataViewer(); }; modal.innerHTML = '
' + '
' + '
' + '' + '

Semantic Metadata Browser

' + 'Loading...' + '
' + '' + '
' + '
' + '
' + '
' + '' + '' + '
' + '
' + '-' + '
' + '
' + '
' + '
' + '
' + 'Loading metadata...' + '
' + '
' + '' + '
' + '
'; document.body.appendChild(modal); requestAnimationFrame(function() { modal.classList.add('active'); }); var handleEscape = function(e) { if (e.key === 'Escape') { closeSemanticMetadataViewer(); document.removeEventListener('keydown', handleEscape); } }; document.addEventListener('keydown', handleEscape); if (window.lucide) { lucide.createIcons(); } await loadSemanticMetadata(); } function closeSemanticMetadataViewer() { var modal = document.getElementById('semanticMetadataModal'); if (modal) { modal.classList.remove('active'); setTimeout(function() { modal.remove(); }, 200); } } async function loadSemanticMetadata(offset, toolFilter) { offset = typeof offset === 'number' ? offset : semanticMetadataCache.offset; toolFilter = toolFilter !== undefined ? toolFilter : (document.getElementById('semanticToolFilter')?.value || ''); semanticMetadataCache.loading = true; var container = document.getElementById('semanticMetadataTableContainer'); if (container) { container.innerHTML = '
' + '
' + 'Loading metadata...' + '
'; } try { var url = '/api/codexlens/semantic/metadata?offset=' + offset + '&limit=' + semanticMetadataCache.limit; if (toolFilter) { url += '&tool=' + encodeURIComponent(toolFilter); } var response = await fetch(url); var data = await response.json(); if (data.success && data.result) { semanticMetadataCache.entries = data.result.entries || []; semanticMetadataCache.total = data.result.total || 0; semanticMetadataCache.offset = offset; renderSemanticMetadataTable(); updateSemanticPagination(); } else { container.innerHTML = '
' + '' + '

Error loading metadata: ' + (data.error || 'Unknown error') + '

' + '
'; if (window.lucide) lucide.createIcons(); } } catch (err) { container.innerHTML = '
' + '' + '

Error: ' + err.message + '

' + '
'; if (window.lucide) lucide.createIcons(); } semanticMetadataCache.loading = false; } function escapeHtmlSemantic(text) { if (!text) return ''; var div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function renderSemanticMetadataTable() { var container = document.getElementById('semanticMetadataTableContainer'); if (!container) return; var entries = semanticMetadataCache.entries; if (!entries.length) { container.innerHTML = '
' + '' + '

No semantic metadata found

' + '

Run \'codex-lens enhance\' to generate metadata for indexed files.

' + '' + '
'; if (window.lucide) lucide.createIcons(); return; } var rows = entries.map(function(entry, idx) { var keywordsHtml = (entry.keywords || []).slice(0, 4).map(function(k) { return '' + escapeHtmlSemantic(k) + ''; }).join(''); if ((entry.keywords || []).length > 4) { keywordsHtml += '+' + (entry.keywords.length - 4) + ''; } var date = entry.generated_at ? new Date(entry.generated_at * 1000).toLocaleDateString() : '-'; return ( '' + '' + '
' + '' + '' + escapeHtmlSemantic(entry.file_name || '-') + '' + '
' + '
' + escapeHtmlSemantic(entry.full_path || '-') + '
' + '' + '' + escapeHtmlSemantic(entry.language || '-') + '' + '' + escapeHtmlSemantic((entry.purpose || '-').substring(0, 50)) + ((entry.purpose || '').length > 50 ? '...' : '') + '' + '' + (keywordsHtml || '-') + '' + '' + '' + escapeHtmlSemantic(entry.llm_tool || '-') + '' + '' + '' + date + '' + '' + '' + '' + '
' + '
' + '

Summary

' + '

' + escapeHtmlSemantic(entry.summary || 'No summary available') + '

' + '
' + '
' + '

All Keywords

' + '
' + (entry.keywords || []).map(function(k) { return '' + escapeHtmlSemantic(k) + ''; }).join('') + '
' + '
' + '
' + ' ' + (entry.line_count || 0) + ' lines' + ' ' + escapeHtmlSemantic(entry.llm_tool || 'Unknown') + '' + ' ' + date + '' + '
' + '
' + '' + '' ); }).join(''); container.innerHTML = '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + rows + '' + '
FileLanguagePurposeKeywordsToolDate
'; if (window.lucide) lucide.createIcons(); } function toggleSemanticDetail(idx) { var detailRow = document.getElementById('semanticDetail' + idx); if (detailRow) { detailRow.classList.toggle('hidden'); if (window.lucide) lucide.createIcons(); } } function updateSemanticPagination() { var total = semanticMetadataCache.total; var offset = semanticMetadataCache.offset; var limit = semanticMetadataCache.limit; var entries = semanticMetadataCache.entries; var countBadge = document.getElementById('semanticMetadataCount'); if (countBadge) { countBadge.textContent = total + ' entries'; } var paginationInfo = document.getElementById('semanticPaginationInfo'); if (paginationInfo) { if (total > 0) { paginationInfo.textContent = (offset + 1) + '-' + (offset + entries.length) + ' of ' + total; } else { paginationInfo.textContent = 'No entries'; } } var pageSelect = document.getElementById('semanticPageSelect'); if (pageSelect) { var totalPages = Math.ceil(total / limit) || 1; var currentPage = Math.floor(offset / limit); pageSelect.innerHTML = ''; for (var i = 0; i < totalPages; i++) { var opt = document.createElement('option'); opt.value = i; opt.textContent = i + 1; if (i === currentPage) opt.selected = true; pageSelect.appendChild(opt); } } var prevBtn = document.getElementById('semanticPrevBtn'); var nextBtn = document.getElementById('semanticNextBtn'); if (prevBtn) prevBtn.disabled = offset === 0; if (nextBtn) nextBtn.disabled = offset + limit >= total; } function semanticPrevPage() { if (semanticMetadataCache.offset > 0) { loadSemanticMetadata(Math.max(0, semanticMetadataCache.offset - semanticMetadataCache.limit)); } } function semanticNextPage() { if (semanticMetadataCache.offset + semanticMetadataCache.limit < semanticMetadataCache.total) { loadSemanticMetadata(semanticMetadataCache.offset + semanticMetadataCache.limit); } } function semanticGoToPage(pageIndex) { var offset = parseInt(pageIndex, 10) * semanticMetadataCache.limit; loadSemanticMetadata(offset); } function filterSemanticByTool(tool) { loadSemanticMetadata(0, tool); } function refreshSemanticMetadata() { loadSemanticMetadata(semanticMetadataCache.offset); } function getLlmEnhancementSettings() { return Object.assign({}, llmEnhancementSettings); }