mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-14 02:42:04 +08:00
feat: 优化工作区索引状态刷新,增强头部徽章更新逻辑
This commit is contained in:
@@ -24,106 +24,138 @@ function escapeHtml(str) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh workspace index status (FTS and Vector coverage)
|
* Refresh workspace index status (FTS and Vector coverage)
|
||||||
|
* Updates both the detailed panel (if exists) and header badges
|
||||||
*/
|
*/
|
||||||
async function refreshWorkspaceIndexStatus() {
|
async function refreshWorkspaceIndexStatus() {
|
||||||
var container = document.getElementById('workspaceIndexStatusContent');
|
var container = document.getElementById('workspaceIndexStatusContent');
|
||||||
if (!container) return;
|
var headerFtsEl = document.getElementById('headerFtsPercent');
|
||||||
|
var headerVectorEl = document.getElementById('headerVectorPercent');
|
||||||
|
|
||||||
// Show loading state
|
// If neither container nor header elements exist, nothing to update
|
||||||
container.innerHTML = '<div class="text-xs text-muted-foreground text-center py-2">' +
|
if (!container && !headerFtsEl) return;
|
||||||
'<i data-lucide="loader-2" class="w-4 h-4 animate-spin inline mr-1"></i> ' + (t('common.loading') || 'Loading...') +
|
|
||||||
'</div>';
|
// Show loading state in container
|
||||||
if (window.lucide) lucide.createIcons();
|
if (container) {
|
||||||
|
container.innerHTML = '<div class="text-xs text-muted-foreground text-center py-2">' +
|
||||||
|
'<i data-lucide="loader-2" class="w-4 h-4 animate-spin inline mr-1"></i> ' + (t('common.loading') || 'Loading...') +
|
||||||
|
'</div>';
|
||||||
|
if (window.lucide) lucide.createIcons();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var response = await fetch('/api/codexlens/workspace-status');
|
var response = await fetch('/api/codexlens/workspace-status');
|
||||||
var result = await response.json();
|
var result = await response.json();
|
||||||
|
|
||||||
if (result.success) {
|
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) {
|
// Update header badges (always update if elements exist)
|
||||||
// No index for current workspace
|
if (headerFtsEl) {
|
||||||
html = '<div class="text-center py-3">' +
|
headerFtsEl.textContent = ftsPercent + '%';
|
||||||
'<div class="text-sm text-muted-foreground mb-2">' +
|
headerFtsEl.className = 'text-sm font-medium ' +
|
||||||
'<i data-lucide="alert-circle" class="w-4 h-4 inline mr-1"></i> ' +
|
(ftsPercent >= 100 ? 'text-success' : (ftsPercent > 0 ? 'text-blue-500' : 'text-muted-foreground'));
|
||||||
(t('codexlens.noIndexFound') || 'No index found for current workspace') +
|
}
|
||||||
'</div>' +
|
if (headerVectorEl) {
|
||||||
'<button onclick="runFtsFullIndex()" class="text-xs text-primary hover:underline">' +
|
headerVectorEl.textContent = vectorPercent.toFixed(1) + '%';
|
||||||
(t('codexlens.createIndex') || 'Create Index') +
|
headerVectorEl.className = 'text-sm font-medium ' +
|
||||||
'</button>' +
|
(vectorPercent >= 100 ? 'text-success' : (vectorPercent >= 50 ? 'text-purple-500' : (vectorPercent > 0 ? 'text-purple-400' : 'text-muted-foreground')));
|
||||||
'</div>';
|
|
||||||
} 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 += '<div class="space-y-1">' +
|
|
||||||
'<div class="flex items-center justify-between text-xs">' +
|
|
||||||
'<span class="flex items-center gap-1.5">' +
|
|
||||||
'<i data-lucide="file-text" class="w-3.5 h-3.5 text-blue-500"></i> ' +
|
|
||||||
'<span class="font-medium">' + (t('codexlens.ftsIndex') || 'FTS Index') + '</span>' +
|
|
||||||
'</span>' +
|
|
||||||
'<span class="' + ftsTextColor + ' font-medium">' + ftsPercent + '%</span>' +
|
|
||||||
'</div>' +
|
|
||||||
'<div class="h-1.5 bg-muted rounded-full overflow-hidden">' +
|
|
||||||
'<div class="h-full ' + ftsColor + ' transition-all duration-300" style="width: ' + ftsPercent + '%"></div>' +
|
|
||||||
'</div>' +
|
|
||||||
'<div class="text-xs text-muted-foreground">' +
|
|
||||||
(result.fts.indexedFiles || 0) + ' / ' + (result.fts.totalFiles || 0) + ' ' + (t('codexlens.filesIndexed') || 'files indexed') +
|
|
||||||
'</div>' +
|
|
||||||
'</div>';
|
|
||||||
|
|
||||||
// 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 += '<div class="space-y-1 mt-3">' +
|
|
||||||
'<div class="flex items-center justify-between text-xs">' +
|
|
||||||
'<span class="flex items-center gap-1.5">' +
|
|
||||||
'<i data-lucide="brain" class="w-3.5 h-3.5 text-purple-500"></i> ' +
|
|
||||||
'<span class="font-medium">' + (t('codexlens.vectorIndex') || 'Vector Index') + '</span>' +
|
|
||||||
'</span>' +
|
|
||||||
'<span class="' + vectorTextColor + ' font-medium">' + vectorPercent.toFixed(1) + '%</span>' +
|
|
||||||
'</div>' +
|
|
||||||
'<div class="h-1.5 bg-muted rounded-full overflow-hidden">' +
|
|
||||||
'<div class="h-full ' + vectorColor + ' transition-all duration-300" style="width: ' + vectorPercent + '%"></div>' +
|
|
||||||
'</div>' +
|
|
||||||
'<div class="text-xs text-muted-foreground">' +
|
|
||||||
(result.vector.filesWithEmbeddings || 0) + ' / ' + (result.vector.totalFiles || 0) + ' ' + (t('codexlens.filesWithEmbeddings') || 'files with embeddings') +
|
|
||||||
(result.vector.totalChunks > 0 ? ' (' + result.vector.totalChunks + ' chunks)' : '') +
|
|
||||||
'</div>' +
|
|
||||||
'</div>';
|
|
||||||
|
|
||||||
// Vector search availability indicator
|
|
||||||
if (vectorPercent >= 50) {
|
|
||||||
html += '<div class="flex items-center gap-1.5 mt-2 pt-2 border-t border-border">' +
|
|
||||||
'<i data-lucide="check-circle-2" class="w-3.5 h-3.5 text-success"></i>' +
|
|
||||||
'<span class="text-xs text-success">' + (t('codexlens.vectorSearchEnabled') || 'Vector search enabled') + '</span>' +
|
|
||||||
'</div>';
|
|
||||||
} else if (vectorPercent > 0) {
|
|
||||||
html += '<div class="flex items-center gap-1.5 mt-2 pt-2 border-t border-border">' +
|
|
||||||
'<i data-lucide="alert-triangle" class="w-3.5 h-3.5 text-warning"></i>' +
|
|
||||||
'<span class="text-xs text-warning">' + (t('codexlens.vectorSearchPartial') || 'Vector search requires ≥50% coverage') + '</span>' +
|
|
||||||
'</div>';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
container.innerHTML = html;
|
// Update detailed container (if exists)
|
||||||
|
if (container) {
|
||||||
|
var html = '';
|
||||||
|
|
||||||
|
if (!result.hasIndex) {
|
||||||
|
// No index for current workspace
|
||||||
|
html = '<div class="text-center py-3">' +
|
||||||
|
'<div class="text-sm text-muted-foreground mb-2">' +
|
||||||
|
'<i data-lucide="alert-circle" class="w-4 h-4 inline mr-1"></i> ' +
|
||||||
|
(t('codexlens.noIndexFound') || 'No index found for current workspace') +
|
||||||
|
'</div>' +
|
||||||
|
'<button onclick="runFtsFullIndex()" class="text-xs text-primary hover:underline">' +
|
||||||
|
(t('codexlens.createIndex') || 'Create Index') +
|
||||||
|
'</button>' +
|
||||||
|
'</div>';
|
||||||
|
} 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 += '<div class="space-y-1">' +
|
||||||
|
'<div class="flex items-center justify-between text-xs">' +
|
||||||
|
'<span class="flex items-center gap-1.5">' +
|
||||||
|
'<i data-lucide="file-text" class="w-3.5 h-3.5 text-blue-500"></i> ' +
|
||||||
|
'<span class="font-medium">' + (t('codexlens.ftsIndex') || 'FTS Index') + '</span>' +
|
||||||
|
'</span>' +
|
||||||
|
'<span class="' + ftsTextColor + ' font-medium">' + ftsPercent + '%</span>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="h-1.5 bg-muted rounded-full overflow-hidden">' +
|
||||||
|
'<div class="h-full ' + ftsColor + ' transition-all duration-300" style="width: ' + ftsPercent + '%"></div>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="text-xs text-muted-foreground">' +
|
||||||
|
(result.fts.indexedFiles || 0) + ' / ' + (result.fts.totalFiles || 0) + ' ' + (t('codexlens.filesIndexed') || 'files indexed') +
|
||||||
|
'</div>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
// 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 += '<div class="space-y-1 mt-3">' +
|
||||||
|
'<div class="flex items-center justify-between text-xs">' +
|
||||||
|
'<span class="flex items-center gap-1.5">' +
|
||||||
|
'<i data-lucide="brain" class="w-3.5 h-3.5 text-purple-500"></i> ' +
|
||||||
|
'<span class="font-medium">' + (t('codexlens.vectorIndex') || 'Vector Index') + '</span>' +
|
||||||
|
'</span>' +
|
||||||
|
'<span class="' + vectorTextColor + ' font-medium">' + vectorPercent.toFixed(1) + '%</span>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="h-1.5 bg-muted rounded-full overflow-hidden">' +
|
||||||
|
'<div class="h-full ' + vectorColor + ' transition-all duration-300" style="width: ' + vectorPercent + '%"></div>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="text-xs text-muted-foreground">' +
|
||||||
|
(result.vector.filesWithEmbeddings || 0) + ' / ' + (result.vector.totalFiles || 0) + ' ' + (t('codexlens.filesWithEmbeddings') || 'files with embeddings') +
|
||||||
|
(result.vector.totalChunks > 0 ? ' (' + result.vector.totalChunks + ' chunks)' : '') +
|
||||||
|
'</div>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
// Vector search availability indicator
|
||||||
|
if (vectorPercent >= 50) {
|
||||||
|
html += '<div class="flex items-center gap-1.5 mt-2 pt-2 border-t border-border">' +
|
||||||
|
'<i data-lucide="check-circle-2" class="w-3.5 h-3.5 text-success"></i>' +
|
||||||
|
'<span class="text-xs text-success">' + (t('codexlens.vectorSearchEnabled') || 'Vector search enabled') + '</span>' +
|
||||||
|
'</div>';
|
||||||
|
} else if (vectorPercent > 0) {
|
||||||
|
html += '<div class="flex items-center gap-1.5 mt-2 pt-2 border-t border-border">' +
|
||||||
|
'<i data-lucide="alert-triangle" class="w-3.5 h-3.5 text-warning"></i>' +
|
||||||
|
'<span class="text-xs text-warning">' + (t('codexlens.vectorSearchPartial') || 'Vector search requires ≥50% coverage') + '</span>' +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = html;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
container.innerHTML = '<div class="text-xs text-destructive text-center py-2">' +
|
// Error from API
|
||||||
'<i data-lucide="alert-circle" class="w-4 h-4 inline mr-1"></i> ' +
|
if (headerFtsEl) headerFtsEl.textContent = '--';
|
||||||
(result.error || t('common.error') || 'Error loading status') +
|
if (headerVectorEl) headerVectorEl.textContent = '--';
|
||||||
'</div>';
|
if (container) {
|
||||||
|
container.innerHTML = '<div class="text-xs text-destructive text-center py-2">' +
|
||||||
|
'<i data-lucide="alert-circle" class="w-4 h-4 inline mr-1"></i> ' +
|
||||||
|
(result.error || t('common.error') || 'Error loading status') +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[CodexLens] Failed to load workspace status:', err);
|
console.error('[CodexLens] Failed to load workspace status:', err);
|
||||||
container.innerHTML = '<div class="text-xs text-destructive text-center py-2">' +
|
if (headerFtsEl) headerFtsEl.textContent = '--';
|
||||||
'<i data-lucide="alert-circle" class="w-4 h-4 inline mr-1"></i> ' +
|
if (headerVectorEl) headerVectorEl.textContent = '--';
|
||||||
(t('common.error') || 'Error') + ': ' + err.message +
|
if (container) {
|
||||||
'</div>';
|
container.innerHTML = '<div class="text-xs text-destructive text-center py-2">' +
|
||||||
|
'<i data-lucide="alert-circle" class="w-4 h-4 inline mr-1"></i> ' +
|
||||||
|
(t('common.error') || 'Error') + ': ' + err.message +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.lucide) lucide.createIcons();
|
if (window.lucide) lucide.createIcons();
|
||||||
@@ -3884,32 +3916,45 @@ async function renderCodexLensManager() {
|
|||||||
// Load additional data in parallel (non-blocking)
|
// Load additional data in parallel (non-blocking)
|
||||||
var isInstalled = window.cliToolsStatus?.codexlens?.installed || dashboardData?.installed;
|
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)
|
// Wait for LiteLLM config before loading semantic deps (it may need provider info)
|
||||||
await litellmPromise;
|
await litellmPromise;
|
||||||
|
|
||||||
// Load FastEmbed installation status (show/hide install card)
|
// OPTIMIZATION: Batch non-critical loads with requestIdleCallback or setTimeout
|
||||||
loadFastEmbedInstallStatus();
|
// 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
|
// Load index stats if installed (independent from above)
|
||||||
// which are not included in the aggregated endpoint
|
if (isInstalled) {
|
||||||
loadSemanticDepsStatus();
|
loadIndexStatsForPage();
|
||||||
|
checkIndexHealth();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
loadModelList();
|
// Use requestIdleCallback for deferred loads if available, otherwise use setTimeout
|
||||||
loadRerankerModelList();
|
if (typeof requestIdleCallback === 'function') {
|
||||||
|
requestIdleCallback(deferredLoads, { timeout: 500 });
|
||||||
// Initialize model mode and semantic status badge
|
} else {
|
||||||
updateSemanticStatusBadge();
|
setTimeout(deferredLoads, 50);
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
container.innerHTML = '<div class="text-center py-12 text-destructive"><i data-lucide="alert-circle" class="w-8 h-8 mx-auto mb-2"></i><p>' + t('common.error') + ': ' + escapeHtml(err.message) + '</p></div>';
|
container.innerHTML = '<div class="text-center py-12 text-destructive"><i data-lucide="alert-circle" class="w-8 h-8 mx-auto mb-2"></i><p>' + t('common.error') + ': ' + escapeHtml(err.message) + '</p></div>';
|
||||||
@@ -3946,6 +3991,21 @@ function buildCodexLensManagerPage(config) {
|
|||||||
'<span class="text-sm text-muted-foreground">' + t('codexlens.indexes') + ':</span>' +
|
'<span class="text-sm text-muted-foreground">' + t('codexlens.indexes') + ':</span>' +
|
||||||
'<span class="text-lg font-bold text-primary">' + indexCount + '</span>' +
|
'<span class="text-lg font-bold text-primary">' + indexCount + '</span>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
|
// Workspace Index Status badges (FTS/Vector percentages)
|
||||||
|
(isInstalled
|
||||||
|
? '<div id="headerIndexStatusContainer" class="flex items-center gap-2">' +
|
||||||
|
'<div class="flex items-center gap-1.5 px-2.5 py-1 rounded-lg bg-blue-500/10 border border-blue-500/20" title="' + (t('codexlens.ftsIndex') || 'FTS Index') + '">' +
|
||||||
|
'<i data-lucide="file-text" class="w-3.5 h-3.5 text-blue-500"></i>' +
|
||||||
|
'<span class="text-xs text-muted-foreground">FTS:</span>' +
|
||||||
|
'<span id="headerFtsPercent" class="text-sm font-medium text-blue-500">--</span>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="flex items-center gap-1.5 px-2.5 py-1 rounded-lg bg-purple-500/10 border border-purple-500/20" title="' + (t('codexlens.vectorIndex') || 'Vector Index') + '">' +
|
||||||
|
'<i data-lucide="brain" class="w-3.5 h-3.5 text-purple-500"></i>' +
|
||||||
|
'<span class="text-xs text-muted-foreground">Vector:</span>' +
|
||||||
|
'<span id="headerVectorPercent" class="text-sm font-medium text-purple-500">--</span>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>'
|
||||||
|
: '') +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
|
|||||||
Reference in New Issue
Block a user