// CLI Manager View
// Main view combining CLI status and CCW installations panels (two-column layout)
// ========== CLI Manager State ==========
var currentCliExecution = null;
var cliExecutionOutput = '';
var ccwInstallations = [];
var ccwEndpointTools = [];
var cliToolConfig = null; // Store loaded CLI config
var predefinedModels = {}; // Store predefined models per tool
// ========== CSRF Token Management ==========
var csrfToken = null; // Store CSRF token for state-changing requests
/**
* Fetch wrapper that handles CSRF token management
* Captures new token from response and includes token in requests
*/
async function csrfFetch(url, options) {
options = options || {};
options.headers = options.headers || {};
// Add CSRF token header for state-changing methods
var method = (options.method || 'GET').toUpperCase();
// Auto-initialize CSRF token for state-changing requests
if (['POST', 'PUT', 'PATCH', 'DELETE'].indexOf(method) !== -1) {
await initCsrfToken();
}
if (['POST', 'PUT', 'PATCH', 'DELETE'].indexOf(method) !== -1 && csrfToken) {
options.headers['X-CSRF-Token'] = csrfToken;
}
var response = await fetch(url, options);
// Capture new CSRF token from response
var newToken = response.headers.get('X-CSRF-Token');
if (newToken) {
csrfToken = newToken;
}
return response;
}
/**
* Initialize CSRF token by fetching from server
* Should be called before any state-changing requests
*/
async function initCsrfToken() {
if (csrfToken) return; // Already initialized
try {
var response = await fetch('/api/csrf-token');
if (response.ok) {
var data = await response.json();
csrfToken = data.csrfToken || response.headers.get('X-CSRF-Token');
}
} catch (err) {
console.warn('[CLI Manager] Failed to fetch CSRF token:', err);
}
}
// ========== Active Execution Sync ==========
/**
* Sync active CLI executions from server
* Called when view is opened to restore running execution state
*/
async function syncActiveExecutions() {
try {
var response = await fetch('/api/cli/active');
if (!response.ok) return;
var data = await response.json();
if (!data.executions || data.executions.length === 0) return;
// Restore the first active execution
var active = data.executions[0];
// Restore execution state
currentCliExecution = {
executionId: active.id,
tool: active.tool,
mode: active.mode,
startTime: active.startTime
};
cliExecutionOutput = active.output || '';
// Update UI if output panel exists
var outputPanel = document.getElementById('cli-output-panel');
var outputContent = document.getElementById('cli-output-content');
if (outputPanel && outputContent) {
outputPanel.style.display = 'block';
outputContent.textContent = cliExecutionOutput;
outputContent.scrollTop = outputContent.scrollHeight;
// Update status indicator
var statusIndicator = outputPanel.querySelector('.cli-status-indicator');
if (statusIndicator) {
statusIndicator.className = 'cli-status-indicator running';
statusIndicator.textContent = t('cli.running') || 'Running...';
}
}
console.log('[CLI Manager] Restored active execution:', active.id);
} catch (err) {
console.warn('[CLI Manager] Failed to sync active executions:', err);
}
}
// ========== Navigation Helpers ==========
/**
* Navigate to CodexLens Manager page
*/
function navigateToCodexLensManager() {
var navItem = document.querySelector('.nav-item[data-view="codexlens-manager"]');
if (navItem) {
navItem.click();
} else {
// Fallback: try to render directly
if (typeof renderCodexLensManager === 'function') {
currentView = 'codexlens-manager';
renderCodexLensManager();
} else {
showRefreshToast(t('common.error') + ': CodexLens Manager not available', 'error');
}
}
}
// ========== CCW Installations ==========
async function loadCcwInstallations() {
try {
var response = await fetch('/api/ccw/installations');
if (!response.ok) throw new Error('Failed to load CCW installations');
var data = await response.json();
ccwInstallations = data.installations || [];
return ccwInstallations;
} catch (err) {
console.error('Failed to load CCW installations:', err);
ccwInstallations = [];
return [];
}
}
// ========== CCW Endpoint Tools ==========
async function loadCcwEndpointTools() {
try {
var response = await fetch('/api/ccw/tools');
if (!response.ok) throw new Error('Failed to load CCW endpoint tools');
var data = await response.json();
ccwEndpointTools = data.tools || [];
return ccwEndpointTools;
} catch (err) {
console.error('Failed to load CCW endpoint tools:', err);
ccwEndpointTools = [];
return [];
}
}
// ========== LiteLLM API Endpoints ==========
var litellmApiEndpoints = [];
var cliCustomEndpoints = [];
async function loadLitellmApiEndpoints() {
try {
var response = await fetch('/api/litellm-api/config');
if (!response.ok) throw new Error('Failed to load LiteLLM endpoints');
var data = await response.json();
litellmApiEndpoints = data.endpoints || [];
window.litellmApiConfig = data;
return litellmApiEndpoints;
} catch (err) {
console.error('Failed to load LiteLLM endpoints:', err);
litellmApiEndpoints = [];
return [];
}
}
async function loadCliCustomEndpoints() {
try {
var response = await fetch('/api/cli/endpoints');
if (!response.ok) throw new Error('Failed to load CLI custom endpoints');
var data = await response.json();
cliCustomEndpoints = data.endpoints || [];
return cliCustomEndpoints;
} catch (err) {
console.error('Failed to load CLI custom endpoints:', err);
cliCustomEndpoints = [];
return [];
}
}
async function toggleEndpointEnabled(endpointId, enabled) {
try {
await initCsrfToken();
var response = await csrfFetch('/api/cli/endpoints/' + endpointId, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled: enabled })
});
if (!response.ok) throw new Error('Failed to update endpoint');
var data = await response.json();
if (data.success) {
// Update local state
var idx = cliCustomEndpoints.findIndex(function(e) { return e.id === endpointId; });
if (idx >= 0) {
cliCustomEndpoints[idx].enabled = enabled;
}
showRefreshToast((enabled ? 'Enabled' : 'Disabled') + ' endpoint: ' + endpointId, 'success');
}
return data;
} catch (err) {
showRefreshToast('Failed to update endpoint: ' + err.message, 'error');
throw err;
}
}
async function syncEndpointToCliTools(endpoint) {
try {
await initCsrfToken();
var response = await csrfFetch('/api/cli/endpoints', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: endpoint.id,
name: endpoint.name,
enabled: true
})
});
if (!response.ok) throw new Error('Failed to sync endpoint');
var data = await response.json();
if (data.success) {
cliCustomEndpoints = data.endpoints;
showRefreshToast('Endpoint synced to CLI tools: ' + endpoint.id, 'success');
renderToolsSection();
}
return data;
} catch (err) {
showRefreshToast('Failed to sync endpoint: ' + err.message, 'error');
throw err;
}
}
window.toggleEndpointEnabled = toggleEndpointEnabled;
window.syncEndpointToCliTools = syncEndpointToCliTools;
// ========== CLI Tool Configuration ==========
async function loadCliToolConfig() {
try {
var response = await fetch('/api/cli/config');
if (!response.ok) throw new Error('Failed to load CLI config');
var data = await response.json();
cliToolConfig = data.config || null;
predefinedModels = data.predefinedModels || {};
return data;
} catch (err) {
console.error('Failed to load CLI config:', err);
cliToolConfig = null;
predefinedModels = {};
return null;
}
}
async function updateCliToolConfig(tool, updates) {
try {
// Ensure CSRF token is initialized before making state-changing request
await initCsrfToken();
var response = await csrfFetch('/api/cli/config/' + tool, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates)
});
var data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to update CLI config');
}
if (data.success && cliToolConfig && cliToolConfig.tools) {
cliToolConfig.tools[tool] = data.config;
}
return data;
} catch (err) {
console.error('Failed to update CLI config:', err);
throw err;
}
}
// ========== Tool Configuration Modal ==========
async function showToolConfigModal(toolName) {
// Load config if not already loaded
if (!cliToolConfig) {
await loadCliToolConfig();
}
var toolConfig = cliToolConfig && cliToolConfig.tools ? cliToolConfig.tools[toolName] : null;
var models = predefinedModels[toolName] || [];
var status = cliToolStatus[toolName] || {};
if (!toolConfig) {
toolConfig = { enabled: true, primaryModel: '', secondaryModel: '' };
}
var content = buildToolConfigModalContent(toolName, toolConfig, models, status);
showModal('Configure ' + toolName.charAt(0).toUpperCase() + toolName.slice(1), content, { size: 'md' });
// Initialize event handlers after modal is shown
setTimeout(function() {
initToolConfigModalEvents(toolName, toolConfig, models);
}, 100);
}
function buildToolConfigModalContent(tool, config, models, status) {
var isAvailable = status.available;
var isEnabled = config.enabled;
// Check if model is custom (not in predefined list or empty)
var isPrimaryCustom = !config.primaryModel || models.indexOf(config.primaryModel) === -1;
var isSecondaryCustom = !config.secondaryModel || models.indexOf(config.secondaryModel) === -1;
var modelsOptionsHtml = function(selected, isCustom) {
var html = '';
for (var i = 0; i < models.length; i++) {
var m = models[i];
html += '' + escapeHtml(m) + ' ';
}
html += 'Custom... ';
return html;
};
return '
';
}
function initToolConfigModalEvents(tool, currentConfig, models) {
// Local tags state (copy from config)
var currentTags = (currentConfig.tags || []).slice();
// Helper to render tags inline with input
function renderTags() {
var container = document.getElementById('tagsUnifiedInput');
var input = document.getElementById('tagInput');
if (!container) return;
// Remove existing tag items but keep the input
container.querySelectorAll('.tag-item').forEach(function(el) { el.remove(); });
// Insert tags before the input
currentTags.forEach(function(tag) {
var tagEl = document.createElement('span');
tagEl.className = 'tag-item tag-' + escapeHtml(tag);
tagEl.innerHTML = escapeHtml(tag) + '× ';
container.insertBefore(tagEl, input);
});
// Re-attach remove handlers
container.querySelectorAll('.tag-remove').forEach(function(btn) {
btn.onclick = function(e) {
e.stopPropagation();
var tagToRemove = this.getAttribute('data-tag');
currentTags = currentTags.filter(function(t) { return t !== tagToRemove; });
renderTags();
};
});
// Update predefined tag buttons state
document.querySelectorAll('.predefined-tag-btn').forEach(function(btn) {
var tag = btn.getAttribute('data-tag');
if (currentTags.indexOf(tag) !== -1) {
btn.classList.add('selected');
btn.disabled = true;
} else {
btn.classList.remove('selected');
btn.disabled = false;
}
});
}
// Click on unified input container focuses the input
var unifiedInput = document.getElementById('tagsUnifiedInput');
if (unifiedInput) {
unifiedInput.onclick = function(e) {
if (e.target === this) {
document.getElementById('tagInput').focus();
}
};
}
// Tag input handler
var tagInput = document.getElementById('tagInput');
if (tagInput) {
tagInput.onkeydown = function(e) {
if (e.key === 'Enter') {
e.preventDefault();
var newTag = this.value.trim();
if (newTag && currentTags.indexOf(newTag) === -1) {
currentTags.push(newTag);
renderTags();
}
this.value = '';
}
};
}
// Predefined tag click handlers
document.querySelectorAll('.predefined-tag-btn').forEach(function(btn) {
btn.onclick = function() {
var tag = this.getAttribute('data-tag');
if (tag && currentTags.indexOf(tag) === -1) {
currentTags.push(tag);
renderTags();
}
};
});
// Initialize tags display
renderTags();
// Initialize lucide icons for predefined buttons
if (window.lucide) lucide.createIcons();
// Toggle Enable/Disable
var toggleBtn = document.getElementById('toggleEnableBtn');
if (toggleBtn) {
toggleBtn.onclick = async function() {
var newEnabled = !currentConfig.enabled;
try {
await updateCliToolConfig(tool, { enabled: newEnabled });
showRefreshToast(tool + ' ' + (newEnabled ? 'enabled' : 'disabled'), 'success');
closeModal();
renderToolsSection();
if (window.lucide) lucide.createIcons();
} catch (err) {
showRefreshToast('Failed to update: ' + err.message, 'error');
}
};
}
// Install/Uninstall
var installBtn = document.getElementById('installBtn');
if (installBtn) {
installBtn.onclick = function() {
var status = cliToolStatus[tool] || {};
closeModal();
if (status.available) {
openCliUninstallWizard(tool);
} else {
openCliInstallWizard(tool);
}
};
}
// Model select handlers
var primarySelect = document.getElementById('primaryModelSelect');
var primaryCustom = document.getElementById('primaryModelCustom');
var secondarySelect = document.getElementById('secondaryModelSelect');
var secondaryCustom = document.getElementById('secondaryModelCustom');
if (primarySelect && primaryCustom) {
primarySelect.onchange = function() {
if (this.value === '__custom__') {
primaryCustom.style.display = 'block';
primaryCustom.focus();
} else {
primaryCustom.style.display = 'none';
primaryCustom.value = '';
}
};
}
if (secondarySelect && secondaryCustom) {
secondarySelect.onchange = function() {
if (this.value === '__custom__') {
secondaryCustom.style.display = 'block';
secondaryCustom.focus();
} else {
secondaryCustom.style.display = 'none';
secondaryCustom.value = '';
}
};
}
// Save button
var saveBtn = document.getElementById('saveConfigBtn');
if (saveBtn) {
saveBtn.onclick = async function() {
var primaryModel = primarySelect.value === '__custom__'
? primaryCustom.value.trim()
: primarySelect.value;
var secondaryModel = secondarySelect.value === '__custom__'
? secondaryCustom.value.trim()
: secondarySelect.value;
if (!primaryModel) {
showRefreshToast('Primary model is required', 'error');
return;
}
if (!secondaryModel) {
showRefreshToast('Secondary model is required', 'error');
return;
}
try {
await updateCliToolConfig(tool, {
primaryModel: primaryModel,
secondaryModel: secondaryModel,
tags: currentTags
});
// Reload config to reflect changes
await loadCliToolConfig();
showRefreshToast('Configuration saved', 'success');
closeModal();
renderToolsSection();
if (window.lucide) lucide.createIcons();
} catch (err) {
showRefreshToast('Failed to save: ' + err.message, 'error');
}
};
}
// Initialize lucide icons in modal
if (window.lucide) lucide.createIcons();
}
// ========== Rendering ==========
async function renderCliManager() {
var container = document.getElementById('mainContent');
if (!container) return;
// Hide stats grid and search for CLI view
var statsGrid = document.getElementById('statsGrid');
var searchInput = document.getElementById('searchInput');
if (statsGrid) statsGrid.style.display = 'none';
if (searchInput) searchInput.parentElement.style.display = 'none';
// Load data (including CodexLens status for tools section)
await Promise.all([
loadCliToolStatus(),
loadCodexLensStatus(),
loadCcwInstallations(),
loadCcwEndpointTools(),
loadLitellmApiEndpoints(),
loadCliCustomEndpoints()
]);
container.innerHTML = '' +
'
' +
'
' +
'
' +
'
' +
'
' +
'';
// Render sub-panels
renderToolsSection();
renderCcwSection();
renderLanguageSettingsSection();
renderCliSettingsSection();
renderCcwEndpointToolsSection();
// Initialize storage manager card
if (typeof initStorageManager === 'function') {
initStorageManager();
}
// Initialize Lucide icons
if (window.lucide) lucide.createIcons();
// Sync active executions to restore running state
await syncActiveExecutions();
}
// ========== Helper Functions ==========
/**
* Get selected embedding model from dropdown
* @returns {string} Selected model profile (code, fast, multilingual, balanced)
*/
function getSelectedModel() {
var select = document.getElementById('codexlensModelSelect');
return select ? select.value : 'code';
}
/**
* Build model select options HTML, showing only installed models
* @returns {string} HTML string for select options
*/
function buildModelSelectOptions() {
var installedModels = window.cliToolsStatus?.codexlens?.installedModels || [];
var allModels = window.cliToolsStatus?.codexlens?.allModels || [];
// Model display configuration
var modelConfig = {
'code': { label: t('index.modelCode') || 'Code (768d)', star: true },
'base': { label: t('index.modelBase') || 'Base (768d)', star: false },
'fast': { label: t('index.modelFast') || 'Fast (384d)', star: false },
'minilm': { label: t('index.modelMinilm') || 'MiniLM (384d)', star: false },
'multilingual': { label: t('index.modelMultilingual') || 'Multilingual (1024d)', warn: true },
'balanced': { label: t('index.modelBalanced') || 'Balanced (1024d)', warn: true }
};
// If no models installed, show placeholder
if (installedModels.length === 0) {
return '' + (t('index.noModelsInstalled') || 'No models installed') + ' ';
}
// Build options for installed models only
var options = '';
var firstInstalled = null;
// Preferred order: code, fast, minilm, base, multilingual, balanced
var preferredOrder = ['code', 'fast', 'minilm', 'base', 'multilingual', 'balanced'];
preferredOrder.forEach(function(profile) {
if (installedModels.includes(profile) && modelConfig[profile]) {
var config = modelConfig[profile];
var style = config.warn ? ' style="color: var(--muted-foreground)"' : '';
var suffix = config.star ? ' ⭐' : (config.warn ? ' ⚠️' : '');
var selected = !firstInstalled ? ' selected' : '';
if (!firstInstalled) firstInstalled = profile;
options += '' + config.label + suffix + ' ';
}
});
return options;
}
// ========== Tools Section (Left Column) ==========
function renderToolsSection() {
var container = document.getElementById('tools-section');
if (!container) return;
var toolDescriptions = {
gemini: t('cli.geminiDesc'),
qwen: t('cli.qwenDesc'),
codex: t('cli.codexDesc'),
claude: t('cli.claudeDesc') || 'Anthropic Claude Code CLI for AI-assisted development',
opencode: t('cli.opencodeDesc') || 'OpenCode CLI - Multi-provider AI coding assistant'
};
var tools = ['gemini', 'qwen', 'codex', 'claude', 'opencode'];
var available = Object.values(cliToolStatus).filter(function(t) { return t.available; }).length;
var toolsHtml = tools.map(function(tool) {
var status = cliToolStatus[tool] || {};
var isAvailable = status.available;
var toolConfig = cliToolConfig && cliToolConfig.tools ? cliToolConfig.tools[tool] : null;
var tags = toolConfig && toolConfig.tags ? toolConfig.tags : [];
// Build tags HTML with color classes
var tagsHtml = tags.length > 0
? '' + tags.map(function(tag) {
return '' + escapeHtml(tag) + ' ';
}).join('') + '
'
: '';
return '';
}).join('');
// CodexLens item - simplified view with link to manager page
var codexLensHtml = '';
// Semantic Search item (only show if CodexLens is installed)
var semanticHtml = '';
if (codexLensStatus.ready) {
semanticHtml = '';
}
// API Endpoints section
var apiEndpointsHtml = '';
if (litellmApiEndpoints.length > 0) {
var endpointItems = litellmApiEndpoints.map(function(endpoint) {
// Check if endpoint is synced to CLI tools
var cliEndpoint = cliCustomEndpoints.find(function(e) { return e.id === endpoint.id; });
var isSynced = !!cliEndpoint;
var isEnabled = cliEndpoint ? cliEndpoint.enabled : false;
// Find provider info
var provider = (window.litellmApiConfig?.providers || []).find(function(p) { return p.id === endpoint.providerId; });
var providerName = provider ? provider.name : endpoint.providerId;
return '';
}).join('');
apiEndpointsHtml = '';
}
container.innerHTML = '' +
'' +
toolsHtml +
codexLensHtml +
semanticHtml +
'
' +
apiEndpointsHtml;
if (window.lucide) lucide.createIcons();
}
// ========== CCW Section (Right Column) ==========
function renderCcwSection() {
var container = document.getElementById('ccw-section');
if (!container) return;
var installationsHtml = '';
if (ccwInstallations.length === 0) {
installationsHtml = '' +
'
' +
'
' + t('ccw.noInstallations') + '
' +
'
' +
' ' + t('ccw.installCcw') + ' ' +
'
';
} else {
installationsHtml = '';
for (var i = 0; i < ccwInstallations.length; i++) {
var inst = ccwInstallations[i];
var isGlobal = inst.installation_mode === 'Global';
var modeIcon = isGlobal ? 'home' : 'folder';
var version = inst.application_version || 'unknown';
var installDate = new Date(inst.installation_date).toLocaleDateString();
installationsHtml += '
' +
'
' +
'
' +
' ' +
'
' +
'
' +
'' +
'
' + escapeHtml(inst.installation_path) + '
' +
'
' +
' ' + installDate + ' ' +
' ' + (inst.files_count || 0) + ' files ' +
'
' +
'
' +
'
' +
'
' +
'' +
' ' +
' ' +
'' +
' ' +
' ' +
'
' +
'
';
}
installationsHtml += '
';
}
container.innerHTML = '' +
installationsHtml;
if (window.lucide) lucide.createIcons();
}
// ========== Language Settings State ==========
var chineseResponseEnabled = false;
var chineseResponseLoading = false;
var codexChineseResponseEnabled = false;
var codexChineseResponseLoading = false;
var windowsPlatformEnabled = false;
var windowsPlatformLoading = false;
// ========== Language Settings Section ==========
async function loadLanguageSettings() {
try {
var response = await fetch('/api/language/chinese-response');
if (!response.ok) throw new Error('Failed to load language settings');
var data = await response.json();
chineseResponseEnabled = data.claudeEnabled || data.enabled || false;
codexChineseResponseEnabled = data.codexEnabled || false;
return data;
} catch (err) {
console.error('Failed to load language settings:', err);
chineseResponseEnabled = false;
codexChineseResponseEnabled = false;
return { claudeEnabled: false, codexEnabled: false, guidelinesExists: false };
}
}
async function loadWindowsPlatformSettings() {
try {
var response = await fetch('/api/language/windows-platform');
if (!response.ok) throw new Error('Failed to load Windows platform settings');
var data = await response.json();
windowsPlatformEnabled = data.enabled || false;
return data;
} catch (err) {
console.error('Failed to load Windows platform settings:', err);
windowsPlatformEnabled = false;
return { enabled: false, guidelinesExists: false };
}
}
async function toggleChineseResponse(enabled, target) {
// target: 'claude' (default) or 'codex'
target = target || 'claude';
var isCodex = target === 'codex';
var loadingVar = isCodex ? 'codexChineseResponseLoading' : 'chineseResponseLoading';
if (isCodex ? codexChineseResponseLoading : chineseResponseLoading) return;
// Pre-check: verify CCW workflows are installed (only when enabling)
if (enabled && typeof ccwInstallStatus !== 'undefined' && !ccwInstallStatus.installed) {
var missingFile = ccwInstallStatus.missingFiles.find(function(f) { return f === 'chinese-response.md'; });
if (missingFile) {
showRefreshToast(t('lang.installRequired'), 'warning');
return;
}
}
if (isCodex) {
codexChineseResponseLoading = true;
} else {
chineseResponseLoading = true;
}
try {
var response = await fetch('/api/language/chinese-response', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled: enabled, target: target })
});
if (!response.ok) {
var errData = await response.json();
// Show specific error message from backend
var errorMsg = errData.error || 'Failed to update setting';
if (errorMsg.includes('not found')) {
showRefreshToast(t('lang.installRequired'), 'warning');
} else {
showRefreshToast((enabled ? t('lang.enableFailed') : t('lang.disableFailed')) + ': ' + errorMsg, 'error');
}
throw new Error(errorMsg);
}
var data = await response.json();
if (isCodex) {
codexChineseResponseEnabled = data.enabled;
} else {
chineseResponseEnabled = data.enabled;
}
// Update UI
renderLanguageSettingsSection();
// Show toast
var toolName = isCodex ? 'Codex' : 'Claude';
showRefreshToast(toolName + ': ' + (enabled ? t('lang.enableSuccess') : t('lang.disableSuccess')), 'success');
} catch (err) {
console.error('Failed to toggle Chinese response:', err);
// Error already shown in the !response.ok block
} finally {
if (isCodex) {
codexChineseResponseLoading = false;
} else {
chineseResponseLoading = false;
}
}
}
async function toggleWindowsPlatform(enabled) {
if (windowsPlatformLoading) return;
// Pre-check: verify CCW workflows are installed (only when enabling)
if (enabled && typeof ccwInstallStatus !== 'undefined' && !ccwInstallStatus.installed) {
var missingFile = ccwInstallStatus.missingFiles.find(function(f) { return f === 'windows-platform.md'; });
if (missingFile) {
showRefreshToast(t('lang.installRequired'), 'warning');
return;
}
}
windowsPlatformLoading = true;
try {
var response = await fetch('/api/language/windows-platform', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled: enabled })
});
if (!response.ok) {
var errData = await response.json();
// Show specific error message from backend
var errorMsg = errData.error || 'Failed to update setting';
if (errorMsg.includes('not found')) {
showRefreshToast(t('lang.installRequired'), 'warning');
} else {
showRefreshToast((enabled ? t('lang.windowsEnableFailed') : t('lang.windowsDisableFailed')) + ': ' + errorMsg, 'error');
}
throw new Error(errorMsg);
}
var data = await response.json();
windowsPlatformEnabled = data.enabled;
// Update UI
renderLanguageSettingsSection();
// Show toast
showRefreshToast(enabled ? t('lang.windowsEnableSuccess') : t('lang.windowsDisableSuccess'), 'success');
} catch (err) {
console.error('Failed to toggle Windows platform:', err);
// Error already shown in the !response.ok block
} finally {
windowsPlatformLoading = false;
}
}
async function renderLanguageSettingsSection() {
var container = document.getElementById('language-settings-section');
if (!container) return;
// Load current state if not loaded
if (!chineseResponseEnabled && !codexChineseResponseEnabled && !chineseResponseLoading) {
await loadLanguageSettings();
}
if (!windowsPlatformEnabled && !windowsPlatformLoading) {
await loadWindowsPlatformSettings();
}
var settingsHtml = '' +
'' +
// Chinese Response - Claude
'
' +
'
' +
' ' +
t('lang.chinese') + ' Claude ' +
' ' +
'
' +
'' +
' ' +
' ' +
' ' +
'' +
(chineseResponseEnabled ? t('lang.enabled') : t('lang.disabled')) +
' ' +
'
' +
'
' + t('lang.chineseDescClaude') + '
' +
'
' +
// Chinese Response - Codex
'
' +
'
' +
' ' +
t('lang.chinese') + ' Codex ' +
' ' +
'
' +
'' +
' ' +
' ' +
' ' +
'' +
(codexChineseResponseEnabled ? t('lang.enabled') : t('lang.disabled')) +
' ' +
'
' +
'
' + t('lang.chineseDescCodex') + '
' +
'
' +
// Windows Platform
'
' +
'
' +
' ' +
t('lang.windows') +
' ' +
'
' +
'' +
' ' +
' ' +
' ' +
'' +
(windowsPlatformEnabled ? t('lang.enabled') : t('lang.disabled')) +
' ' +
'
' +
'
' + t('lang.windowsDesc') + '
' +
'
' +
'
';
container.innerHTML = settingsHtml;
if (window.lucide) lucide.createIcons();
}
// ========== CLI Settings Section (Full Width) ==========
function renderCliSettingsSection() {
var container = document.getElementById('cli-settings-section');
if (!container) return;
var settingsHtml = '' +
'' +
'
' +
'
' +
' ' +
t('cli.promptFormat') +
' ' +
'
' +
'' +
'Plain Text ' +
'YAML ' +
'JSON ' +
' ' +
'
' +
'
' + t('cli.promptFormatDesc') + '
' +
'
' +
'
' +
'
' +
' ' +
t('cli.storageBackend') +
' ' +
'
' +
'SQLite ' +
'
' +
'
' + t('cli.storageBackendDesc') + '
' +
'
' +
'
' +
'
' +
' ' +
t('cli.smartContext') +
' ' +
'
' +
'' +
' ' +
' ' +
' ' +
'
' +
'
' + t('cli.smartContextDesc') + '
' +
'
' +
'
' +
'
' +
' ' +
t('cli.nativeResume') +
' ' +
'
' +
'' +
' ' +
' ' +
' ' +
'
' +
'
' + t('cli.nativeResumeDesc') + '
' +
'
' +
'
' +
'
' +
' ' +
t('cli.recursiveQuery') +
' ' +
'
' +
'' +
' ' +
' ' +
' ' +
'
' +
'
' + t('cli.recursiveQueryDesc') + '
' +
'
' +
'
' +
'
' +
' ' +
t('cli.maxContextFiles') +
' ' +
'
' +
'' +
'5 files ' +
'10 files ' +
'20 files ' +
' ' +
'
' +
'
' + t('cli.maxContextFilesDesc') + '
' +
'
' +
'
';
container.innerHTML = settingsHtml;
if (window.lucide) lucide.createIcons();
}
// ========== CCW Endpoint Tools Section (Full Width) ==========
function renderCcwEndpointToolsSection() {
var container = document.getElementById('ccw-endpoint-tools-section');
if (!container) return;
var count = (ccwEndpointTools || []).length;
var toolsHtml = '';
if (!ccwEndpointTools || ccwEndpointTools.length === 0) {
toolsHtml = '' +
'
' +
'
' + t('ccw.noEndpointTools') + '
' +
'
' +
' ' + t('common.refresh') + ' ' +
'
';
} else {
toolsHtml = '';
}
container.innerHTML = '' +
toolsHtml;
if (window.lucide) lucide.createIcons();
}
// ========== Endpoint Tool Detail Modal ==========
function showEndpointToolDetail(toolIndex) {
var tool = ccwEndpointTools[toolIndex];
if (!tool) return;
var name = tool.name || 'unknown';
var desc = tool.description || 'No description available';
var params = tool.parameters || {};
var properties = params.properties || {};
var required = params.required || [];
// Build parameters table
var paramsHtml = '';
var propKeys = Object.keys(properties);
if (propKeys.length > 0) {
paramsHtml = '';
} else {
paramsHtml = '' +
' ' +
'This tool has no parameters ' +
'
';
}
// Usage example
var usageExample = 'ccw tool exec ' + name;
if (propKeys.length > 0) {
var exampleParams = {};
for (var j = 0; j < Math.min(propKeys.length, 2); j++) {
var k = propKeys[j];
var p = properties[k];
if (p.type === 'string') exampleParams[k] = '';
else if (p.type === 'boolean') exampleParams[k] = true;
else if (p.type === 'number') exampleParams[k] = 0;
else exampleParams[k] = '';
}
usageExample += " '" + JSON.stringify(exampleParams) + "'";
}
var modalContent = '';
showModal(name, modalContent, { size: 'lg' });
}
function copyToolUsage(btn, text) {
navigator.clipboard.writeText(text).then(function() {
var icon = btn.querySelector('i');
if (icon) {
icon.setAttribute('data-lucide', 'check');
if (window.lucide) lucide.createIcons();
setTimeout(function() {
icon.setAttribute('data-lucide', 'copy');
if (window.lucide) lucide.createIcons();
}, 2000);
}
});
}
// CCW Install Carousel State
var ccwCarouselIndex = 0;
function renderCcwInstallPanel() {
var container = document.getElementById('ccw-install-panel');
if (!container) return;
var html = '' +
'';
if (ccwInstallations.length === 0) {
html += '
' +
'
' +
'
No installations found
' +
'
' +
' Install CCW ';
} else {
// Carousel container
html += '
';
// Left arrow (show only if more than 1 installation)
if (ccwInstallations.length > 1) {
html += '
' +
' ';
}
html += '
';
for (var i = 0; i < ccwInstallations.length; i++) {
var inst = ccwInstallations[i];
var isGlobal = inst.installation_mode === 'Global';
var modeIcon = isGlobal ? 'home' : 'folder';
var version = inst.application_version || 'unknown';
var installDate = new Date(inst.installation_date).toLocaleDateString();
var activeClass = i === ccwCarouselIndex ? 'active' : '';
html += '
' +
'' +
'
' + escapeHtml(inst.installation_path) + '
' +
'
' +
' ' + installDate + ' ' +
' ' + (inst.files_count || 0) + ' files ' +
'
' +
'
';
}
html += '
';
// Right arrow (show only if more than 1 installation)
if (ccwInstallations.length > 1) {
html += '
' +
' ';
}
html += '
';
// Dots indicator (show only if more than 1 installation)
if (ccwInstallations.length > 1) {
html += '
';
for (var j = 0; j < ccwInstallations.length; j++) {
var dotActive = j === ccwCarouselIndex ? 'active' : '';
html += ' ';
}
html += '
';
}
}
html += '
';
container.innerHTML = html;
if (window.lucide) lucide.createIcons();
// Update carousel position
updateCcwCarouselPosition();
}
function ccwCarouselPrev() {
if (ccwCarouselIndex > 0) {
ccwCarouselIndex--;
updateCcwCarouselPosition();
updateCcwCarouselDots();
}
}
function ccwCarouselNext() {
if (ccwCarouselIndex < ccwInstallations.length - 1) {
ccwCarouselIndex++;
updateCcwCarouselPosition();
updateCcwCarouselDots();
}
}
function ccwCarouselGoTo(index) {
ccwCarouselIndex = index;
updateCcwCarouselPosition();
updateCcwCarouselDots();
}
function updateCcwCarouselPosition() {
var track = document.getElementById('ccwCarouselTrack');
if (track) {
track.style.transform = 'translateX(-' + (ccwCarouselIndex * 100) + '%)';
}
// Update card active states
var cards = document.querySelectorAll('.ccw-carousel-card');
cards.forEach(function(card, idx) {
card.classList.toggle('active', idx === ccwCarouselIndex);
});
}
function updateCcwCarouselDots() {
var dots = document.querySelectorAll('.ccw-carousel-dot');
dots.forEach(function(dot, idx) {
dot.classList.toggle('active', idx === ccwCarouselIndex);
});
}
// CCW Install Modal
function showCcwInstallModal() {
var modalContent = '' +
'
' +
'
' +
'
' +
'
' +
'
Global Installation
' +
'
Install to user home directory (~/.claude)
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
Path Installation
' +
'
Install to a specific project folder
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
';
showModal('Install CCW', modalContent);
}
function selectCcwInstallMode(mode) {
if (mode === 'Global') {
closeModal();
runCcwInstall('Global');
}
}
function toggleCcwPathInput() {
var section = document.getElementById('ccwPathInputSection');
if (section) {
section.classList.toggle('hidden');
if (!section.classList.contains('hidden')) {
var input = document.getElementById('ccwInstallPath');
if (input) input.focus();
}
}
}
function executeCcwInstall() {
var input = document.getElementById('ccwInstallPath');
var path = input ? input.value.trim() : '';
if (!path) {
showRefreshToast('Please enter a path', 'error');
return;
}
closeModal();
runCcwInstall('Path', path);
}
function truncatePath(path) {
if (!path) return '';
var maxLen = 35;
if (path.length <= maxLen) return path;
return '...' + path.slice(-maxLen + 3);
}
function renderCliExecutePanel() {
var container = document.getElementById('cli-execute-panel');
if (!container) return;
var tools = ['gemini', 'qwen', 'codex'];
var modes = ['analysis', 'write', 'auto'];
var html = '' +
'';
container.innerHTML = html;
if (window.lucide) lucide.createIcons();
}
// ========== CCW Actions ==========
function runCcwInstall(mode, customPath) {
var command;
if (mode === 'Global') {
command = 'ccw install --mode Global';
} else {
var installPath = customPath || projectPath;
command = 'ccw install --mode Path --path "' + installPath + '"';
}
// Copy command to clipboard
if (navigator.clipboard) {
navigator.clipboard.writeText(command).then(function() {
showRefreshToast('Command copied: ' + command, 'success');
}).catch(function() {
showRefreshToast('Run: ' + command, 'info');
});
} else {
showRefreshToast('Run: ' + command, 'info');
}
}
async function runCcwUpgrade() {
showRefreshToast(t('ccw.upgradeStarting'), 'info');
try {
var response = await fetch('/api/ccw/upgrade', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({})
});
var result = await response.json();
if (result.success) {
showRefreshToast(t('ccw.upgradeCompleted'), 'success');
// Reload installations after upgrade
setTimeout(function() {
loadCcwInstallations().then(function() {
renderCcwInstallPanel();
});
}, 1000);
} else {
showRefreshToast(t('ccw.upgradeFailed', { error: result.error || 'Unknown error' }), 'error');
}
} catch (err) {
showRefreshToast(t('ccw.upgradeFailed', { error: err.message }), 'error');
}
}
function confirmCcwUninstall(installPath) {
if (confirm(t('ccw.uninstallConfirm') + '\n' + (installPath || 'Current installation'))) {
var command = installPath
? 'ccw uninstall --path "' + installPath + '"'
: 'ccw uninstall';
if (navigator.clipboard) {
navigator.clipboard.writeText(command).then(function() {
showRefreshToast('Command copied: ' + command, 'success');
}).catch(function() {
showRefreshToast('Run: ' + command, 'info');
});
} else {
showRefreshToast('Run: ' + command, 'info');
}
}
}
// ========== Execution ==========
async function executeCliFromDashboard() {
var toolEl = document.getElementById('cli-exec-tool');
var modeEl = document.getElementById('cli-exec-mode');
var promptEl = document.getElementById('cli-exec-prompt');
var tool = toolEl ? toolEl.value : 'gemini';
var mode = modeEl ? modeEl.value : 'analysis';
var prompt = promptEl ? promptEl.value.trim() : '';
if (!prompt) {
showRefreshToast(t('toast.enterPrompt'), 'error');
return;
}
currentCliExecution = { tool: tool, mode: mode, prompt: prompt, startTime: Date.now() };
cliExecutionOutput = '';
var outputPanel = document.getElementById('cli-output-panel');
var outputContent = document.getElementById('cli-output-content');
var statusIndicator = document.getElementById('cli-output-status-indicator');
var statusText = document.getElementById('cli-output-status-text');
if (outputPanel) outputPanel.classList.remove('hidden');
if (outputContent) outputContent.textContent = '';
if (statusIndicator) statusIndicator.className = 'status-indicator running';
if (statusText) statusText.textContent = 'Running...';
var execBtn = document.querySelector('.cli-execute-actions .btn-primary');
if (execBtn) execBtn.disabled = true;
try {
var response = await fetch('/api/cli/execute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tool: tool,
mode: mode,
prompt: prompt,
dir: projectPath,
format: promptConcatFormat,
smartContext: {
enabled: smartContextEnabled,
maxFiles: smartContextMaxFiles
}
})
});
var result = await response.json();
if (statusIndicator) statusIndicator.className = 'status-indicator ' + (result.success ? 'success' : 'error');
if (statusText) {
var duration = formatDuration(result.execution ? result.execution.duration_ms : (Date.now() - currentCliExecution.startTime));
statusText.textContent = result.success ? 'Completed in ' + duration : 'Failed: ' + (result.error || 'Unknown');
}
await loadCliHistory();
renderCliHistory();
showRefreshToast(result.success ? t('toast.completed') : (result.error || t('toast.failed')), result.success ? 'success' : 'error');
} catch (error) {
if (statusIndicator) statusIndicator.className = 'status-indicator error';
if (statusText) statusText.textContent = 'Error: ' + error.message;
showRefreshToast(t('toast.error', { error: error.message }), 'error');
}
currentCliExecution = null;
if (execBtn) execBtn.disabled = false;
}
// ========== WebSocket Event Handlers ==========
function handleCliExecutionStarted(payload) {
currentCliExecution = {
executionId: payload.executionId,
tool: payload.tool,
mode: payload.mode,
startTime: new Date(payload.timestamp).getTime()
};
cliExecutionOutput = '';
// Show toast notification
if (typeof addGlobalNotification === 'function') {
addGlobalNotification('info', 'CLI ' + payload.tool + ' started', payload.mode + ' mode', 'CLI');
}
if (currentView === 'cli-manager') {
var outputPanel = document.getElementById('cli-output-panel');
var outputContent = document.getElementById('cli-output-content');
var statusIndicator = document.getElementById('cli-output-status-indicator');
var statusText = document.getElementById('cli-output-status-text');
if (outputPanel) outputPanel.classList.remove('hidden');
if (outputContent) outputContent.textContent = '';
if (statusIndicator) statusIndicator.className = 'status-indicator running';
if (statusText) statusText.textContent = 'Running ' + payload.tool + ' (' + payload.mode + ')...';
}
}
function handleCliOutput(payload) {
cliExecutionOutput += payload.data;
var outputContent = document.getElementById('cli-output-content');
if (outputContent) {
outputContent.textContent = cliExecutionOutput;
outputContent.scrollTop = outputContent.scrollHeight;
}
}
function handleCliExecutionCompleted(payload) {
var statusIndicator = document.getElementById('cli-output-status-indicator');
var statusText = document.getElementById('cli-output-status-text');
if (statusIndicator) statusIndicator.className = 'status-indicator ' + (payload.success ? 'success' : 'error');
if (statusText) statusText.textContent = payload.success ? 'Completed in ' + formatDuration(payload.duration_ms) : 'Failed: ' + payload.status;
// Show toast notification
if (typeof addGlobalNotification === 'function') {
if (payload.success) {
addGlobalNotification('success', 'CLI execution completed', formatDuration(payload.duration_ms), 'CLI');
} else {
addGlobalNotification('error', 'CLI execution failed', payload.status, 'CLI');
}
}
currentCliExecution = null;
if (currentView === 'cli-manager') {
loadCliHistory().then(function() { renderCliHistory(); });
}
}
function handleCliExecutionError(payload) {
var statusIndicator = document.getElementById('cli-output-status-indicator');
var statusText = document.getElementById('cli-output-status-text');
if (statusIndicator) statusIndicator.className = 'status-indicator error';
if (statusText) statusText.textContent = 'Error: ' + payload.error;
// Show toast notification
if (typeof addGlobalNotification === 'function') {
addGlobalNotification('error', 'CLI execution error', payload.error, 'CLI');
}
currentCliExecution = null;
}
// ========== CLI Tool Install/Uninstall Wizards ==========
function openCliInstallWizard(toolName) {
var toolDescriptions = {
gemini: 'Google AI for code analysis and generation',
qwen: 'Alibaba AI assistant for coding',
codex: 'OpenAI code generation and understanding',
claude: 'Anthropic AI assistant'
};
var toolPackages = {
gemini: '@google/gemini-cli',
qwen: '@qwen-code/qwen-code',
codex: '@openai/codex',
claude: '@anthropic-ai/claude-code'
};
var modal = document.createElement('div');
modal.id = 'cliInstallModal';
modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50';
modal.innerHTML =
'' +
'
' +
'
' +
'
' +
' ' +
'
' +
'
' +
'
Install ' + toolName.charAt(0).toUpperCase() + toolName.slice(1) + ' ' +
'
' + (toolDescriptions[toolName] || 'CLI tool') + '
' +
'
' +
'
' +
'
' +
'
' +
'
What will be installed: ' +
'
' +
'' +
' ' +
'NPM Package: ' + (toolPackages[toolName] || toolName) + ' ' +
' ' +
'' +
' ' +
'Global installation - Available system-wide ' +
' ' +
'' +
' ' +
'CLI commands - Accessible from terminal ' +
' ' +
' ' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
Installation Method
' +
'
Uses npm install -g
' +
'
First installation may take 1-2 minutes depending on network speed.
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
Starting installation... ' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'Cancel ' +
'' +
' ' +
'Install Now' +
' ' +
'
' +
'
';
document.body.appendChild(modal);
if (window.lucide) {
lucide.createIcons();
}
}
function closeCliInstallWizard() {
var modal = document.getElementById('cliInstallModal');
if (modal) {
modal.remove();
}
}
async function startCliInstall(toolName) {
var progressDiv = document.getElementById('cliInstallProgress');
var installBtn = document.getElementById('cliInstallBtn');
var statusText = document.getElementById('cliInstallStatus');
var progressBar = document.getElementById('cliInstallProgressBar');
progressDiv.classList.remove('hidden');
installBtn.disabled = true;
installBtn.innerHTML = 'Installing... ';
var stages = [
{ progress: 20, text: 'Connecting to NPM registry...' },
{ progress: 40, text: 'Downloading package...' },
{ progress: 60, text: 'Installing dependencies...' },
{ progress: 80, text: 'Setting up CLI commands...' },
{ progress: 95, text: 'Finalizing installation...' }
];
var currentStage = 0;
var progressInterval = setInterval(function() {
if (currentStage < stages.length) {
statusText.textContent = stages[currentStage].text;
progressBar.style.width = stages[currentStage].progress + '%';
currentStage++;
}
}, 1000);
try {
var response = await fetch('/api/cli/install', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tool: toolName })
});
clearInterval(progressInterval);
var result = await response.json();
if (result.success) {
progressBar.style.width = '100%';
statusText.textContent = 'Installation complete!';
setTimeout(function() {
closeCliInstallWizard();
showRefreshToast(toolName + ' installed successfully!', 'success');
loadCliToolStatus().then(function() {
renderToolsSection();
if (window.lucide) lucide.createIcons();
});
}, 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 openCliUninstallWizard(toolName) {
var modal = document.createElement('div');
modal.id = 'cliUninstallModal';
modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50';
modal.innerHTML =
'' +
'
' +
'
' +
'
' +
' ' +
'
' +
'
' +
'
Uninstall ' + toolName.charAt(0).toUpperCase() + toolName.slice(1) + ' ' +
'
Remove CLI tool from system
' +
'
' +
'
' +
'
' +
'
' +
'
What will be removed: ' +
'
' +
'' +
' ' +
'Global NPM package ' +
' ' +
'' +
' ' +
'CLI commands and executables ' +
' ' +
'' +
' ' +
'Tool configuration (if any) ' +
' ' +
' ' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
Note
' +
'
You can reinstall this tool anytime from the CLI Manager.
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
Removing package... ' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'Cancel ' +
'' +
' ' +
'Uninstall' +
' ' +
'
' +
'
';
document.body.appendChild(modal);
if (window.lucide) {
lucide.createIcons();
}
}
function closeCliUninstallWizard() {
var modal = document.getElementById('cliUninstallModal');
if (modal) {
modal.remove();
}
}
async function startCliUninstall(toolName) {
var progressDiv = document.getElementById('cliUninstallProgress');
var uninstallBtn = document.getElementById('cliUninstallBtn');
var statusText = document.getElementById('cliUninstallStatus');
var progressBar = document.getElementById('cliUninstallProgressBar');
progressDiv.classList.remove('hidden');
uninstallBtn.disabled = true;
uninstallBtn.innerHTML = 'Uninstalling... ';
var stages = [
{ progress: 33, text: 'Removing package files...' },
{ progress: 66, text: 'Cleaning up dependencies...' },
{ progress: 90, text: 'Finalizing removal...' }
];
var currentStage = 0;
var progressInterval = setInterval(function() {
if (currentStage < stages.length) {
statusText.textContent = stages[currentStage].text;
progressBar.style.width = stages[currentStage].progress + '%';
currentStage++;
}
}, 500);
try {
var response = await fetch('/api/cli/uninstall', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tool: toolName })
});
clearInterval(progressInterval);
var result = await response.json();
if (result.success) {
progressBar.style.width = '100%';
statusText.textContent = 'Uninstallation complete!';
setTimeout(function() {
closeCliUninstallWizard();
showRefreshToast(toolName + ' uninstalled successfully!', 'success');
loadCliToolStatus().then(function() {
renderToolsSection();
if (window.lucide) lucide.createIcons();
});
}, 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();
}
}
// ========== CodexLens Configuration Modal ==========
async function showCodexLensConfigModal() {
var loadingContent = '' +
'
' +
'
' + t('codexlens.loadingConfig') + '
' +
'
';
showModal(t('codexlens.config'), loadingContent, { size: 'md' });
try {
// Fetch current configuration
var response = await fetch('/api/codexlens/config');
var config = await response.json();
var content = buildCodexLensConfigContent(config);
showModal('CodexLens Configuration', content, { size: 'md' });
setTimeout(function() {
initCodexLensConfigEvents(config);
if (window.lucide) lucide.createIcons();
}, 100);
} catch (err) {
var errorContent = '' +
'
' +
'
' +
'
' +
'
Failed to load configuration
' +
'
' + err.message + '
' +
'
' +
'
' +
'
';
showModal('CodexLens Configuration', errorContent, { size: 'md' });
}
}
function buildCodexLensConfigContent(config) {
var status = codexLensStatus || {};
var isInstalled = status.ready;
var indexDir = config.index_dir || '~/.codexlens/indexes';
var currentWorkspace = config.current_workspace || 'None';
var indexCount = config.index_count || 0;
return '';
}
function initCodexLensConfigEvents(currentConfig) {
var saveBtn = document.getElementById('saveCodexLensConfigBtn');
if (saveBtn) {
saveBtn.onclick = async function() {
var indexDirInput = document.getElementById('indexDirInput');
var newIndexDir = indexDirInput ? indexDirInput.value.trim() : '';
if (!newIndexDir) {
showRefreshToast(t('codexlens.pathEmpty'), 'error');
return;
}
if (newIndexDir === currentConfig.index_dir) {
closeModal();
return;
}
saveBtn.disabled = true;
saveBtn.innerHTML = '' + t('common.saving') + ' ';
try {
var response = await fetch('/api/codexlens/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ index_dir: newIndexDir })
});
var result = await response.json();
if (result.success) {
showRefreshToast(t('codexlens.configSaved'), 'success');
closeModal();
// Refresh CodexLens status
if (typeof loadCodexLensStatus === 'function') {
await loadCodexLensStatus();
renderToolsSection();
if (window.lucide) lucide.createIcons();
}
} else {
showRefreshToast(t('common.saveFailed') + ': ' + result.error, 'error');
saveBtn.disabled = false;
saveBtn.innerHTML = ' ' + t('codexlens.saveConfig');
if (window.lucide) lucide.createIcons();
}
} catch (err) {
showRefreshToast(t('common.error') + ': ' + err.message, 'error');
saveBtn.disabled = false;
saveBtn.innerHTML = ' ' + t('codexlens.saveConfig');
if (window.lucide) lucide.createIcons();
}
};
}
// Test Search Button
var runSearchBtn = document.getElementById('runSearchBtn');
if (runSearchBtn) {
runSearchBtn.onclick = async function() {
var searchType = document.getElementById('searchTypeSelect').value;
var searchMode = document.getElementById('searchModeSelect').value;
var query = document.getElementById('searchQueryInput').value.trim();
var resultsDiv = document.getElementById('searchResults');
var resultCount = document.getElementById('searchResultCount');
var resultContent = document.getElementById('searchResultContent');
if (!query) {
showRefreshToast(t('codexlens.enterQuery'), 'warning');
return;
}
runSearchBtn.disabled = true;
runSearchBtn.innerHTML = '' + t('codexlens.searching') + ' ';
resultsDiv.classList.add('hidden');
try {
var endpoint = '/api/codexlens/' + searchType;
var params = new URLSearchParams({ query: query, limit: '20' });
// Add mode parameter for search and search_files (not for symbol search)
if (searchType === 'search' || searchType === 'search_files') {
params.append('mode', searchMode);
}
var response = await fetch(endpoint + '?' + params.toString());
var result = await response.json();
console.log('[CodexLens Test] Search result:', result);
if (result.success) {
var results = result.results || result.files || [];
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');
}
runSearchBtn.disabled = false;
runSearchBtn.innerHTML = ' ' + t('codexlens.runSearch');
if (window.lucide) lucide.createIcons();
} catch (err) {
console.error('[CodexLens Test] Error:', err);
resultContent.textContent = t('common.exception') + ': ' + err.message;
resultsDiv.classList.remove('hidden');
showRefreshToast(t('common.error') + ': ' + err.message, 'error');
runSearchBtn.disabled = false;
runSearchBtn.innerHTML = ' ' + t('codexlens.runSearch');
if (window.lucide) lucide.createIcons();
}
};
}
// Load semantic dependencies status
loadSemanticDepsStatus();
// Load model list
loadModelList();
}
// Load semantic dependencies status
async function loadSemanticDepsStatus() {
var container = document.getElementById('semanticDepsStatus');
if (!container) return;
try {
var response = await fetch('/api/codexlens/semantic/status');
var result = await response.json();
if (result.available) {
container.innerHTML =
'' +
' ' +
'' + t('codexlens.semanticInstalled') + ' ' +
'(' + (result.backend || 'fastembed') + ') ' +
'
';
} else {
container.innerHTML =
'' +
'
' +
' ' +
'' + t('codexlens.semanticNotInstalled') + ' ' +
'
' +
'
' +
' ' + t('codexlens.installDeps') +
' ' +
'
';
}
if (window.lucide) lucide.createIcons();
} catch (err) {
container.innerHTML =
'' + t('common.error') + ': ' + err.message + '
';
}
}
// Install semantic dependencies
async function installSemanticDeps() {
var container = document.getElementById('semanticDepsStatus');
if (!container) return;
container.innerHTML =
'' + t('codexlens.installingDeps') + '
';
try {
var response = await fetch('/api/codexlens/semantic/install', { method: 'POST' });
var result = await response.json();
if (result.success) {
showRefreshToast(t('codexlens.depsInstalled'), 'success');
await loadSemanticDepsStatus();
await loadModelList();
} else {
showRefreshToast(t('codexlens.depsInstallFailed') + ': ' + result.error, 'error');
await loadSemanticDepsStatus();
}
} catch (err) {
showRefreshToast(t('common.error') + ': ' + err.message, 'error');
await loadSemanticDepsStatus();
}
}
// Load model list
async function loadModelList() {
var container = document.getElementById('modelListContainer');
if (!container) return;
try {
var response = await fetch('/api/codexlens/models');
var result = await response.json();
if (!result.success || !result.result || !result.result.models) {
container.innerHTML =
'' + t('codexlens.semanticNotInstalled') + '
';
return;
}
var models = result.result.models;
var html = '';
models.forEach(function(model) {
var statusIcon = model.installed
? '
'
: '
';
var sizeText = model.installed
? model.actual_size_mb.toFixed(1) + ' MB'
: '~' + model.estimated_size_mb + ' MB';
var actionBtn = model.installed
? '
' +
' ' + t('codexlens.deleteModel') +
' '
: '
' +
' ' + t('codexlens.downloadModel') +
' ';
html +=
'
' +
'
' +
'
' +
'
' +
statusIcon +
'' + model.profile + ' ' +
'(' + model.dimensions + ' dims) ' +
'
' +
'
' + model.model_name + '
' +
'
' + model.use_case + '
' +
'
' +
'
' +
'
' + sizeText + '
' +
actionBtn +
'
' +
'
' +
'
';
});
html += '
';
container.innerHTML = html;
if (window.lucide) lucide.createIcons();
} catch (err) {
container.innerHTML =
'' + t('common.error') + ': ' + err.message + '
';
}
}
// Download model
async function downloadModel(profile) {
var modelCard = document.getElementById('model-' + profile);
if (!modelCard) return;
var originalHTML = modelCard.innerHTML;
modelCard.innerHTML =
'' +
'' + t('codexlens.downloading') + ' ' +
'
';
try {
var response = await fetch('/api/codexlens/models/download', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ profile: profile })
});
var result = await response.json();
if (result.success) {
showRefreshToast(t('codexlens.modelDownloaded') + ': ' + profile, 'success');
await loadModelList();
} else {
showRefreshToast(t('codexlens.modelDownloadFailed') + ': ' + result.error, 'error');
modelCard.innerHTML = originalHTML;
if (window.lucide) lucide.createIcons();
}
} catch (err) {
showRefreshToast(t('common.error') + ': ' + err.message, 'error');
modelCard.innerHTML = originalHTML;
if (window.lucide) lucide.createIcons();
}
}
// Delete model
async function deleteModel(profile) {
if (!confirm(t('codexlens.deleteModelConfirm') + ' ' + profile + '?')) {
return;
}
var modelCard = document.getElementById('model-' + profile);
if (!modelCard) return;
var originalHTML = modelCard.innerHTML;
modelCard.innerHTML =
'' +
'' + t('codexlens.deleting') + ' ' +
'
';
try {
var response = await fetch('/api/codexlens/models/delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ profile: profile })
});
var result = await response.json();
if (result.success) {
showRefreshToast(t('codexlens.modelDeleted') + ': ' + profile, 'success');
await loadModelList();
} else {
showRefreshToast(t('codexlens.modelDeleteFailed') + ': ' + result.error, 'error');
modelCard.innerHTML = originalHTML;
if (window.lucide) lucide.createIcons();
}
} catch (err) {
showRefreshToast(t('common.error') + ': ' + err.message, 'error');
modelCard.innerHTML = originalHTML;
if (window.lucide) lucide.createIcons();
}
}
/**
* Clean current workspace index
*/
async function cleanCurrentWorkspaceIndex() {
if (!confirm(t('codexlens.cleanCurrentWorkspaceConfirm'))) {
return;
}
try {
showRefreshToast(t('codexlens.cleaning'), 'info');
// Get current workspace path (projectPath is a global variable from state.js)
var workspacePath = projectPath;
var response = await fetch('/api/codexlens/clean', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path: workspacePath })
});
var result = await response.json();
if (result.success) {
showRefreshToast(t('codexlens.cleanCurrentWorkspaceSuccess'), 'success');
// Refresh status
if (typeof loadCodexLensStatus === 'function') {
await loadCodexLensStatus();
renderToolsSection();
if (window.lucide) lucide.createIcons();
}
} else {
showRefreshToast(t('codexlens.cleanFailed') + ': ' + result.error, 'error');
}
} catch (err) {
showRefreshToast(t('common.error') + ': ' + err.message, 'error');
}
}
/**
* Clean all CodexLens indexes
*/
async function cleanCodexLensIndexes() {
if (!confirm(t('codexlens.cleanConfirm'))) {
return;
}
try {
showRefreshToast(t('codexlens.cleaning'), 'info');
var response = await fetch('/api/codexlens/clean', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ all: true })
});
var result = await response.json();
if (result.success) {
showRefreshToast(t('codexlens.cleanSuccess'), 'success');
// Refresh status
if (typeof loadCodexLensStatus === 'function') {
await loadCodexLensStatus();
renderToolsSection();
if (window.lucide) lucide.createIcons();
}
} else {
showRefreshToast(t('codexlens.cleanFailed') + ': ' + result.error, 'error');
}
} catch (err) {
showRefreshToast(t('common.error') + ': ' + err.message, 'error');
}
}