mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-09 02:24:11 +08:00
- Phase 3: Added Mermaid diagram generation for system architecture, function modules, algorithms, class diagrams, sequence diagrams, and error flows. - Phase 4: Assembled analysis and diagrams into a structured CPCC-compliant document with section templates and figure numbering. - Phase 5: Developed compliance review process with iterative refinement based on analysis findings and user feedback. - Added CPCC compliance requirements and quality standards for project analysis reports. - Established a comprehensive project analysis skill with detailed execution flow and report types. - Enhanced error handling and recovery mechanisms throughout the analysis phases.
1403 lines
55 KiB
JavaScript
1403 lines
55 KiB
JavaScript
// CLI Status Component
|
|
// Displays CLI tool availability status and allows setting default tool
|
|
|
|
// ========== CLI State ==========
|
|
let cliToolStatus = { gemini: {}, qwen: {}, codex: {}, claude: {} };
|
|
let codexLensStatus = { ready: false };
|
|
let semanticStatus = { available: false };
|
|
let ccwInstallStatus = { installed: true, workflowsInstalled: true, missingFiles: [], installPath: '' };
|
|
let defaultCliTool = 'gemini';
|
|
let promptConcatFormat = localStorage.getItem('ccw-prompt-format') || 'plain'; // plain, yaml, json
|
|
let cliToolsConfig = {}; // CLI tools enable/disable config
|
|
let apiEndpoints = []; // API endpoints from LiteLLM config
|
|
|
|
// Smart Context settings
|
|
let smartContextEnabled = localStorage.getItem('ccw-smart-context') === 'true';
|
|
let smartContextMaxFiles = parseInt(localStorage.getItem('ccw-smart-context-max-files') || '10', 10);
|
|
|
|
// Native Resume settings
|
|
let nativeResumeEnabled = localStorage.getItem('ccw-native-resume') !== 'false'; // default true
|
|
|
|
// Recursive Query settings (for hierarchical storage aggregation)
|
|
let recursiveQueryEnabled = localStorage.getItem('ccw-recursive-query') !== 'false'; // default true
|
|
|
|
// Code Index MCP provider (codexlens, ace, or none)
|
|
let codeIndexMcpProvider = 'codexlens';
|
|
|
|
// ========== Helper Functions ==========
|
|
/**
|
|
* Get the context-tools filename based on provider
|
|
*/
|
|
function getContextToolsFileName(provider) {
|
|
switch (provider) {
|
|
case 'ace':
|
|
return 'context-tools-ace.md';
|
|
case 'none':
|
|
return 'context-tools-none.md';
|
|
default:
|
|
return 'context-tools.md';
|
|
}
|
|
}
|
|
|
|
// ========== Initialization ==========
|
|
function initCliStatus() {
|
|
// Load all statuses in one call using aggregated endpoint
|
|
loadAllStatuses();
|
|
}
|
|
|
|
// ========== Data Loading ==========
|
|
/**
|
|
* Load all statuses using aggregated endpoint (single API call)
|
|
*/
|
|
async function loadAllStatuses() {
|
|
try {
|
|
const response = await fetch('/api/status/all');
|
|
if (!response.ok) throw new Error('Failed to load status');
|
|
const data = await response.json();
|
|
|
|
// Update all status data
|
|
cliToolStatus = data.cli || { gemini: {}, qwen: {}, codex: {}, claude: {} };
|
|
codexLensStatus = data.codexLens || { ready: false };
|
|
semanticStatus = data.semantic || { available: false };
|
|
ccwInstallStatus = data.ccwInstall || { installed: true, workflowsInstalled: true, missingFiles: [], installPath: '' };
|
|
|
|
// Load CLI tools config and API endpoints
|
|
await Promise.all([
|
|
loadCliToolsConfig(),
|
|
loadApiEndpoints()
|
|
]);
|
|
|
|
// Update badges
|
|
updateCliBadge();
|
|
updateCodexLensBadge();
|
|
updateCcwInstallBadge();
|
|
|
|
return data;
|
|
} catch (err) {
|
|
console.error('Failed to load aggregated status:', err);
|
|
// Fallback to individual calls if aggregated endpoint fails
|
|
return await loadAllStatusesFallback();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fallback: Load statuses individually if aggregated endpoint fails
|
|
*/
|
|
async function loadAllStatusesFallback() {
|
|
console.warn('[CLI Status] Using fallback individual API calls');
|
|
await Promise.all([
|
|
loadCliToolStatus(),
|
|
loadCodexLensStatus()
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Legacy: Load CLI tool status individually
|
|
*/
|
|
async function loadCliToolStatus() {
|
|
try {
|
|
const response = await fetch('/api/cli/status');
|
|
if (!response.ok) throw new Error('Failed to load CLI status');
|
|
const data = await response.json();
|
|
cliToolStatus = data;
|
|
|
|
// Update badge
|
|
updateCliBadge();
|
|
|
|
return data;
|
|
} catch (err) {
|
|
console.error('Failed to load CLI status:', err);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Legacy: Load CodexLens status individually
|
|
*/
|
|
async function loadCodexLensStatus() {
|
|
try {
|
|
const response = await fetch('/api/codexlens/status');
|
|
if (!response.ok) throw new Error('Failed to load CodexLens status');
|
|
const data = await response.json();
|
|
codexLensStatus = data;
|
|
|
|
// Expose to window for other modules (e.g., codexlens-manager.js)
|
|
if (!window.cliToolsStatus) {
|
|
window.cliToolsStatus = {};
|
|
}
|
|
window.cliToolsStatus.codexlens = {
|
|
installed: data.ready || false,
|
|
version: data.version || null,
|
|
installedModels: [] // Will be populated by loadSemanticStatus
|
|
};
|
|
|
|
// Update CodexLens badge
|
|
updateCodexLensBadge();
|
|
|
|
// If CodexLens is ready, also check semantic status and models
|
|
if (data.ready) {
|
|
await loadSemanticStatus();
|
|
await loadInstalledModels();
|
|
}
|
|
|
|
return data;
|
|
} catch (err) {
|
|
console.error('Failed to load CodexLens status:', err);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load CodexLens dashboard data using aggregated endpoint (single API call)
|
|
* This is optimized for the CodexLens Manager page initialization
|
|
* @returns {Promise<object|null>} Dashboard init data or null on error
|
|
*/
|
|
async function loadCodexLensDashboardInit() {
|
|
try {
|
|
const response = await fetch('/api/codexlens/dashboard-init');
|
|
if (!response.ok) throw new Error('Failed to load CodexLens dashboard init');
|
|
const data = await response.json();
|
|
|
|
// Update status variables from aggregated response
|
|
codexLensStatus = data.status || { ready: false };
|
|
semanticStatus = data.semantic || { available: false };
|
|
|
|
// Expose to window for other modules
|
|
if (!window.cliToolsStatus) {
|
|
window.cliToolsStatus = {};
|
|
}
|
|
window.cliToolsStatus.codexlens = {
|
|
installed: data.installed || false,
|
|
version: data.status?.version || null,
|
|
installedModels: [],
|
|
config: data.config || {},
|
|
semantic: data.semantic || {}
|
|
};
|
|
|
|
// Store config globally for easy access
|
|
window.codexLensConfig = data.config || {};
|
|
window.codexLensStatusData = data.statusData || {};
|
|
|
|
// Update badges
|
|
updateCodexLensBadge();
|
|
|
|
console.log('[CLI Status] CodexLens dashboard init loaded:', {
|
|
installed: data.installed,
|
|
version: data.status?.version,
|
|
semanticAvailable: data.semantic?.available
|
|
});
|
|
|
|
return data;
|
|
} catch (err) {
|
|
console.error('Failed to load CodexLens dashboard init:', err);
|
|
// Fallback to individual calls
|
|
return await loadCodexLensStatus();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Legacy: Load semantic status individually
|
|
*/
|
|
async function loadSemanticStatus() {
|
|
try {
|
|
const response = await fetch('/api/codexlens/semantic/status');
|
|
if (!response.ok) throw new Error('Failed to load semantic status');
|
|
const data = await response.json();
|
|
semanticStatus = data;
|
|
return data;
|
|
} catch (err) {
|
|
console.error('Failed to load semantic status:', err);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load installed embedding models
|
|
*/
|
|
async function loadInstalledModels() {
|
|
try {
|
|
const response = await fetch('/api/codexlens/models');
|
|
if (!response.ok) throw new Error('Failed to load models');
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.result && data.result.models) {
|
|
// Filter to only installed models
|
|
const installedModels = data.result.models
|
|
.filter(m => m.installed)
|
|
.map(m => m.profile);
|
|
|
|
// Update window.cliToolsStatus
|
|
if (window.cliToolsStatus && window.cliToolsStatus.codexlens) {
|
|
window.cliToolsStatus.codexlens.installedModels = installedModels;
|
|
window.cliToolsStatus.codexlens.allModels = data.result.models;
|
|
}
|
|
|
|
console.log('[CLI Status] Installed models:', installedModels);
|
|
return installedModels;
|
|
}
|
|
return [];
|
|
} catch (err) {
|
|
console.error('Failed to load installed models:', err);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load CLI tools config from .claude/cli-tools.json (project or global fallback)
|
|
*/
|
|
async function loadCliToolsConfig() {
|
|
try {
|
|
const response = await fetch('/api/cli/tools-config');
|
|
if (!response.ok) return null;
|
|
const data = await response.json();
|
|
// Store full config and extract tools for backward compatibility
|
|
cliToolsConfig = data.tools || {};
|
|
window.claudeCliToolsConfig = data; // Full config available globally
|
|
|
|
// Load default tool from config
|
|
if (data.defaultTool) {
|
|
defaultCliTool = data.defaultTool;
|
|
}
|
|
|
|
// Load Code Index MCP provider from config
|
|
if (data.settings?.codeIndexMcp) {
|
|
codeIndexMcpProvider = data.settings.codeIndexMcp;
|
|
}
|
|
|
|
console.log('[CLI Config] Loaded from:', data._configInfo?.source || 'unknown', '| Default:', data.defaultTool, '| CodeIndexMCP:', codeIndexMcpProvider);
|
|
return data;
|
|
} catch (err) {
|
|
console.error('Failed to load CLI tools config:', err);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update CLI tool enabled status
|
|
*/
|
|
async function updateCliToolEnabled(tool, enabled) {
|
|
try {
|
|
const response = await fetch('/api/cli/tools-config/' + tool, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ enabled: enabled })
|
|
});
|
|
if (!response.ok) throw new Error('Failed to update');
|
|
showRefreshToast(tool + (enabled ? ' enabled' : ' disabled'), 'success');
|
|
return await response.json();
|
|
} catch (err) {
|
|
console.error('Failed to update CLI tool:', err);
|
|
showRefreshToast('Failed to update ' + tool, 'error');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load API endpoints from LiteLLM config
|
|
*/
|
|
async function loadApiEndpoints() {
|
|
try {
|
|
const response = await fetch('/api/litellm-api/endpoints');
|
|
if (!response.ok) return [];
|
|
const data = await response.json();
|
|
apiEndpoints = data.endpoints || [];
|
|
return apiEndpoints;
|
|
} catch (err) {
|
|
console.error('Failed to load API endpoints:', err);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// ========== Badge Update ==========
|
|
function updateCliBadge() {
|
|
const badge = document.getElementById('badgeCliTools');
|
|
if (badge) {
|
|
const available = Object.values(cliToolStatus).filter(t => t.available).length;
|
|
const total = Object.keys(cliToolStatus).length;
|
|
badge.textContent = `${available}/${total}`;
|
|
badge.classList.toggle('text-success', available === total);
|
|
badge.classList.toggle('text-warning', available > 0 && available < total);
|
|
badge.classList.toggle('text-destructive', available === 0);
|
|
}
|
|
}
|
|
|
|
function updateCodexLensBadge() {
|
|
const badge = document.getElementById('badgeCodexLens');
|
|
if (badge) {
|
|
badge.textContent = codexLensStatus.ready ? 'Ready' : 'Not Installed';
|
|
badge.classList.toggle('text-success', codexLensStatus.ready);
|
|
badge.classList.toggle('text-muted-foreground', !codexLensStatus.ready);
|
|
}
|
|
}
|
|
|
|
function updateCcwInstallBadge() {
|
|
const badge = document.getElementById('badgeCcwInstall');
|
|
if (badge) {
|
|
if (ccwInstallStatus.installed) {
|
|
badge.textContent = t('status.installed');
|
|
badge.classList.add('text-success');
|
|
badge.classList.remove('text-warning', 'text-destructive');
|
|
} else if (ccwInstallStatus.workflowsInstalled === false) {
|
|
badge.textContent = t('status.incomplete');
|
|
badge.classList.add('text-warning');
|
|
badge.classList.remove('text-success', 'text-destructive');
|
|
} else {
|
|
badge.textContent = t('status.notInstalled');
|
|
badge.classList.add('text-destructive');
|
|
badge.classList.remove('text-success', 'text-warning');
|
|
}
|
|
}
|
|
}
|
|
|
|
// ========== Rendering ==========
|
|
function renderCliStatus() {
|
|
const container = document.getElementById('cli-status-panel');
|
|
if (!container) return;
|
|
|
|
const toolDescriptions = {
|
|
gemini: 'Google AI for code analysis',
|
|
qwen: 'Alibaba AI assistant',
|
|
codex: 'OpenAI code generation',
|
|
claude: 'Anthropic AI assistant'
|
|
};
|
|
|
|
const toolIcons = {
|
|
gemini: 'sparkle',
|
|
qwen: 'bot',
|
|
codex: 'code-2',
|
|
claude: 'brain'
|
|
};
|
|
|
|
const tools = ['gemini', 'qwen', 'codex', 'claude'];
|
|
|
|
const toolsHtml = tools.map(tool => {
|
|
const status = cliToolStatus[tool] || {};
|
|
const isAvailable = status.available;
|
|
const isDefault = defaultCliTool === tool;
|
|
const config = cliToolsConfig[tool] || { enabled: true };
|
|
const isEnabled = config.enabled !== false;
|
|
const canSetDefault = isAvailable && isEnabled && !isDefault;
|
|
|
|
return `
|
|
<div class="cli-tool-card tool-${tool} ${isAvailable ? 'available' : 'unavailable'} ${!isEnabled ? 'disabled' : ''}">
|
|
<div class="cli-tool-header">
|
|
<span class="cli-tool-status ${isAvailable && isEnabled ? 'status-available' : 'status-unavailable'}"></span>
|
|
<span class="cli-tool-name">${tool.charAt(0).toUpperCase() + tool.slice(1)}</span>
|
|
${isDefault ? '<span class="cli-tool-badge">Default</span>' : ''}
|
|
${!isEnabled && isAvailable ? '<span class="cli-tool-badge-disabled">Disabled</span>' : ''}
|
|
</div>
|
|
<div class="cli-tool-desc text-xs text-muted-foreground mt-1">
|
|
${toolDescriptions[tool]}
|
|
</div>
|
|
<div class="cli-tool-info mt-2 flex items-center justify-between">
|
|
<div>
|
|
${isAvailable
|
|
? (isEnabled
|
|
? `<span class="text-success flex items-center gap-1"><i data-lucide="check-circle" class="w-3 h-3"></i> Ready</span>`
|
|
: `<span class="text-warning flex items-center gap-1"><i data-lucide="pause-circle" class="w-3 h-3"></i> Disabled</span>`)
|
|
: `<span class="text-muted-foreground flex items-center gap-1"><i data-lucide="circle-dashed" class="w-3 h-3"></i> Not Installed</span>`
|
|
}
|
|
</div>
|
|
</div>
|
|
<div class="cli-tool-actions mt-3 flex gap-2">
|
|
${isAvailable ? (isEnabled
|
|
? `<button class="btn-sm btn-outline-warning flex items-center gap-1" onclick="toggleCliTool('${tool}', false)">
|
|
<i data-lucide="pause" class="w-3 h-3"></i> Disable
|
|
</button>`
|
|
: `<button class="btn-sm btn-outline-success flex items-center gap-1" onclick="toggleCliTool('${tool}', true)">
|
|
<i data-lucide="play" class="w-3 h-3"></i> Enable
|
|
</button>`
|
|
) : ''}
|
|
${canSetDefault
|
|
? `<button class="btn-sm btn-outline flex items-center gap-1" onclick="setDefaultCliTool('${tool}')">
|
|
<i data-lucide="star" class="w-3 h-3"></i> Set Default
|
|
</button>`
|
|
: ''
|
|
}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
// CodexLens card with semantic search info
|
|
const codexLensHtml = `
|
|
<div class="cli-tool-card tool-codexlens ${codexLensStatus.ready ? 'available' : 'unavailable'}">
|
|
<div class="cli-tool-header">
|
|
<span class="cli-tool-status ${codexLensStatus.ready ? 'status-available' : 'status-unavailable'}"></span>
|
|
<span class="cli-tool-name">CodexLens</span>
|
|
<span class="badge px-1.5 py-0.5 text-xs rounded bg-muted text-muted-foreground">Index</span>
|
|
</div>
|
|
<div class="cli-tool-desc text-xs text-muted-foreground mt-1">
|
|
${codexLensStatus.ready ? 'Code indexing & FTS search' : 'Full-text code search engine'}
|
|
</div>
|
|
<div class="cli-tool-info mt-2">
|
|
${codexLensStatus.ready
|
|
? `<span class="text-success flex items-center gap-1"><i data-lucide="check-circle" class="w-3 h-3"></i> v${codexLensStatus.version || 'installed'}</span>`
|
|
: `<span class="text-muted-foreground flex items-center gap-1"><i data-lucide="circle-dashed" class="w-3 h-3"></i> Not Installed</span>`
|
|
}
|
|
</div>
|
|
<div class="cli-tool-actions flex gap-2 mt-3">
|
|
${!codexLensStatus.ready
|
|
? `<button class="btn-sm btn-primary flex items-center gap-1" onclick="installCodexLens()">
|
|
<i data-lucide="download" class="w-3 h-3"></i> Install
|
|
</button>`
|
|
: `<button class="btn-sm btn-outline flex items-center gap-1" onclick="initCodexLensIndex()">
|
|
<i data-lucide="database" class="w-3 h-3"></i> Init Index
|
|
</button>
|
|
<button class="btn-sm btn-outline flex items-center gap-1" onclick="uninstallCodexLens()">
|
|
<i data-lucide="trash-2" class="w-3 h-3"></i> Uninstall
|
|
</button>`
|
|
}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Semantic Search card (only show if CodexLens is installed)
|
|
const semanticHtml = codexLensStatus.ready ? `
|
|
<div class="cli-tool-card tool-semantic ${semanticStatus.available ? 'available' : 'unavailable'}">
|
|
<div class="cli-tool-header">
|
|
<span class="cli-tool-status ${semanticStatus.available ? 'status-available' : 'status-unavailable'}"></span>
|
|
<span class="cli-tool-name">Semantic Search</span>
|
|
<span class="badge px-1.5 py-0.5 text-xs rounded ${semanticStatus.available ? 'bg-primary/20 text-primary' : 'bg-muted text-muted-foreground'}">AI</span>
|
|
</div>
|
|
<div class="cli-tool-desc text-xs text-muted-foreground mt-1">
|
|
${semanticStatus.available ? 'AI-powered code understanding' : 'Natural language code search'}
|
|
</div>
|
|
<div class="cli-tool-info mt-2">
|
|
${semanticStatus.available
|
|
? `<span class="text-success flex items-center gap-1"><i data-lucide="sparkles" class="w-3 h-3"></i> ${semanticStatus.backend || 'Ready'}</span>`
|
|
: `<span class="text-muted-foreground flex items-center gap-1"><i data-lucide="circle-dashed" class="w-3 h-3"></i> Not Installed</span>`
|
|
}
|
|
</div>
|
|
<div class="cli-tool-actions flex flex-col gap-2 mt-3">
|
|
${!semanticStatus.available ? `
|
|
<button class="btn-sm btn-primary w-full flex items-center justify-center gap-1" onclick="openSemanticInstallWizard()">
|
|
<i data-lucide="brain" class="w-3 h-3"></i> Install AI Model
|
|
</button>
|
|
<div class="flex items-center gap-1 text-xs text-muted-foreground mt-1">
|
|
<i data-lucide="hard-drive" class="w-3 h-3"></i>
|
|
<span>~130MB</span>
|
|
</div>
|
|
` : `
|
|
<div class="flex items-center gap-1 text-xs text-muted-foreground">
|
|
<i data-lucide="cpu" class="w-3 h-3"></i>
|
|
<span>bge-small-en-v1.5</span>
|
|
</div>
|
|
`}
|
|
</div>
|
|
</div>
|
|
` : '';
|
|
|
|
// CCW Installation Status card (show warning if not fully installed)
|
|
const ccwInstallHtml = !ccwInstallStatus.installed ? `
|
|
<div class="cli-tool-card tool-ccw-install unavailable" style="border: 1px solid var(--warning); background: rgba(var(--warning-rgb), 0.05);">
|
|
<div class="cli-tool-header">
|
|
<span class="cli-tool-status status-unavailable" style="background: var(--warning);"></span>
|
|
<span class="cli-tool-name">${t('status.ccwInstall')}</span>
|
|
<span class="badge px-1.5 py-0.5 text-xs rounded bg-warning/20 text-warning">${t('status.required')}</span>
|
|
</div>
|
|
<div class="cli-tool-desc text-xs text-muted-foreground mt-1">
|
|
${t('status.ccwInstallDesc')}
|
|
</div>
|
|
<div class="cli-tool-info mt-2">
|
|
<span class="text-warning flex items-center gap-1">
|
|
<i data-lucide="alert-triangle" class="w-3 h-3"></i>
|
|
${ccwInstallStatus.missingFiles.length} ${t('status.filesMissing')}
|
|
</span>
|
|
</div>
|
|
<div class="cli-tool-actions flex flex-col gap-2 mt-3">
|
|
<div class="text-xs text-muted-foreground">
|
|
<p class="mb-1">${t('status.missingFiles')}:</p>
|
|
<ul class="list-disc list-inside text-xs opacity-70">
|
|
${ccwInstallStatus.missingFiles.slice(0, 3).map(f => `<li>${f}</li>`).join('')}
|
|
${ccwInstallStatus.missingFiles.length > 3 ? `<li>+${ccwInstallStatus.missingFiles.length - 3} more...</li>` : ''}
|
|
</ul>
|
|
</div>
|
|
<div class="bg-muted/50 rounded p-2 mt-2">
|
|
<p class="text-xs font-medium mb-1">${t('status.runToFix')}:</p>
|
|
<code class="text-xs bg-background px-2 py-1 rounded block">ccw install</code>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
` : '';
|
|
|
|
// API Endpoints section
|
|
const apiEndpointsHtml = apiEndpoints.length > 0 ? `
|
|
<div class="cli-api-endpoints-section" style="margin-top: 1.5rem;">
|
|
<div class="cli-section-header" style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1rem;">
|
|
<h4 style="display: flex; align-items: center; gap: 0.5rem; font-weight: 600; margin: 0;">
|
|
<i data-lucide="link" class="w-4 h-4"></i> API Endpoints
|
|
</h4>
|
|
<span class="badge" style="padding: 0.125rem 0.5rem; font-size: 0.75rem; border-radius: 0.25rem; background: var(--muted); color: var(--muted-foreground);">${apiEndpoints.length}</span>
|
|
</div>
|
|
<div class="cli-endpoints-list" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 0.75rem;">
|
|
${apiEndpoints.map(ep => `
|
|
<div class="cli-endpoint-card ${ep.enabled ? 'available' : 'unavailable'}" style="padding: 0.75rem; border: 1px solid var(--border); border-radius: 0.5rem; background: var(--card);">
|
|
<div class="cli-endpoint-header" style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
|
|
<span class="cli-tool-status ${ep.enabled ? 'status-available' : 'status-unavailable'}" style="width: 8px; height: 8px; border-radius: 50%; background: ${ep.enabled ? 'var(--success)' : 'var(--muted-foreground)'}; flex-shrink: 0;"></span>
|
|
<span class="cli-endpoint-id" style="font-weight: 500; font-size: 0.875rem;">${ep.id}</span>
|
|
</div>
|
|
<div class="cli-endpoint-info" style="margin-top: 0.25rem;">
|
|
<span class="text-xs text-muted-foreground" style="font-size: 0.75rem; color: var(--muted-foreground);">${ep.model}</span>
|
|
</div>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
</div>
|
|
` : '';
|
|
|
|
// Config source info
|
|
const configInfo = window.claudeCliToolsConfig?._configInfo || {};
|
|
const configSourceLabel = configInfo.source === 'project' ? 'Project' : configInfo.source === 'global' ? 'Global' : 'Default';
|
|
const configSourceClass = configInfo.source === 'project' ? 'text-success' : configInfo.source === 'global' ? 'text-primary' : 'text-muted-foreground';
|
|
|
|
// CLI Settings section
|
|
const settingsHtml = `
|
|
<div class="cli-settings-section">
|
|
<div class="cli-settings-header">
|
|
<h4><i data-lucide="settings" class="w-3.5 h-3.5"></i> Settings</h4>
|
|
<span class="badge text-xs ${configSourceClass}" title="${configInfo.activePath || ''}">${configSourceLabel}</span>
|
|
</div>
|
|
<div class="cli-settings-grid">
|
|
<div class="cli-setting-item">
|
|
<label class="cli-setting-label">
|
|
<i data-lucide="layers" class="w-3 h-3"></i>
|
|
Prompt Format
|
|
</label>
|
|
<div class="cli-setting-control">
|
|
<select class="cli-setting-select" onchange="setPromptFormat(this.value)">
|
|
<option value="plain" ${promptConcatFormat === 'plain' ? 'selected' : ''}>Plain Text</option>
|
|
<option value="yaml" ${promptConcatFormat === 'yaml' ? 'selected' : ''}>YAML</option>
|
|
<option value="json" ${promptConcatFormat === 'json' ? 'selected' : ''}>JSON</option>
|
|
</select>
|
|
</div>
|
|
<p class="cli-setting-desc">Format for multi-turn conversation concatenation</p>
|
|
</div>
|
|
<div class="cli-setting-item">
|
|
<label class="cli-setting-label">
|
|
<i data-lucide="database" class="w-3 h-3"></i>
|
|
Storage Backend
|
|
</label>
|
|
<div class="cli-setting-control">
|
|
<span class="cli-setting-value">SQLite</span>
|
|
</div>
|
|
<p class="cli-setting-desc">CLI history stored in SQLite with FTS search</p>
|
|
</div>
|
|
<div class="cli-setting-item">
|
|
<label class="cli-setting-label">
|
|
<i data-lucide="sparkles" class="w-3 h-3"></i>
|
|
Smart Context
|
|
</label>
|
|
<div class="cli-setting-control">
|
|
<label class="cli-toggle">
|
|
<input type="checkbox" ${smartContextEnabled ? 'checked' : ''} onchange="setSmartContextEnabled(this.checked)">
|
|
<span class="cli-toggle-slider"></span>
|
|
</label>
|
|
</div>
|
|
<p class="cli-setting-desc">Auto-analyze prompt and add relevant file paths</p>
|
|
</div>
|
|
<div class="cli-setting-item">
|
|
<label class="cli-setting-label">
|
|
<i data-lucide="refresh-cw" class="w-3 h-3"></i>
|
|
Native Resume
|
|
</label>
|
|
<div class="cli-setting-control">
|
|
<label class="cli-toggle">
|
|
<input type="checkbox" ${nativeResumeEnabled ? 'checked' : ''} onchange="setNativeResumeEnabled(this.checked)">
|
|
<span class="cli-toggle-slider"></span>
|
|
</label>
|
|
</div>
|
|
<p class="cli-setting-desc">Use native tool resume (gemini -r, qwen --resume, codex resume, claude --resume)</p>
|
|
</div>
|
|
<div class="cli-setting-item ${!smartContextEnabled ? 'disabled' : ''}">
|
|
<label class="cli-setting-label">
|
|
<i data-lucide="files" class="w-3 h-3"></i>
|
|
Max Context Files
|
|
</label>
|
|
<div class="cli-setting-control">
|
|
<select class="cli-setting-select" onchange="setSmartContextMaxFiles(this.value)" ${!smartContextEnabled ? 'disabled' : ''}>
|
|
<option value="5" ${smartContextMaxFiles === 5 ? 'selected' : ''}>5 files</option>
|
|
<option value="10" ${smartContextMaxFiles === 10 ? 'selected' : ''}>10 files</option>
|
|
<option value="20" ${smartContextMaxFiles === 20 ? 'selected' : ''}>20 files</option>
|
|
</select>
|
|
</div>
|
|
<p class="cli-setting-desc">Maximum files to include in smart context</p>
|
|
</div>
|
|
<div class="cli-setting-item">
|
|
<label class="cli-setting-label">
|
|
<i data-lucide="hard-drive" class="w-3 h-3"></i>
|
|
Cache Injection
|
|
</label>
|
|
<div class="cli-setting-control">
|
|
<select class="cli-setting-select" onchange="setCacheInjectionMode(this.value)">
|
|
<option value="auto" ${getCacheInjectionMode() === 'auto' ? 'selected' : ''}>Auto</option>
|
|
<option value="manual" ${getCacheInjectionMode() === 'manual' ? 'selected' : ''}>Manual</option>
|
|
<option value="disabled" ${getCacheInjectionMode() === 'disabled' ? 'selected' : ''}>Disabled</option>
|
|
</select>
|
|
</div>
|
|
<p class="cli-setting-desc">Cache prefix/suffix injection mode for prompts</p>
|
|
</div>
|
|
<div class="cli-setting-item">
|
|
<label class="cli-setting-label">
|
|
<i data-lucide="search" class="w-3 h-3"></i>
|
|
Code Index MCP
|
|
</label>
|
|
<div class="cli-setting-control">
|
|
<div class="flex items-center bg-muted rounded-lg p-0.5">
|
|
<button class="code-mcp-btn px-3 py-1.5 text-xs font-medium rounded-md transition-all ${codeIndexMcpProvider === 'codexlens' ? 'bg-primary text-primary-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'}"
|
|
onclick="setCodeIndexMcpProvider('codexlens')">
|
|
CodexLens
|
|
</button>
|
|
<button class="code-mcp-btn px-3 py-1.5 text-xs font-medium rounded-md transition-all ${codeIndexMcpProvider === 'ace' ? 'bg-primary text-primary-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'}"
|
|
onclick="setCodeIndexMcpProvider('ace')">
|
|
ACE
|
|
</button>
|
|
<button class="code-mcp-btn px-3 py-1.5 text-xs font-medium rounded-md transition-all ${codeIndexMcpProvider === 'none' ? 'bg-primary text-primary-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'}"
|
|
onclick="setCodeIndexMcpProvider('none')">
|
|
None
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<p class="cli-setting-desc">Code search provider (updates CLAUDE.md context-tools reference)</p>
|
|
<p class="cli-setting-desc text-xs text-muted-foreground mt-1">
|
|
<i data-lucide="file-text" class="w-3 h-3 inline-block mr-1"></i>
|
|
Current: <code class="bg-muted px-1 rounded">${getContextToolsFileName(codeIndexMcpProvider)}</code>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
container.innerHTML = `
|
|
<div class="cli-status-header">
|
|
<h3><i data-lucide="terminal" class="w-4 h-4"></i> CLI Tools</h3>
|
|
<button class="btn-icon" onclick="refreshAllCliStatus()" title="Refresh">
|
|
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
|
</button>
|
|
</div>
|
|
${ccwInstallHtml}
|
|
<div class="cli-tools-grid">
|
|
${toolsHtml}
|
|
${codexLensHtml}
|
|
${semanticHtml}
|
|
</div>
|
|
${apiEndpointsHtml}
|
|
${settingsHtml}
|
|
`;
|
|
|
|
// Initialize Lucide icons
|
|
if (window.lucide) {
|
|
lucide.createIcons();
|
|
}
|
|
}
|
|
|
|
// ========== Actions ==========
|
|
function setDefaultCliTool(tool) {
|
|
// Validate: tool must be available and enabled
|
|
const status = cliToolStatus[tool] || {};
|
|
const config = cliToolsConfig[tool] || { enabled: true };
|
|
|
|
if (!status.available) {
|
|
showRefreshToast(`Cannot set ${tool} as default: not installed`, 'error');
|
|
return;
|
|
}
|
|
|
|
if (config.enabled === false) {
|
|
showRefreshToast(`Cannot set ${tool} as default: tool is disabled`, 'error');
|
|
return;
|
|
}
|
|
|
|
defaultCliTool = tool;
|
|
// Save to config
|
|
if (window.claudeCliToolsConfig) {
|
|
window.claudeCliToolsConfig.defaultTool = tool;
|
|
fetch('/api/cli/tools-config', {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ defaultTool: tool })
|
|
}).catch(err => console.error('Failed to save default tool:', err));
|
|
}
|
|
renderCliStatus();
|
|
showRefreshToast(`Default CLI tool set to ${tool}`, 'success');
|
|
}
|
|
|
|
function setPromptFormat(format) {
|
|
promptConcatFormat = format;
|
|
localStorage.setItem('ccw-prompt-format', format);
|
|
showRefreshToast(`Prompt format set to ${format.toUpperCase()}`, 'success');
|
|
}
|
|
|
|
function setSmartContextEnabled(enabled) {
|
|
smartContextEnabled = enabled;
|
|
localStorage.setItem('ccw-smart-context', enabled.toString());
|
|
// Re-render the appropriate settings panel
|
|
if (typeof renderCliSettingsSection === 'function') {
|
|
renderCliSettingsSection();
|
|
} else {
|
|
renderCliStatus();
|
|
}
|
|
showRefreshToast(`Smart Context ${enabled ? 'enabled' : 'disabled'}`, 'success');
|
|
}
|
|
|
|
function setSmartContextMaxFiles(max) {
|
|
smartContextMaxFiles = parseInt(max, 10);
|
|
localStorage.setItem('ccw-smart-context-max-files', max);
|
|
showRefreshToast(`Smart Context max files set to ${max}`, 'success');
|
|
}
|
|
|
|
function setNativeResumeEnabled(enabled) {
|
|
nativeResumeEnabled = enabled;
|
|
localStorage.setItem('ccw-native-resume', enabled.toString());
|
|
showRefreshToast(`Native Resume ${enabled ? 'enabled' : 'disabled'}`, 'success');
|
|
}
|
|
|
|
function setRecursiveQueryEnabled(enabled) {
|
|
recursiveQueryEnabled = enabled;
|
|
localStorage.setItem('ccw-recursive-query', enabled.toString());
|
|
showRefreshToast(`Recursive Query ${enabled ? 'enabled' : 'disabled'}`, 'success');
|
|
}
|
|
|
|
function getCacheInjectionMode() {
|
|
if (window.claudeCliToolsConfig && window.claudeCliToolsConfig.settings) {
|
|
return window.claudeCliToolsConfig.settings.cache?.injectionMode || 'auto';
|
|
}
|
|
return localStorage.getItem('ccw-cache-injection-mode') || 'auto';
|
|
}
|
|
|
|
async function setCacheInjectionMode(mode) {
|
|
try {
|
|
const response = await fetch('/api/cli/tools-config/cache', {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ injectionMode: mode })
|
|
});
|
|
if (response.ok) {
|
|
localStorage.setItem('ccw-cache-injection-mode', mode);
|
|
if (window.claudeCliToolsConfig) {
|
|
window.claudeCliToolsConfig.settings.cache.injectionMode = mode;
|
|
}
|
|
showRefreshToast(`Cache injection mode set to ${mode}`, 'success');
|
|
} else {
|
|
showRefreshToast('Failed to update cache settings', 'error');
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to update cache settings:', err);
|
|
showRefreshToast('Failed to update cache settings', 'error');
|
|
}
|
|
}
|
|
|
|
async function setCodeIndexMcpProvider(provider) {
|
|
try {
|
|
const response = await fetch('/api/cli/code-index-mcp', {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ provider: provider })
|
|
});
|
|
if (response.ok) {
|
|
codeIndexMcpProvider = provider;
|
|
if (window.claudeCliToolsConfig && window.claudeCliToolsConfig.settings) {
|
|
window.claudeCliToolsConfig.settings.codeIndexMcp = provider;
|
|
}
|
|
const providerName = provider === 'ace' ? 'ACE (Augment)' : provider === 'none' ? 'None (Built-in only)' : 'CodexLens';
|
|
showRefreshToast(`Code Index MCP switched to ${providerName}`, 'success');
|
|
// Re-render both CLI status and settings section
|
|
if (typeof renderCliStatus === 'function') renderCliStatus();
|
|
if (typeof renderCliSettingsSection === 'function') renderCliSettingsSection();
|
|
} else {
|
|
const data = await response.json();
|
|
showRefreshToast(`Failed to switch Code Index MCP: ${data.error}`, 'error');
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to switch Code Index MCP:', err);
|
|
showRefreshToast('Failed to switch Code Index MCP', 'error');
|
|
}
|
|
}
|
|
|
|
async function refreshAllCliStatus() {
|
|
await loadAllStatuses();
|
|
renderCliStatus();
|
|
}
|
|
|
|
async function toggleCliTool(tool, enabled) {
|
|
// If disabling the current default tool, switch to another available+enabled tool
|
|
if (!enabled && defaultCliTool === tool) {
|
|
const tools = ['gemini', 'qwen', 'codex', 'claude'];
|
|
const newDefault = tools.find(t => {
|
|
if (t === tool) return false;
|
|
const status = cliToolStatus[t] || {};
|
|
const config = cliToolsConfig[t] || { enabled: true };
|
|
return status.available && config.enabled !== false;
|
|
});
|
|
|
|
if (newDefault) {
|
|
defaultCliTool = newDefault;
|
|
if (window.claudeCliToolsConfig) {
|
|
window.claudeCliToolsConfig.defaultTool = newDefault;
|
|
}
|
|
showRefreshToast(`Default tool switched to ${newDefault}`, 'info');
|
|
} else {
|
|
showRefreshToast(`Warning: No other enabled tool available for default`, 'warning');
|
|
}
|
|
}
|
|
|
|
await updateCliToolEnabled(tool, enabled);
|
|
await loadAllStatuses();
|
|
renderCliStatus();
|
|
}
|
|
|
|
function installCodexLens() {
|
|
openCodexLensInstallWizard();
|
|
}
|
|
|
|
function openCodexLensInstallWizard() {
|
|
const modal = document.createElement('div');
|
|
modal.id = 'codexlensInstallModal';
|
|
modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50';
|
|
modal.innerHTML = `
|
|
<div class="bg-card rounded-lg shadow-xl w-full max-w-md mx-4 overflow-hidden">
|
|
<div class="p-6">
|
|
<div class="flex items-center gap-3 mb-4">
|
|
<div class="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center">
|
|
<i data-lucide="database" class="w-5 h-5 text-primary"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-semibold">Install CodexLens</h3>
|
|
<p class="text-sm text-muted-foreground">Python-based code indexing engine</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
<div class="bg-muted/50 rounded-lg p-4">
|
|
<h4 class="font-medium mb-2">What will be installed:</h4>
|
|
<ul class="text-sm space-y-2 text-muted-foreground">
|
|
<li class="flex items-start gap-2">
|
|
<i data-lucide="check" class="w-4 h-4 text-success mt-0.5"></i>
|
|
<span><strong>Python virtual environment</strong> - Isolated Python environment</span>
|
|
</li>
|
|
<li class="flex items-start gap-2">
|
|
<i data-lucide="check" class="w-4 h-4 text-success mt-0.5"></i>
|
|
<span><strong>CodexLens package</strong> - Code indexing and search engine</span>
|
|
</li>
|
|
<li class="flex items-start gap-2">
|
|
<i data-lucide="check" class="w-4 h-4 text-success mt-0.5"></i>
|
|
<span><strong>SQLite FTS5</strong> - Full-text search database</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="bg-primary/5 border border-primary/20 rounded-lg p-3">
|
|
<div class="flex items-start gap-2">
|
|
<i data-lucide="info" class="w-4 h-4 text-primary mt-0.5"></i>
|
|
<div class="text-sm text-muted-foreground">
|
|
<p class="font-medium text-foreground">Installation Location</p>
|
|
<p class="mt-1"><code class="bg-muted px-1 rounded">~/.codexlens/venv</code></p>
|
|
<p class="mt-1">First installation may take 2-3 minutes to download and setup Python packages.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="codexlensInstallProgress" class="hidden">
|
|
<div class="flex items-center gap-3">
|
|
<div class="animate-spin w-5 h-5 border-2 border-primary border-t-transparent rounded-full"></div>
|
|
<span class="text-sm" id="codexlensInstallStatus">Starting installation...</span>
|
|
</div>
|
|
<div class="mt-2 h-2 bg-muted rounded-full overflow-hidden">
|
|
<div id="codexlensProgressBar" class="h-full bg-primary transition-all duration-300" style="width: 0%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="border-t border-border p-4 flex justify-end gap-3 bg-muted/30">
|
|
<button class="btn-outline px-4 py-2" onclick="closeCodexLensInstallWizard()">Cancel</button>
|
|
<button id="codexlensInstallBtn" class="btn-primary px-4 py-2" onclick="startCodexLensInstall()">
|
|
<i data-lucide="download" class="w-4 h-4 mr-2"></i>
|
|
Install Now
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(modal);
|
|
|
|
if (window.lucide) {
|
|
lucide.createIcons();
|
|
}
|
|
}
|
|
|
|
function closeCodexLensInstallWizard() {
|
|
const modal = document.getElementById('codexlensInstallModal');
|
|
if (modal) {
|
|
modal.remove();
|
|
}
|
|
}
|
|
|
|
async function startCodexLensInstall() {
|
|
const progressDiv = document.getElementById('codexlensInstallProgress');
|
|
const installBtn = document.getElementById('codexlensInstallBtn');
|
|
const statusText = document.getElementById('codexlensInstallStatus');
|
|
const progressBar = document.getElementById('codexlensProgressBar');
|
|
|
|
// Show progress, disable button
|
|
progressDiv.classList.remove('hidden');
|
|
installBtn.disabled = true;
|
|
installBtn.innerHTML = '<span class="animate-pulse">Installing...</span>';
|
|
|
|
// Simulate progress stages
|
|
const stages = [
|
|
{ progress: 10, text: 'Creating virtual environment...' },
|
|
{ progress: 30, text: 'Installing pip packages...' },
|
|
{ progress: 50, text: 'Installing CodexLens package...' },
|
|
{ progress: 70, text: 'Setting up Python dependencies...' },
|
|
{ progress: 90, text: 'Finalizing installation...' }
|
|
];
|
|
|
|
let currentStage = 0;
|
|
const progressInterval = setInterval(() => {
|
|
if (currentStage < stages.length) {
|
|
statusText.textContent = stages[currentStage].text;
|
|
progressBar.style.width = `${stages[currentStage].progress}%`;
|
|
currentStage++;
|
|
}
|
|
}, 1500);
|
|
|
|
try {
|
|
const response = await fetch('/api/codexlens/bootstrap', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({})
|
|
});
|
|
|
|
clearInterval(progressInterval);
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
progressBar.style.width = '100%';
|
|
statusText.textContent = 'Installation complete!';
|
|
|
|
setTimeout(() => {
|
|
closeCodexLensInstallWizard();
|
|
showRefreshToast('CodexLens installed successfully!', 'success');
|
|
loadCodexLensStatus().then(() => renderCliStatus());
|
|
}, 1000);
|
|
} else {
|
|
statusText.textContent = `Error: ${result.error}`;
|
|
progressBar.classList.add('bg-destructive');
|
|
installBtn.disabled = false;
|
|
installBtn.innerHTML = '<i data-lucide="refresh-cw" class="w-4 h-4 mr-2"></i> 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 = '<i data-lucide="refresh-cw" class="w-4 h-4 mr-2"></i> Retry';
|
|
if (window.lucide) lucide.createIcons();
|
|
}
|
|
}
|
|
|
|
function uninstallCodexLens() {
|
|
openCodexLensUninstallWizard();
|
|
}
|
|
|
|
function openCodexLensUninstallWizard() {
|
|
const modal = document.createElement('div');
|
|
modal.id = 'codexlensUninstallModal';
|
|
modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50';
|
|
modal.innerHTML = `
|
|
<div class="bg-card rounded-lg shadow-xl w-full max-w-md mx-4 overflow-hidden">
|
|
<div class="p-6">
|
|
<div class="flex items-center gap-3 mb-4">
|
|
<div class="w-10 h-10 rounded-full bg-destructive/10 flex items-center justify-center">
|
|
<i data-lucide="trash-2" class="w-5 h-5 text-destructive"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-semibold">Uninstall CodexLens</h3>
|
|
<p class="text-sm text-muted-foreground">Remove CodexLens and all data</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
<div class="bg-destructive/5 border border-destructive/20 rounded-lg p-4">
|
|
<h4 class="font-medium text-destructive mb-2">What will be removed:</h4>
|
|
<ul class="text-sm space-y-2 text-muted-foreground">
|
|
<li class="flex items-start gap-2">
|
|
<i data-lucide="x" class="w-4 h-4 text-destructive mt-0.5"></i>
|
|
<span>Virtual environment at <code class="bg-muted px-1 rounded">~/.codexlens/venv</code></span>
|
|
</li>
|
|
<li class="flex items-start gap-2">
|
|
<i data-lucide="x" class="w-4 h-4 text-destructive mt-0.5"></i>
|
|
<span>All CodexLens indexed data and databases</span>
|
|
</li>
|
|
<li class="flex items-start gap-2">
|
|
<i data-lucide="x" class="w-4 h-4 text-destructive mt-0.5"></i>
|
|
<span>Configuration and semantic search models</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="bg-warning/10 border border-warning/20 rounded-lg p-3">
|
|
<div class="flex items-start gap-2">
|
|
<i data-lucide="alert-triangle" class="w-4 h-4 text-warning mt-0.5"></i>
|
|
<div class="text-sm">
|
|
<p class="font-medium text-warning">Warning</p>
|
|
<p class="text-muted-foreground">This action cannot be undone. All indexed code data will be permanently deleted.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="codexlensUninstallProgress" class="hidden">
|
|
<div class="flex items-center gap-3">
|
|
<div class="animate-spin w-5 h-5 border-2 border-destructive border-t-transparent rounded-full"></div>
|
|
<span class="text-sm" id="codexlensUninstallStatus">Removing files...</span>
|
|
</div>
|
|
<div class="mt-2 h-2 bg-muted rounded-full overflow-hidden">
|
|
<div id="codexlensUninstallProgressBar" class="h-full bg-destructive transition-all duration-300" style="width: 0%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="border-t border-border p-4 flex justify-end gap-3 bg-muted/30">
|
|
<button class="btn-outline px-4 py-2" onclick="closeCodexLensUninstallWizard()">Cancel</button>
|
|
<button id="codexlensUninstallBtn" class="btn-destructive px-4 py-2" onclick="startCodexLensUninstall()">
|
|
<i data-lucide="trash-2" class="w-4 h-4 mr-2"></i>
|
|
Uninstall
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(modal);
|
|
|
|
if (window.lucide) {
|
|
lucide.createIcons();
|
|
}
|
|
}
|
|
|
|
function closeCodexLensUninstallWizard() {
|
|
const modal = document.getElementById('codexlensUninstallModal');
|
|
if (modal) {
|
|
modal.remove();
|
|
}
|
|
}
|
|
|
|
async function startCodexLensUninstall() {
|
|
const progressDiv = document.getElementById('codexlensUninstallProgress');
|
|
const uninstallBtn = document.getElementById('codexlensUninstallBtn');
|
|
const statusText = document.getElementById('codexlensUninstallStatus');
|
|
const progressBar = document.getElementById('codexlensUninstallProgressBar');
|
|
|
|
// Show progress, disable button
|
|
progressDiv.classList.remove('hidden');
|
|
uninstallBtn.disabled = true;
|
|
uninstallBtn.innerHTML = '<span class="animate-pulse">Uninstalling...</span>';
|
|
|
|
// Simulate progress stages
|
|
const stages = [
|
|
{ progress: 25, text: 'Removing virtual environment...' },
|
|
{ progress: 50, text: 'Deleting indexed data...' },
|
|
{ progress: 75, text: 'Cleaning up configuration...' },
|
|
{ progress: 90, text: 'Finalizing removal...' }
|
|
];
|
|
|
|
let currentStage = 0;
|
|
const progressInterval = setInterval(() => {
|
|
if (currentStage < stages.length) {
|
|
statusText.textContent = stages[currentStage].text;
|
|
progressBar.style.width = `${stages[currentStage].progress}%`;
|
|
currentStage++;
|
|
}
|
|
}, 500);
|
|
|
|
try {
|
|
const response = await fetch('/api/codexlens/uninstall', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({})
|
|
});
|
|
|
|
clearInterval(progressInterval);
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
progressBar.style.width = '100%';
|
|
statusText.textContent = 'Uninstallation complete!';
|
|
|
|
setTimeout(() => {
|
|
closeCodexLensUninstallWizard();
|
|
showRefreshToast('CodexLens uninstalled successfully!', 'success');
|
|
loadCodexLensStatus().then(() => renderCliStatus());
|
|
}, 1000);
|
|
} else {
|
|
statusText.textContent = `Error: ${result.error}`;
|
|
progressBar.classList.remove('bg-destructive');
|
|
progressBar.classList.add('bg-destructive');
|
|
uninstallBtn.disabled = false;
|
|
uninstallBtn.innerHTML = '<i data-lucide="refresh-cw" class="w-4 h-4 mr-2"></i> 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 = '<i data-lucide="refresh-cw" class="w-4 h-4 mr-2"></i> Retry';
|
|
if (window.lucide) lucide.createIcons();
|
|
}
|
|
}
|
|
|
|
async function initCodexLensIndex() {
|
|
// Get current workspace path from multiple sources
|
|
let targetPath = null;
|
|
|
|
// Helper function to check if path is valid
|
|
const isValidPath = (path) => {
|
|
return path && typeof path === 'string' && path.length > 0 &&
|
|
(path.includes('/') || path.includes('\\')) &&
|
|
!path.startsWith('{{') && !path.endsWith('}}');
|
|
};
|
|
|
|
console.log('[CodexLens] Attempting to get project path...');
|
|
|
|
// Try 1: Global projectPath variable
|
|
if (isValidPath(projectPath)) {
|
|
targetPath = projectPath;
|
|
console.log('[CodexLens] ✓ Using global projectPath:', targetPath);
|
|
}
|
|
|
|
// Try 2: Get from workflowData
|
|
if (!targetPath && typeof workflowData !== 'undefined' && workflowData && isValidPath(workflowData.projectPath)) {
|
|
targetPath = workflowData.projectPath;
|
|
console.log('[CodexLens] ✓ Using workflowData.projectPath:', targetPath);
|
|
}
|
|
|
|
// Try 3: Get from current path display element
|
|
if (!targetPath) {
|
|
const currentPathEl = document.getElementById('currentPath');
|
|
if (currentPathEl && currentPathEl.textContent) {
|
|
const pathText = currentPathEl.textContent.trim();
|
|
if (isValidPath(pathText)) {
|
|
targetPath = pathText;
|
|
console.log('[CodexLens] ✓ Using currentPath element text:', targetPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Final validation
|
|
if (!targetPath) {
|
|
showRefreshToast('Error: No workspace loaded. Please open a workspace first.', 'error');
|
|
console.error('[CodexLens] No valid project path available');
|
|
console.error('[CodexLens] Attempted sources: projectPath:', projectPath, 'workflowData:', workflowData);
|
|
return;
|
|
}
|
|
|
|
showRefreshToast('Initializing CodexLens index...', 'info');
|
|
console.log('[CodexLens] Initializing index for path:', targetPath);
|
|
|
|
try {
|
|
const response = await fetch('/api/codexlens/init', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ path: targetPath })
|
|
});
|
|
|
|
const result = await response.json();
|
|
console.log('[CodexLens] Init result:', result);
|
|
|
|
if (result.success) {
|
|
let data = null;
|
|
|
|
// Try to parse nested JSON in output field
|
|
if (result.output && typeof result.output === 'string') {
|
|
try {
|
|
// Extract JSON from output (it may contain other text before the JSON)
|
|
const jsonMatch = result.output.match(/\{[\s\S]*\}/);
|
|
if (jsonMatch) {
|
|
const parsed = JSON.parse(jsonMatch[0]);
|
|
data = parsed.result || parsed;
|
|
console.log('[CodexLens] Parsed from output:', data);
|
|
}
|
|
} catch (e) {
|
|
console.warn('[CodexLens] Failed to parse output as JSON:', e);
|
|
}
|
|
}
|
|
|
|
// Fallback to direct result field
|
|
if (!data) {
|
|
data = result.result?.result || result.result || result;
|
|
}
|
|
|
|
const files = data.files_indexed || 0;
|
|
const dirs = data.dirs_indexed || 0;
|
|
const symbols = data.symbols_indexed || 0;
|
|
|
|
console.log('[CodexLens] Parsed data:', { files, dirs, symbols });
|
|
|
|
if (files === 0 && dirs === 0) {
|
|
showRefreshToast(`Warning: No files indexed. Path: ${targetPath}`, 'warning');
|
|
console.warn('[CodexLens] No files indexed. Full data:', data);
|
|
} else {
|
|
showRefreshToast(`Index created: ${files} files, ${dirs} directories`, 'success');
|
|
console.log('[CodexLens] Index created successfully');
|
|
|
|
// Reload CodexLens status and refresh the view
|
|
loadCodexLensStatus().then(() => renderCliStatus());
|
|
}
|
|
} else {
|
|
showRefreshToast(`Init failed: ${result.error}`, 'error');
|
|
console.error('[CodexLens] Init error:', result.error);
|
|
}
|
|
} catch (err) {
|
|
showRefreshToast(`Init error: ${err.message}`, 'error');
|
|
console.error('[CodexLens] Exception:', err);
|
|
}
|
|
}
|
|
|
|
// ========== Semantic Search Installation Wizard ==========
|
|
function openSemanticInstallWizard() {
|
|
const modal = document.createElement('div');
|
|
modal.id = 'semanticInstallModal';
|
|
modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50';
|
|
modal.innerHTML = `
|
|
<div class="bg-card rounded-lg shadow-xl w-full max-w-md mx-4 overflow-hidden">
|
|
<div class="p-6">
|
|
<div class="flex items-center gap-3 mb-4">
|
|
<div class="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center">
|
|
<i data-lucide="brain" class="w-5 h-5 text-primary"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-semibold">Install Semantic Search</h3>
|
|
<p class="text-sm text-muted-foreground">AI-powered code understanding</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
<div class="bg-muted/50 rounded-lg p-4">
|
|
<h4 class="font-medium mb-2">What will be installed:</h4>
|
|
<ul class="text-sm space-y-2 text-muted-foreground">
|
|
<li class="flex items-start gap-2">
|
|
<i data-lucide="check" class="w-4 h-4 text-success mt-0.5"></i>
|
|
<span><strong>sentence-transformers</strong> - ML framework</span>
|
|
</li>
|
|
<li class="flex items-start gap-2">
|
|
<i data-lucide="check" class="w-4 h-4 text-success mt-0.5"></i>
|
|
<span><strong>bge-small-en-v1.5</strong> - Embedding model (~130MB)</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="bg-primary/10 border border-primary/20 rounded-lg p-3">
|
|
<div class="flex items-start gap-2">
|
|
<i data-lucide="info" class="w-4 h-4 text-primary mt-0.5"></i>
|
|
<div class="text-sm">
|
|
<p class="font-medium text-primary">Download Size</p>
|
|
<p class="text-muted-foreground">Total size: ~130MB. First-time model loading may take a few minutes.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="semanticInstallProgress" class="hidden">
|
|
<div class="flex items-center gap-3">
|
|
<div class="animate-spin w-5 h-5 border-2 border-primary border-t-transparent rounded-full"></div>
|
|
<span class="text-sm" id="semanticInstallStatus">Installing dependencies...</span>
|
|
</div>
|
|
<div class="mt-2 h-2 bg-muted rounded-full overflow-hidden">
|
|
<div id="semanticProgressBar" class="h-full bg-primary transition-all duration-300" style="width: 0%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="border-t border-border p-4 flex justify-end gap-3 bg-muted/30">
|
|
<button class="btn-outline px-4 py-2" onclick="closeSemanticInstallWizard()">Cancel</button>
|
|
<button id="semanticInstallBtn" class="btn-primary px-4 py-2" onclick="startSemanticInstall()">
|
|
<i data-lucide="download" class="w-4 h-4 mr-2"></i>
|
|
Install Now
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(modal);
|
|
|
|
// Initialize Lucide icons in modal
|
|
if (window.lucide) {
|
|
lucide.createIcons();
|
|
}
|
|
}
|
|
|
|
function closeSemanticInstallWizard() {
|
|
const modal = document.getElementById('semanticInstallModal');
|
|
if (modal) {
|
|
modal.remove();
|
|
}
|
|
}
|
|
|
|
async function startSemanticInstall() {
|
|
const progressDiv = document.getElementById('semanticInstallProgress');
|
|
const installBtn = document.getElementById('semanticInstallBtn');
|
|
const statusText = document.getElementById('semanticInstallStatus');
|
|
const progressBar = document.getElementById('semanticProgressBar');
|
|
|
|
// Show progress, disable button
|
|
progressDiv.classList.remove('hidden');
|
|
installBtn.disabled = true;
|
|
installBtn.innerHTML = '<span class="animate-pulse">Installing...</span>';
|
|
|
|
// Simulate progress stages
|
|
const stages = [
|
|
{ progress: 20, text: 'Installing sentence-transformers...' },
|
|
{ progress: 50, text: 'Downloading embedding model...' },
|
|
{ progress: 80, text: 'Setting up model cache...' },
|
|
{ progress: 95, text: 'Finalizing installation...' }
|
|
];
|
|
|
|
let currentStage = 0;
|
|
const progressInterval = setInterval(() => {
|
|
if (currentStage < stages.length) {
|
|
statusText.textContent = stages[currentStage].text;
|
|
progressBar.style.width = `${stages[currentStage].progress}%`;
|
|
currentStage++;
|
|
}
|
|
}, 2000);
|
|
|
|
try {
|
|
const response = await fetch('/api/codexlens/semantic/install', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({})
|
|
});
|
|
|
|
clearInterval(progressInterval);
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
progressBar.style.width = '100%';
|
|
statusText.textContent = 'Installation complete!';
|
|
|
|
setTimeout(() => {
|
|
closeSemanticInstallWizard();
|
|
showRefreshToast('Semantic search installed successfully!', 'success');
|
|
loadSemanticStatus().then(() => renderCliStatus());
|
|
}, 1000);
|
|
} else {
|
|
statusText.textContent = `Error: ${result.error}`;
|
|
progressBar.classList.add('bg-destructive');
|
|
installBtn.disabled = false;
|
|
installBtn.innerHTML = '<i data-lucide="refresh-cw" class="w-4 h-4 mr-2"></i> 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 = '<i data-lucide="refresh-cw" class="w-4 h-4 mr-2"></i> Retry';
|
|
if (window.lucide) lucide.createIcons();
|
|
}
|
|
}
|
|
|