// 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 cliWrapperEndpoints = []; // CLI封装 endpoints from /api/cli/settings var cliToolConfig = null; // Store loaded CLI config var predefinedModels = {}; // Store predefined models per tool // ========== Cache Key Mapping ========== // 缓存键映射(旧键名 -> 新键名) var CLI_CACHE_KEY_MAP = { toolConfig: 'cli-config', toolStatus: 'cli-status', installations: 'cli-installations', endpointTools: 'cli-endpoint-tools', litellmEndpoints: 'cli-litellm-endpoints', customEndpoints: 'cli-custom-endpoints', wrapperEndpoints: 'cli-wrapper-endpoints' }; // ========== CLI Cache Bridge ========== /** * 获取 CLI 缓存数据 * @param {string} key - 缓存键 * @returns {*} 缓存的数据或 null */ function getCliCachedData(key) { if (!window.cacheManager) return null; var newKey = CLI_CACHE_KEY_MAP[key] || key; return window.cacheManager.get(newKey); } /** * 设置 CLI 缓存数据 * @param {string} key - 缓存键 * @param {*} data - 要缓存的数据 * @param {number} ttl - 缓存 TTL(毫秒),默认 5 分钟 */ function setCliCacheData(key, data, ttl) { if (!window.cacheManager) return; ttl = ttl || 300000; var newKey = CLI_CACHE_KEY_MAP[key] || key; window.cacheManager.set(newKey, data, ttl); } /** * 注册 CLI 相关数据源到预加载服务 * 仅在数据源尚未注册时添加 */ function registerCliDataSources() { if (!window.preloadService) return; var sources = [ { key: 'cli-installations', url: '/api/ccw/installations', priority: false, ttl: 300000 }, { key: 'cli-endpoint-tools', url: '/api/ccw/tools', priority: false, ttl: 300000 }, { key: 'cli-litellm-endpoints', url: '/api/litellm-api/config', priority: false, ttl: 300000 }, { key: 'cli-custom-endpoints', url: '/api/cli/endpoints', priority: false, ttl: 300000 }, { key: 'cli-wrapper-endpoints', url: '/api/cli/settings', priority: false, ttl: 300000 } ]; sources.forEach(function(src) { if (!window.preloadService.sources.has(src.key)) { window.preloadService.register(src.key, function() { return fetch(src.url).then(function(r) { return r.ok ? r.json() : Promise.reject(r); }); }, { isHighPriority: src.priority, ttl: src.ttl } ); } }); } // ========== 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 * Note: Renamed from syncActiveExecutions to avoid conflict with cli-stream-viewer.js */ async function syncActiveExecutionsForManager() { 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 []; } } // ========== CLI Wrapper Endpoints (CLI封装) ========== async function loadCliWrapperEndpoints() { try { var response = await fetch('/api/cli/settings'); if (!response.ok) throw new Error('Failed to load CLI wrapper endpoints'); var data = await response.json(); cliWrapperEndpoints = data.endpoints || []; return cliWrapperEndpoints; } catch (err) { console.error('Failed to load CLI wrapper endpoints:', err); cliWrapperEndpoints = []; return []; } } async function toggleCliWrapperEnabled(endpointId, enabled) { try { await initCsrfToken(); var response = await csrfFetch('/api/cli/settings/' + endpointId, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: enabled }) }); if (!response.ok) throw new Error('Failed to update CLI wrapper endpoint'); var data = await response.json(); if (data.success) { // Update local state var idx = cliWrapperEndpoints.findIndex(function(e) { return e.id === endpointId; }); if (idx >= 0) { cliWrapperEndpoints[idx].enabled = enabled; } showRefreshToast((enabled ? t('cli.enabled') || 'Enabled' : t('cli.disabled') || 'Disabled') + ': ' + endpointId, 'success'); } return data; } catch (err) { showRefreshToast((t('cli.updateFailed') || 'Failed to update') + ': ' + err.message, 'error'); throw err; } } 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() { // 尝试从缓存获取 var cached = getCliCachedData('toolConfig'); if (cached) { cliToolConfig = cached.config || null; predefinedModels = cached.predefinedModels || {}; return cached; } 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 || {}; // 缓存结果 setCliCacheData('toolConfig', data); 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; // Invalidate cache to ensure fresh data on page refresh if (window.cacheManager) { window.cacheManager.invalidate('cli-config'); window.cacheManager.invalidate('cli-tools-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 += ''; } html += ''; return html; }; return '
' + // Status Section '
' + '

Status

' + '
' + '' + ' ' + (isAvailable ? 'Installed' : 'Not Installed') + '' + '' + ' ' + (isEnabled ? 'Enabled' : 'Disabled') + '' + '
' + '
' + // Actions Section '
' + '

Actions

' + '
' + '' + '' + '
' + '
' + // Primary Model Section '
' + '

Primary Model (CLI endpoint calls)

' + '
' + '' + '' + '
' + '
' + // Secondary Model Section '
' + '

Secondary Model (internal tools)

' + '
' + '' + '' + '
' + '
' + // Tags Section - Unified input with inline tags '
' + '

Tags (optional labels)

' + '
' + (config.tags || []).map(function(tag) { return '' + escapeHtml(tag) + ''; }).join('') + '' + '
' + '
' + '' + '' + '' + '' + '' + '' + '
' + '
' + // Environment File Section (only for builtin tools: gemini, qwen) (tool === 'gemini' || tool === 'qwen' ? ( '
' + '

' + t('cli.envFile') + ' ' + t('cli.envFileOptional') + '

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

' + ' ' + t('cli.envFileHint') + '

' + '
' + '
' ) : '') + // Footer '' + '
'; } // ========== File Browser Modal ========== var fileBrowserState = { currentPath: '', showHidden: false, onSelect: null }; function showFileBrowserModal(onSelect) { fileBrowserState.onSelect = onSelect; fileBrowserState.showHidden = false; // Create modal overlay var overlay = document.createElement('div'); overlay.id = 'fileBrowserOverlay'; overlay.className = 'modal-overlay'; overlay.innerHTML = buildFileBrowserModalContent(); document.body.appendChild(overlay); // Load initial directory (home) loadFileBrowserDirectory(''); // Initialize events initFileBrowserEvents(); // Initialize icons if (window.lucide) lucide.createIcons(); } function buildFileBrowserModalContent() { // Detect if Windows var isWindows = navigator.platform.indexOf('Win') > -1; var driveButtons = ''; if (isWindows) { driveButtons = '
' + '' + '' + '' + '
'; } return ''; } async function loadFileBrowserDirectory(path) { var listContainer = document.getElementById('fileBrowserList'); var pathInput = document.getElementById('fileBrowserPathInput'); if (listContainer) { listContainer.innerHTML = '
'; if (window.lucide) lucide.createIcons(); } try { var response = await fetch('/api/dialog/browse', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: path, showHidden: fileBrowserState.showHidden }) }); if (!response.ok) { throw new Error('Failed to load directory'); } var data = await response.json(); fileBrowserState.currentPath = data.currentPath; if (pathInput) { pathInput.value = data.currentPath; } renderFileBrowserItems(data.items); } catch (err) { console.error('Failed to load directory:', err); if (listContainer) { listContainer.innerHTML = '
' + '

' + t('cli.fileBrowserApiError') + '

' + '

' + t('cli.fileBrowserManualHint') + '

' + '
'; } // Enable manual path entry mode - enable select button when path is typed var selectBtn = document.getElementById('fileBrowserSelectBtn'); var pathInput = document.getElementById('fileBrowserPathInput'); if (selectBtn && pathInput) { selectBtn.disabled = false; pathInput.focus(); } } } function renderFileBrowserItems(items) { var listContainer = document.getElementById('fileBrowserList'); if (!listContainer) return; if (!items || items.length === 0) { listContainer.innerHTML = '
Empty directory
'; return; } var html = items.map(function(item) { var icon = item.isDirectory ? 'folder' : 'file'; var itemClass = 'file-browser-item' + (item.isDirectory ? ' is-directory' : ' is-file'); return '
' + '' + '' + escapeHtml(item.name) + '' + '
'; }).join(''); listContainer.innerHTML = html; // Initialize icons if (window.lucide) lucide.createIcons(); // Add click handlers listContainer.querySelectorAll('.file-browser-item').forEach(function(el) { el.onclick = function() { var isDir = el.getAttribute('data-is-dir') === 'true'; var path = el.getAttribute('data-path'); if (isDir) { // Navigate into directory loadFileBrowserDirectory(path); } else { // Select file listContainer.querySelectorAll('.file-browser-item').forEach(function(item) { item.classList.remove('selected'); }); el.classList.add('selected'); // Enable select button var selectBtn = document.getElementById('fileBrowserSelectBtn'); if (selectBtn) { selectBtn.disabled = false; selectBtn.setAttribute('data-selected-path', path); } } }; // Double-click to select file or enter directory el.ondblclick = function() { var isDir = el.getAttribute('data-is-dir') === 'true'; var path = el.getAttribute('data-path'); if (isDir) { loadFileBrowserDirectory(path); } else { // Select and close closeFileBrowserModal(path); } }; }); } function initFileBrowserEvents() { // Close button var closeBtn = document.getElementById('fileBrowserCloseBtn'); if (closeBtn) { closeBtn.onclick = function() { closeFileBrowserModal(null); }; } // Cancel button var cancelBtn = document.getElementById('fileBrowserCancelBtn'); if (cancelBtn) { cancelBtn.onclick = function() { closeFileBrowserModal(null); }; } // Select button var selectBtn = document.getElementById('fileBrowserSelectBtn'); if (selectBtn) { selectBtn.onclick = function() { // First try selected path from list, then fall back to path input var path = selectBtn.getAttribute('data-selected-path'); if (!path) { var pathInput = document.getElementById('fileBrowserPathInput'); if (pathInput && pathInput.value.trim()) { path = pathInput.value.trim(); } } if (path) { closeFileBrowserModal(path); } }; } // Up button var upBtn = document.getElementById('fileBrowserUpBtn'); if (upBtn) { upBtn.onclick = function() { // Get parent path var currentPath = fileBrowserState.currentPath; var parentPath = currentPath.replace(/[/\\][^/\\]+$/, '') || '/'; loadFileBrowserDirectory(parentPath); }; } // Home button var homeBtn = document.getElementById('fileBrowserHomeBtn'); if (homeBtn) { homeBtn.onclick = function() { loadFileBrowserDirectory(''); }; } // Drive buttons (Windows) document.querySelectorAll('.drive-btn').forEach(function(btn) { btn.onclick = function() { var drive = btn.getAttribute('data-drive'); if (drive) { loadFileBrowserDirectory(drive); } }; }); // Path input - allow manual entry var pathInput = document.getElementById('fileBrowserPathInput'); if (pathInput) { pathInput.onkeydown = function(e) { if (e.key === 'Enter') { e.preventDefault(); var path = pathInput.value.trim(); if (path) { loadFileBrowserDirectory(path); } } }; } // Show hidden checkbox var showHiddenCheckbox = document.getElementById('fileBrowserShowHidden'); if (showHiddenCheckbox) { showHiddenCheckbox.checked = true; // Default to show hidden fileBrowserState.showHidden = true; showHiddenCheckbox.onchange = function() { fileBrowserState.showHidden = showHiddenCheckbox.checked; loadFileBrowserDirectory(fileBrowserState.currentPath); }; } // Click outside to close var overlay = document.getElementById('fileBrowserOverlay'); if (overlay) { overlay.onclick = function(e) { if (e.target === overlay) { closeFileBrowserModal(null); } }; } } function closeFileBrowserModal(selectedPath) { var overlay = document.getElementById('fileBrowserOverlay'); if (overlay) { overlay.remove(); } if (fileBrowserState.onSelect && selectedPath) { fileBrowserState.onSelect(selectedPath); } fileBrowserState.onSelect = null; } 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; } // Get envFile value (only for gemini/qwen) var envFileInput = document.getElementById('envFileInput'); var envFile = envFileInput ? envFileInput.value.trim() : ''; try { var updateData = { primaryModel: primaryModel, secondaryModel: secondaryModel, tags: currentTags }; // Only include envFile for gemini/qwen tools if (tool === 'gemini' || tool === 'qwen') { updateData.envFile = envFile || null; } await updateCliToolConfig(tool, updateData); // 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'); } }; } // Environment file browse button (only for gemini/qwen) var envFileBrowseBtn = document.getElementById('envFileBrowseBtn'); if (envFileBrowseBtn) { envFileBrowseBtn.onclick = function() { showFileBrowserModal(function(selectedPath) { var envFileInput = document.getElementById('envFileInput'); if (envFileInput && selectedPath) { envFileInput.value = selectedPath; envFileInput.focus(); } }); }; } // Initialize lucide icons in modal if (window.lucide) lucide.createIcons(); } // ========== Rendering ========== /** * 构建 CLI Manager 骨架屏 * @returns {string} HTML 字符串 */ function buildCliManagerSkeleton() { return '
' + '
' + '

' + (t('title.cliTools') || 'CLI Tools & CCW') + '

' + '
' + '
' + // 左侧 Tools 区域骨架 '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + // 右侧 CCW 区域骨架 '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + // 底部区域骨架 '
' + '
' + '
' + '
' + '
' + '
' + '
'; } /** * 渲染 CLI Manager 实际内容(内部容器结构 + 各子面板) * @param {HTMLElement} container - 主容器元素 */ function renderCliManagerContent(container) { container.innerHTML = '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
'; // 渲染子面板 renderToolsSection(); renderCcwSection(); renderLanguageSettingsSection(); renderCliSettingsSection(); renderCcwEndpointToolsSection(); // 初始化存储管理器卡片 if (typeof initStorageManager === 'function') { initStorageManager(); } // 初始化 Lucide 图标 if (window.lucide) lucide.createIcons(); } async function renderCliManager() { var container = document.getElementById('mainContent'); if (!container) return; // 隐藏统计网格和搜索框 var statsGrid = document.getElementById('statsGrid'); var searchInput = document.getElementById('searchInput'); if (statsGrid) statsGrid.style.display = 'none'; if (searchInput) searchInput.parentElement.style.display = 'none'; // 注册数据源(如果尚未注册) registerCliDataSources(); // 1. 立即显示骨架屏 container.innerHTML = buildCliManagerSkeleton(); if (window.lucide) lucide.createIcons(); // 2. 尝试从缓存渲染(快速展示) var cachedConfig = getCliCachedData('toolConfig'); var cachedStatus = getCliCachedData('toolStatus'); var hasCachedData = cachedConfig && cachedStatus; if (hasCachedData) { // 应用缓存数据 cliToolConfig = cachedConfig.config; predefinedModels = cachedConfig.predefinedModels || {}; // 立即渲染缓存数据 renderCliManagerContent(container); console.log('[CLI Manager] Rendered from cache'); } // 3. 后台加载最新数据 try { await Promise.all([ loadCliToolsConfig(), loadCliToolStatus(), loadCodexLensStatus(), loadCcwInstallations(), loadCcwEndpointTools(), loadLitellmApiEndpoints(), loadCliCustomEndpoints(), loadCliWrapperEndpoints() ]); // 4. 用最新数据更新 UI(如果之前未渲染或数据有变化) renderCliManagerContent(container); console.log('[CLI Manager] Rendered with fresh data'); } catch (err) { console.error('[CLI Manager] Failed to load data:', err); // 如果没有缓存数据且加载失败,显示错误提示 if (!hasCachedData) { container.innerHTML = '
' + '' + '

' + (t('common.loadFailed') || 'Failed to load data') + '

' + '' + '
'; if (window.lucide) lucide.createIcons(); } } // 同步活动执行 syncActiveExecutionsForManager(); } // ========== 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 ''; } // 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 += ''; } }); 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 '
' + '
' + '' + '
' + '
' + tool.charAt(0).toUpperCase() + tool.slice(1) + '' + '
' + '
' + toolDescriptions[tool] + '
' + tagsHtml + '
' + '
' + '
' + (isAvailable ? ' ' + t('cli.ready') + '' : ' ' + t('cli.notInstalled') + '') + '
' + '
'; }).join(''); // CodexLens and Semantic Search removed from this list // They are managed in the dedicated CodexLens Manager page (left menu) // 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 '
' + '
' + '' + '
' + '
' + endpoint.id + ' API
' + '
' + endpoint.model + ' (' + providerName + ')
' + '
' + '
' + '
' + (isSynced ? '' : '') + '
' + '
'; }).join(''); apiEndpointsHtml = '
' + '
' + '

' + ' ' + (t('cli.apiEndpoints') || 'API Endpoints') + '

' + '' + litellmApiEndpoints.length + ' ' + (t('cli.configured') || 'configured') + '' + '
' + '
' + endpointItems + '
' + '
'; } // CLI Wrapper (CLI封装) section var cliWrapperHtml = ''; if (cliWrapperEndpoints.length > 0) { var wrapperItems = cliWrapperEndpoints.map(function(endpoint) { var isEnabled = endpoint.enabled !== false; var desc = endpoint.description || (t('cli.customClaudeSettings') || 'Custom Claude CLI settings'); // Show command hint with name for easy copying var commandHint = 'ccw cli --tool ' + endpoint.name; return '
' + '
' + '' + '
' + '
' + escapeHtml(endpoint.name) + ' ' + (t('cli.wrapper') || 'Wrapper') + '
' + '
' + escapeHtml(desc) + '
' + '
' + escapeHtml(commandHint) + '
' + '
' + '
' + '
' + '' + '
' + '
'; }).join(''); var enabledCount = cliWrapperEndpoints.filter(function(e) { return e.enabled !== false; }).length; cliWrapperHtml = '
' + '
' + '

' + ' ' + (t('cli.cliWrapper') || 'CLI Wrapper') + '

' + '' + enabledCount + '/' + cliWrapperEndpoints.length + ' ' + (t('cli.enabled') || 'enabled') + '' + '
' + '
' + wrapperItems + '
' + '
'; } container.innerHTML = '
' + '
' + '

' + t('cli.tools') + '

' + '' + available + '/' + tools.length + ' ' + t('cli.available') + '' + '
' + '' + '
' + '
' + toolsHtml + '
' + apiEndpointsHtml + cliWrapperHtml; if (window.lucide) lucide.createIcons(); } /** * Navigate to API Settings page and open the CLI wrapper endpoint for editing */ function navigateToApiSettings(endpointId) { // Store the endpoint ID to edit after navigation window.pendingCliWrapperEdit = endpointId; var navItem = document.querySelector('.nav-item[data-view="api-settings"]'); if (navItem) { navItem.click(); } else { // Fallback: try to render directly if (typeof renderApiSettings === 'function') { currentView = 'api-settings'; renderApiSettings(); } else { showRefreshToast(t('common.error') + ': API Settings not available', 'error'); } } } // ========== 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') + '

' + '' + '
'; } 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 += '
' + '
' + '
' + '' + '
' + '
' + '
' + '' + inst.installation_mode + '' + 'v' + version + '' + '
' + '
' + escapeHtml(inst.installation_path) + '
' + '
' + ' ' + installDate + '' + ' ' + (inst.files_count || 0) + ' files' + '
' + '
' + '
' + '
' + '' + '' + '
' + '
'; } installationsHtml += '
'; } container.innerHTML = '
' + '
' + '

' + t('ccw.install') + '

' + '' + ccwInstallations.length + ' ' + (ccwInstallations.length !== 1 ? t('ccw.installationsPlural') : t('ccw.installations')) + '' + '
' + '
' + '' + '' + '
' + '
' + 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 = '
' + '
' + '

' + t('lang.settings') + '

' + '
' + '
' + '
' + // Chinese Response - Claude '
' + '' + '
' + '' + '' + (chineseResponseEnabled ? t('lang.enabled') : t('lang.disabled')) + '' + '
' + '

' + t('lang.chineseDescClaude') + '

' + '
' + // Chinese Response - Codex '
' + '' + '
' + '' + '' + (codexChineseResponseEnabled ? t('lang.enabled') : t('lang.disabled')) + '' + '
' + '

' + t('lang.chineseDescCodex') + '

' + '
' + // Windows Platform '
' + '' + '
' + '' + '' + (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.settings') + '

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

' + t('cli.promptFormatDesc') + '

' + '
' + '
' + '' + '
' + 'SQLite' + '
' + '

' + t('cli.storageBackendDesc') + '

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

' + t('cli.smartContextDesc') + '

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

' + t('cli.nativeResumeDesc') + '

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

' + t('cli.recursiveQueryDesc') + '

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

' + 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') + '

' + '' + '
'; } else { toolsHtml = '
' + ccwEndpointTools.map(function(t, idx) { var name = t && t.name ? String(t.name) : 'unknown'; var desc = t && t.description ? String(t.description) : ''; var requiredCount = (t && t.parameters && Array.isArray(t.parameters.required)) ? t.parameters.required.length : 0; var propsCount = (t && t.parameters && t.parameters.properties) ? Object.keys(t.parameters.properties).length : 0; var shortDesc = desc.length > 60 ? desc.substring(0, 60) + '...' : desc; return '
' + '
' + '' + '' + escapeHtml(name) + '' + '
' + '
' + escapeHtml(shortDesc || 'No description') + '
' + '
' + '' + ' ' + propsCount + '' + (requiredCount > 0 ? '' + requiredCount + ' required' : '') + '
' + '
'; }).join('') + '
'; } container.innerHTML = '
' + '
' + '

' + t('ccw.endpointTools') + '

' + '' + count + ' ' + (count !== 1 ? t('ccw.tools') : t('ccw.tool')) + '' + '
' + '' + '
' + 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 = '
' + '

Parameters

' + '
'; for (var i = 0; i < propKeys.length; i++) { var key = propKeys[i]; var prop = properties[key]; var isRequired = required.indexOf(key) !== -1; var propType = prop.type || 'any'; var propDesc = prop.description || ''; var propDefault = prop.default !== undefined ? JSON.stringify(prop.default) : null; var propEnum = prop.enum ? prop.enum.join(', ') : null; paramsHtml += '
' + '
' + '' + escapeHtml(key) + '' + '' + escapeHtml(propType) + '' + (isRequired ? 'required' : 'optional') + '
' + (propDesc ? '
' + escapeHtml(propDesc) + '
' : '') + (propDefault ? '
Default: ' + escapeHtml(propDefault) + '
' : '') + (propEnum ? '
Options: ' + escapeHtml(propEnum) + '
' : '') + '
'; } 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 = '
' + '
' + '
' + '
' + '

' + escapeHtml(name) + '

' + 'endpoint tool' + '
' + '
' + '
' + escapeHtml(desc) + '
' + paramsHtml + '
' + '

Usage Example

' + '
' + '' + escapeHtml(usageExample) + '' + '' + '
' + '
' + '
'; 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 = '

CCW Installations

' + '
' + '' + '' + '
' + '
'; if (ccwInstallations.length === 0) { html += '
' + '' + '

No installations found

' + '
'; } else { // Carousel container html += ''; // Dots indicator (show only if more than 1 installation) if (ccwInstallations.length > 1) { 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 = '

Quick Execute

' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
'; 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.

' + '
' + '
' + '
' + '' + '
' + '
' + '
' + '' + '' + '
' + '
'; 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.

' + '
' + '
' + '
' + '' + '
' + '
' + '
' + '' + '' + '
' + '
'; 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 '
' + // Status Section '
' + '

' + t('codexlens.status') + '

' + '
' + '' + ' ' + (isInstalled ? t('codexlens.installed') : t('codexlens.notInstalled')) + '' + '' + ' ' + indexCount + ' ' + t('codexlens.indexes') + '' + '
' + (currentWorkspace !== 'None' ? '
' + '

' + t('codexlens.currentWorkspace') + ':

' + '

' + escapeHtml(currentWorkspace) + '

' + '
' : '') + '
' + // Index Storage Path Section '
' + '

' + t('codexlens.indexStoragePath') + ' (' + t('codexlens.whereIndexesStored') + ')

' + '
' + '
' + '

' + t('codexlens.currentPath') + ':

' + '

' + escapeHtml(indexDir) + '

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

' + ' ' + t('codexlens.pathInfo') + '

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

' + t('codexlens.migrationRequired') + '

' + '

' + t('codexlens.migrationWarning') + '

' + '
' + '
' + '
' + '
' + '
' + // Actions Section '
' + '

' + t('codexlens.actions') + '

' + '
' + (isInstalled ? '' + '' + '' + '' : '') + '
' + '
' + // Semantic Dependencies Section (isInstalled ? '
' + '

' + t('codexlens.semanticDeps') + '

' + '
' + '
' + t('codexlens.checkingDeps') + '
' + '
' + '
' : '') + // Model Management Section (isInstalled ? '
' + '

' + t('codexlens.modelManagement') + '

' + '
' + '
' + t('codexlens.loadingModels') + '
' + '
' + '
' : '') + // Test Search Section (isInstalled ? '
' + '

' + t('codexlens.testSearch') + ' (' + t('codexlens.testFunctionality') + ')

' + '
' + '
' + '' + '' + '
' + '
' + '' + '
' + '
' + '' + '
' + '' + '
' + '
' : '') + // Footer '' + '
'; } 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') + '' + '
' + '' + '
'; } 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 csrfFetch('/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 ? '' : ''; 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'); } }