// 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 `
`;
}).join('');
// CodexLens card with semantic search info
const codexLensHtml = `
`;
// Semantic Search card (only show if CodexLens is installed)
const llmStatusBadge = llmEnhancementSettings.enabled
? `LLM `
: '';
const semanticHtml = codexLensStatus.ready ? `
` : '';
// CLI Settings section
const settingsHtml = `
Prompt Format
Plain Text
YAML
JSON
Format for multi-turn conversation concatenation
Storage Backend
SQLite
CLI history stored in SQLite with FTS search
Max Context Files
5 files
10 files
20 files
Maximum files to include in smart context
`;
container.innerHTML = `
${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.
Cancel
Install Now
`;
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.
Cancel
Uninstall
`;
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.
Installing dependencies...
Cancel
Install Now
`;
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 '' +
tool.charAt(0).toUpperCase() + tool.slice(1) + ' ';
}).join('');
const fallbackOptions = '' + t('semantic.none') + ' ' + availableTools.map(function(tool) {
return '' +
tool.charAt(0).toUpperCase() + tool.slice(1) + ' ';
}).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.primaryTool') + ' ' +
'' + toolOptions + ' ' +
'
' +
'
' +
'' +
' ' + t('semantic.fallbackTool') + ' ' +
'' + fallbackOptions + ' ' +
'
' +
'
' +
'
' +
'
' +
'' +
' ' + t('semantic.batchSize') + ' ' +
'' +
'1 ' + t('semantic.file') + ' ' +
'3 ' + t('semantic.files') + ' ' +
'5 ' + t('semantic.files') + ' ' +
'10 ' + t('semantic.files') + ' ' +
' ' +
'
' +
'
' +
'' +
' ' + t('semantic.timeout') + ' ' +
'' +
'1 min ' +
'3 min ' +
'5 min ' +
'10 min ' +
' ' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' + t('semantic.enhanceInfo') + '
' +
'
' + t('semantic.enhanceCommand') + ' codex-lens enhance ' + t('semantic.enhanceAfterEnable') + '
' +
'
' +
'
' +
'
' +
'
' +
'' +
' ' + t('semantic.runEnhanceNow') + ' ' +
'' +
' ' + t('semantic.viewStatus') + ' ' +
'
' +
'
' +
'
' +
'
' +
' ' + t('semantic.testSearch') + ' ' +
'
' +
'
' +
' ' +
'
' +
'
' +
'' +
' ' + t('semantic.runSearch') +
' ' +
'
' +
'
' +
'
' +
'
' +
'
' + t('codexlens.results') + ':
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'' + t('semantic.close') + ' ' +
'
' +
'
';
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 =
'';
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.
' +
'
' +
' Run Enhance' +
' ' +
'
';
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 =
'' +
'' +
'' +
'File ' +
'Language ' +
'Purpose ' +
'Keywords ' +
'Tool ' +
'Date ' +
' ' +
' ' +
'' + rows + ' ' +
'
';
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);
}