From 6e93c36b89c44d3972dd8510226a1f6688b881fb Mon Sep 17 00:00:00 2001 From: catlog22 Date: Wed, 7 Jan 2026 22:28:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E5=8C=BA=E7=B4=A2=E5=BC=95=E7=8A=B6=E6=80=81=E5=88=B7=E6=96=B0?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=BC=BA=E5=A4=B4=E9=83=A8=E5=BE=BD=E7=AB=A0?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard-js/views/codexlens-manager.js | 268 +++++++++++------- 1 file changed, 164 insertions(+), 104 deletions(-) diff --git a/ccw/src/templates/dashboard-js/views/codexlens-manager.js b/ccw/src/templates/dashboard-js/views/codexlens-manager.js index 23121d4e..5ab25410 100644 --- a/ccw/src/templates/dashboard-js/views/codexlens-manager.js +++ b/ccw/src/templates/dashboard-js/views/codexlens-manager.js @@ -24,106 +24,138 @@ function escapeHtml(str) { /** * Refresh workspace index status (FTS and Vector coverage) + * Updates both the detailed panel (if exists) and header badges */ async function refreshWorkspaceIndexStatus() { var container = document.getElementById('workspaceIndexStatusContent'); - if (!container) return; + var headerFtsEl = document.getElementById('headerFtsPercent'); + var headerVectorEl = document.getElementById('headerVectorPercent'); - // Show loading state - container.innerHTML = '
' + - ' ' + (t('common.loading') || 'Loading...') + - '
'; - if (window.lucide) lucide.createIcons(); + // If neither container nor header elements exist, nothing to update + if (!container && !headerFtsEl) return; + + // Show loading state in container + if (container) { + container.innerHTML = '
' + + ' ' + (t('common.loading') || 'Loading...') + + '
'; + if (window.lucide) lucide.createIcons(); + } try { var response = await fetch('/api/codexlens/workspace-status'); var result = await response.json(); if (result.success) { - var html = ''; + var ftsPercent = result.hasIndex ? (result.fts.percent || 0) : 0; + var vectorPercent = result.hasIndex ? (result.vector.percent || 0) : 0; - if (!result.hasIndex) { - // No index for current workspace - html = '
' + - '
' + - ' ' + - (t('codexlens.noIndexFound') || 'No index found for current workspace') + - '
' + - '' + - '
'; - } else { - // FTS Status - var ftsPercent = result.fts.percent || 0; - var ftsColor = ftsPercent >= 100 ? 'bg-success' : (ftsPercent > 0 ? 'bg-blue-500' : 'bg-muted-foreground'); - var ftsTextColor = ftsPercent >= 100 ? 'text-success' : (ftsPercent > 0 ? 'text-blue-500' : 'text-muted-foreground'); - - html += '
' + - '
' + - '' + - ' ' + - '' + (t('codexlens.ftsIndex') || 'FTS Index') + '' + - '' + - '' + ftsPercent + '%' + - '
' + - '
' + - '
' + - '
' + - '
' + - (result.fts.indexedFiles || 0) + ' / ' + (result.fts.totalFiles || 0) + ' ' + (t('codexlens.filesIndexed') || 'files indexed') + - '
' + - '
'; - - // Vector Status - var vectorPercent = result.vector.percent || 0; - var vectorColor = vectorPercent >= 100 ? 'bg-success' : (vectorPercent >= 50 ? 'bg-purple-500' : (vectorPercent > 0 ? 'bg-purple-400' : 'bg-muted-foreground')); - var vectorTextColor = vectorPercent >= 100 ? 'text-success' : (vectorPercent >= 50 ? 'text-purple-500' : (vectorPercent > 0 ? 'text-purple-400' : 'text-muted-foreground')); - - html += '
' + - '
' + - '' + - ' ' + - '' + (t('codexlens.vectorIndex') || 'Vector Index') + '' + - '' + - '' + vectorPercent.toFixed(1) + '%' + - '
' + - '
' + - '
' + - '
' + - '
' + - (result.vector.filesWithEmbeddings || 0) + ' / ' + (result.vector.totalFiles || 0) + ' ' + (t('codexlens.filesWithEmbeddings') || 'files with embeddings') + - (result.vector.totalChunks > 0 ? ' (' + result.vector.totalChunks + ' chunks)' : '') + - '
' + - '
'; - - // Vector search availability indicator - if (vectorPercent >= 50) { - html += '
' + - '' + - '' + (t('codexlens.vectorSearchEnabled') || 'Vector search enabled') + '' + - '
'; - } else if (vectorPercent > 0) { - html += '
' + - '' + - '' + (t('codexlens.vectorSearchPartial') || 'Vector search requires ≥50% coverage') + '' + - '
'; - } + // Update header badges (always update if elements exist) + if (headerFtsEl) { + headerFtsEl.textContent = ftsPercent + '%'; + headerFtsEl.className = 'text-sm font-medium ' + + (ftsPercent >= 100 ? 'text-success' : (ftsPercent > 0 ? 'text-blue-500' : 'text-muted-foreground')); + } + if (headerVectorEl) { + headerVectorEl.textContent = vectorPercent.toFixed(1) + '%'; + headerVectorEl.className = 'text-sm font-medium ' + + (vectorPercent >= 100 ? 'text-success' : (vectorPercent >= 50 ? 'text-purple-500' : (vectorPercent > 0 ? 'text-purple-400' : 'text-muted-foreground'))); } - container.innerHTML = html; + // Update detailed container (if exists) + if (container) { + var html = ''; + + if (!result.hasIndex) { + // No index for current workspace + html = '
' + + '
' + + ' ' + + (t('codexlens.noIndexFound') || 'No index found for current workspace') + + '
' + + '' + + '
'; + } else { + // FTS Status + var ftsColor = ftsPercent >= 100 ? 'bg-success' : (ftsPercent > 0 ? 'bg-blue-500' : 'bg-muted-foreground'); + var ftsTextColor = ftsPercent >= 100 ? 'text-success' : (ftsPercent > 0 ? 'text-blue-500' : 'text-muted-foreground'); + + html += '
' + + '
' + + '' + + ' ' + + '' + (t('codexlens.ftsIndex') || 'FTS Index') + '' + + '' + + '' + ftsPercent + '%' + + '
' + + '
' + + '
' + + '
' + + '
' + + (result.fts.indexedFiles || 0) + ' / ' + (result.fts.totalFiles || 0) + ' ' + (t('codexlens.filesIndexed') || 'files indexed') + + '
' + + '
'; + + // Vector Status + var vectorColor = vectorPercent >= 100 ? 'bg-success' : (vectorPercent >= 50 ? 'bg-purple-500' : (vectorPercent > 0 ? 'bg-purple-400' : 'bg-muted-foreground')); + var vectorTextColor = vectorPercent >= 100 ? 'text-success' : (vectorPercent >= 50 ? 'text-purple-500' : (vectorPercent > 0 ? 'text-purple-400' : 'text-muted-foreground')); + + html += '
' + + '
' + + '' + + ' ' + + '' + (t('codexlens.vectorIndex') || 'Vector Index') + '' + + '' + + '' + vectorPercent.toFixed(1) + '%' + + '
' + + '
' + + '
' + + '
' + + '
' + + (result.vector.filesWithEmbeddings || 0) + ' / ' + (result.vector.totalFiles || 0) + ' ' + (t('codexlens.filesWithEmbeddings') || 'files with embeddings') + + (result.vector.totalChunks > 0 ? ' (' + result.vector.totalChunks + ' chunks)' : '') + + '
' + + '
'; + + // Vector search availability indicator + if (vectorPercent >= 50) { + html += '
' + + '' + + '' + (t('codexlens.vectorSearchEnabled') || 'Vector search enabled') + '' + + '
'; + } else if (vectorPercent > 0) { + html += '
' + + '' + + '' + (t('codexlens.vectorSearchPartial') || 'Vector search requires ≥50% coverage') + '' + + '
'; + } + } + + container.innerHTML = html; + } } else { - container.innerHTML = '
' + - ' ' + - (result.error || t('common.error') || 'Error loading status') + - '
'; + // Error from API + if (headerFtsEl) headerFtsEl.textContent = '--'; + if (headerVectorEl) headerVectorEl.textContent = '--'; + if (container) { + container.innerHTML = '
' + + ' ' + + (result.error || t('common.error') || 'Error loading status') + + '
'; + } } } catch (err) { console.error('[CodexLens] Failed to load workspace status:', err); - container.innerHTML = '
' + - ' ' + - (t('common.error') || 'Error') + ': ' + err.message + - '
'; + if (headerFtsEl) headerFtsEl.textContent = '--'; + if (headerVectorEl) headerVectorEl.textContent = '--'; + if (container) { + container.innerHTML = '
' + + ' ' + + (t('common.error') || 'Error') + ': ' + err.message + + '
'; + } } if (window.lucide) lucide.createIcons(); @@ -3884,32 +3916,45 @@ async function renderCodexLensManager() { // Load additional data in parallel (non-blocking) var isInstalled = window.cliToolsStatus?.codexlens?.installed || dashboardData?.installed; + // OPTIMIZATION: Load critical status first (workspace index status for header badges) + // This is prioritized as it updates the visible header immediately + if (isInstalled) { + refreshWorkspaceIndexStatus(); // Updates header FTS/Vector badges + } + // Wait for LiteLLM config before loading semantic deps (it may need provider info) await litellmPromise; - // Load FastEmbed installation status (show/hide install card) - loadFastEmbedInstallStatus(); + // OPTIMIZATION: Batch non-critical loads with requestIdleCallback or setTimeout + // This prevents blocking the main thread and allows smoother UI updates + var deferredLoads = function() { + // Load all independent status checks in parallel + Promise.all([ + // FastEmbed and semantic deps status (independent) + Promise.resolve().then(function() { loadFastEmbedInstallStatus(); }), + Promise.resolve().then(function() { loadSemanticDepsStatus(); }), + // Model lists (independent) + Promise.resolve().then(function() { loadModelList(); }), + Promise.resolve().then(function() { loadRerankerModelList(); }), + // File watcher status (independent) + Promise.resolve().then(function() { initWatcherStatus(); }) + ]).then(function() { + // After all loads complete, update the semantic status badge + updateSemanticStatusBadge(); + }); - // Always load semantic deps status - it needs GPU detection and device list - // which are not included in the aggregated endpoint - loadSemanticDepsStatus(); + // Load index stats if installed (independent from above) + if (isInstalled) { + loadIndexStatsForPage(); + checkIndexHealth(); + } + }; - loadModelList(); - loadRerankerModelList(); - - // Initialize model mode and semantic status badge - updateSemanticStatusBadge(); - - // Initialize file watcher status - initWatcherStatus(); - - // Load index stats for the Index Manager section - if (isInstalled) { - loadIndexStatsForPage(); - // Check index health based on git history - checkIndexHealth(); - // Load workspace index status (FTS and Vector coverage) - refreshWorkspaceIndexStatus(); + // Use requestIdleCallback for deferred loads if available, otherwise use setTimeout + if (typeof requestIdleCallback === 'function') { + requestIdleCallback(deferredLoads, { timeout: 500 }); + } else { + setTimeout(deferredLoads, 50); } } catch (err) { container.innerHTML = '

' + t('common.error') + ': ' + escapeHtml(err.message) + '

'; @@ -3946,6 +3991,21 @@ function buildCodexLensManagerPage(config) { '' + t('codexlens.indexes') + ':' + '' + indexCount + '' + '' + + // Workspace Index Status badges (FTS/Vector percentages) + (isInstalled + ? '
' + + '
' + + '' + + 'FTS:' + + '--' + + '
' + + '
' + + '' + + 'Vector:' + + '--' + + '
' + + '
' + : '') + '' + '' + '' +