mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-10 02:24:35 +08:00
feat: Implement CLAUDE.md Manager View with file tree, viewer, and metadata actions
- Added main JavaScript functionality for CLAUDE.md management including file loading, rendering, and editing capabilities. - Created a test HTML file to validate the functionality of the CLAUDE.md manager. - Introduced CLI generation examples and documentation for rules creation via CLI. - Enhanced error handling and notifications for file operations.
This commit is contained in:
@@ -184,6 +184,9 @@ function renderCliStatus() {
|
||||
</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>
|
||||
@@ -379,8 +382,121 @@ async function refreshAllCliStatus() {
|
||||
renderCliStatus();
|
||||
}
|
||||
|
||||
async function installCodexLens() {
|
||||
showRefreshToast('Installing CodexLens...', 'info');
|
||||
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', {
|
||||
@@ -389,40 +505,288 @@ async function installCodexLens() {
|
||||
body: JSON.stringify({})
|
||||
});
|
||||
|
||||
clearInterval(progressInterval);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showRefreshToast('CodexLens installed successfully!', 'success');
|
||||
await loadCodexLensStatus();
|
||||
renderCliStatus();
|
||||
progressBar.style.width = '100%';
|
||||
statusText.textContent = 'Installation complete!';
|
||||
|
||||
setTimeout(() => {
|
||||
closeCodexLensInstallWizard();
|
||||
showRefreshToast('CodexLens installed successfully!', 'success');
|
||||
loadCodexLensStatus().then(() => renderCliStatus());
|
||||
}, 1000);
|
||||
} else {
|
||||
showRefreshToast(`Install failed: ${result.error}`, 'error');
|
||||
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) {
|
||||
showRefreshToast(`Install error: ${err.message}`, 'error');
|
||||
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: projectPath })
|
||||
body: JSON.stringify({ path: targetPath })
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
console.log('[CodexLens] Init result:', result);
|
||||
|
||||
if (result.success) {
|
||||
const data = result.result?.result || result.result || result;
|
||||
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;
|
||||
showRefreshToast(`Index created: ${files} files, ${symbols} symbols`, 'success');
|
||||
|
||||
console.log('[CodexLens] Parsed data:', { files, dirs, symbols });
|
||||
|
||||
if (files === 0 && dirs === 0) {
|
||||
showRefreshToast(`Warning: No files indexed. Path: ${targetPath}`, 'warning');
|
||||
console.warn('[CodexLens] No files indexed. Full data:', data);
|
||||
} else {
|
||||
showRefreshToast(`Index created: ${files} files, ${dirs} directories`, 'success');
|
||||
console.log('[CodexLens] Index created successfully');
|
||||
}
|
||||
} else {
|
||||
showRefreshToast(`Init failed: ${result.error}`, 'error');
|
||||
console.error('[CodexLens] Init error:', result.error);
|
||||
}
|
||||
} catch (err) {
|
||||
showRefreshToast(`Init error: ${err.message}`, 'error');
|
||||
console.error('[CodexLens] Exception:', err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,7 +955,7 @@ function openSemanticSettingsModal() {
|
||||
tool.charAt(0).toUpperCase() + tool.slice(1) + '</option>';
|
||||
}).join('');
|
||||
|
||||
const fallbackOptions = '<option value="">None</option>' + availableTools.map(function(tool) {
|
||||
const fallbackOptions = '<option value="">' + t('semantic.none') + '</option>' + availableTools.map(function(tool) {
|
||||
return '<option value="' + tool + '"' + (llmEnhancementSettings.fallbackTool === tool ? ' selected' : '') + '>' +
|
||||
tool.charAt(0).toUpperCase() + tool.slice(1) + '</option>';
|
||||
}).join('');
|
||||
@@ -607,16 +971,16 @@ function openSemanticSettingsModal() {
|
||||
'<i data-lucide="sparkles" class="w-5 h-5 text-primary"></i>' +
|
||||
'</div>' +
|
||||
'<div>' +
|
||||
'<h3 class="text-lg font-semibold">Semantic Search Settings</h3>' +
|
||||
'<p class="text-sm text-muted-foreground">Configure LLM enhancement for semantic indexing</p>' +
|
||||
'<h3 class="text-lg font-semibold">' + t('semantic.settings') + '</h3>' +
|
||||
'<p class="text-sm text-muted-foreground">' + t('semantic.configDesc') + '</p>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="space-y-4">' +
|
||||
'<div class="flex items-center justify-between p-4 bg-muted/50 rounded-lg">' +
|
||||
'<div>' +
|
||||
'<h4 class="font-medium flex items-center gap-2">' +
|
||||
'<i data-lucide="brain" class="w-4 h-4"></i>LLM Enhancement</h4>' +
|
||||
'<p class="text-sm text-muted-foreground mt-1">Use LLM to generate code summaries for better semantic search</p>' +
|
||||
'<i data-lucide="brain" class="w-4 h-4"></i>' + t('semantic.llmEnhancement') + '</h4>' +
|
||||
'<p class="text-sm text-muted-foreground mt-1">' + t('semantic.llmDesc') + '</p>' +
|
||||
'</div>' +
|
||||
'<label class="cli-toggle">' +
|
||||
'<input type="checkbox" id="llmEnhancementToggle" ' + (llmEnhancementSettings.enabled ? 'checked' : '') +
|
||||
@@ -628,29 +992,29 @@ function openSemanticSettingsModal() {
|
||||
'<div class="grid grid-cols-2 gap-4">' +
|
||||
'<div>' +
|
||||
'<label class="block text-sm font-medium mb-2">' +
|
||||
'<i data-lucide="cpu" class="w-3 h-3 inline mr-1"></i>Primary LLM Tool</label>' +
|
||||
'<i data-lucide="cpu" class="w-3 h-3 inline mr-1"></i>' + t('semantic.primaryTool') + '</label>' +
|
||||
'<select class="cli-setting-select w-full" id="llmToolSelect" onchange="updateLlmTool(this.value)" ' + disabled + '>' + toolOptions + '</select>' +
|
||||
'</div>' +
|
||||
'<div>' +
|
||||
'<label class="block text-sm font-medium mb-2">' +
|
||||
'<i data-lucide="refresh-cw" class="w-3 h-3 inline mr-1"></i>Fallback Tool</label>' +
|
||||
'<i data-lucide="refresh-cw" class="w-3 h-3 inline mr-1"></i>' + t('semantic.fallbackTool') + '</label>' +
|
||||
'<select class="cli-setting-select w-full" id="llmFallbackSelect" onchange="updateLlmFallback(this.value)" ' + disabled + '>' + fallbackOptions + '</select>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="grid grid-cols-2 gap-4">' +
|
||||
'<div>' +
|
||||
'<label class="block text-sm font-medium mb-2">' +
|
||||
'<i data-lucide="layers" class="w-3 h-3 inline mr-1"></i>Batch Size</label>' +
|
||||
'<i data-lucide="layers" class="w-3 h-3 inline mr-1"></i>' + t('semantic.batchSize') + '</label>' +
|
||||
'<select class="cli-setting-select w-full" id="llmBatchSelect" onchange="updateLlmBatchSize(this.value)" ' + disabled + '>' +
|
||||
'<option value="1"' + (llmEnhancementSettings.batchSize === 1 ? ' selected' : '') + '>1 file</option>' +
|
||||
'<option value="3"' + (llmEnhancementSettings.batchSize === 3 ? ' selected' : '') + '>3 files</option>' +
|
||||
'<option value="5"' + (llmEnhancementSettings.batchSize === 5 ? ' selected' : '') + '>5 files</option>' +
|
||||
'<option value="10"' + (llmEnhancementSettings.batchSize === 10 ? ' selected' : '') + '>10 files</option>' +
|
||||
'<option value="1"' + (llmEnhancementSettings.batchSize === 1 ? ' selected' : '') + '>1 ' + t('semantic.file') + '</option>' +
|
||||
'<option value="3"' + (llmEnhancementSettings.batchSize === 3 ? ' selected' : '') + '>3 ' + t('semantic.files') + '</option>' +
|
||||
'<option value="5"' + (llmEnhancementSettings.batchSize === 5 ? ' selected' : '') + '>5 ' + t('semantic.files') + '</option>' +
|
||||
'<option value="10"' + (llmEnhancementSettings.batchSize === 10 ? ' selected' : '') + '>10 ' + t('semantic.files') + '</option>' +
|
||||
'</select>' +
|
||||
'</div>' +
|
||||
'<div>' +
|
||||
'<label class="block text-sm font-medium mb-2">' +
|
||||
'<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>Timeout</label>' +
|
||||
'<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>' + t('semantic.timeout') + '</label>' +
|
||||
'<select class="cli-setting-select w-full" id="llmTimeoutSelect" onchange="updateLlmTimeout(this.value)" ' + disabled + '>' +
|
||||
'<option value="60000"' + (llmEnhancementSettings.timeoutMs === 60000 ? ' selected' : '') + '>1 min</option>' +
|
||||
'<option value="180000"' + (llmEnhancementSettings.timeoutMs === 180000 ? ' selected' : '') + '>3 min</option>' +
|
||||
@@ -664,26 +1028,110 @@ function openSemanticSettingsModal() {
|
||||
'<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>LLM enhancement generates code summaries and keywords for each file, improving semantic search accuracy.</p>' +
|
||||
'<p class="mt-1">Run <code class="bg-muted px-1 rounded">codex-lens enhance</code> after enabling to process existing files.</p>' +
|
||||
'<p>' + t('semantic.enhanceInfo') + '</p>' +
|
||||
'<p class="mt-1">' + t('semantic.enhanceCommand') + ' <code class="bg-muted px-1 rounded">codex-lens enhance</code> ' + t('semantic.enhanceAfterEnable') + '</p>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="flex gap-2 pt-2">' +
|
||||
'<button class="btn-sm btn-outline flex items-center gap-1 flex-1" onclick="runEnhanceCommand()" ' + disabled + '>' +
|
||||
'<i data-lucide="zap" class="w-3 h-3"></i>Run Enhance Now</button>' +
|
||||
'<i data-lucide="zap" class="w-3 h-3"></i>' + t('semantic.runEnhanceNow') + '</button>' +
|
||||
'<button class="btn-sm btn-outline flex items-center gap-1 flex-1" onclick="viewEnhanceStatus()">' +
|
||||
'<i data-lucide="bar-chart-2" class="w-3 h-3"></i>View Status</button>' +
|
||||
'<i data-lucide="bar-chart-2" class="w-3 h-3"></i>' + t('semantic.viewStatus') + '</button>' +
|
||||
'</div>' +
|
||||
'<div class="border-t border-border my-4"></div>' +
|
||||
'<div>' +
|
||||
'<h4 class="font-medium mb-3 flex items-center gap-2">' +
|
||||
'<i data-lucide="search" class="w-4 h-4"></i>' + t('semantic.testSearch') + '</h4>' +
|
||||
'<div class="space-y-3">' +
|
||||
'<div>' +
|
||||
'<input type="text" id="semanticSearchInput" class="tool-config-input w-full" ' +
|
||||
'placeholder="' + t('semantic.searchPlaceholder') + '" />' +
|
||||
'</div>' +
|
||||
'<div>' +
|
||||
'<button class="btn-sm btn-primary w-full" id="runSemanticSearchBtn">' +
|
||||
'<i data-lucide="search" class="w-3 h-3"></i> ' + t('semantic.runSearch') +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'<div id="semanticSearchResults" class="hidden">' +
|
||||
'<div class="bg-muted/30 rounded-lg p-3 max-h-64 overflow-y-auto">' +
|
||||
'<div class="flex items-center justify-between mb-2">' +
|
||||
'<p class="text-sm font-medium">' + t('codexlens.results') + ':</p>' +
|
||||
'<span id="semanticResultCount" class="text-xs text-muted-foreground"></span>' +
|
||||
'</div>' +
|
||||
'<pre id="semanticResultContent" class="text-xs font-mono whitespace-pre-wrap break-all"></pre>' +
|
||||
'</div>' +
|
||||
'</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="closeSemanticSettingsModal()">Close</button>' +
|
||||
'<button class="btn-outline px-4 py-2" onclick="closeSemanticSettingsModal()">' + t('semantic.close') + '</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Add semantic search button handler
|
||||
setTimeout(function() {
|
||||
var runSemanticSearchBtn = document.getElementById('runSemanticSearchBtn');
|
||||
if (runSemanticSearchBtn) {
|
||||
runSemanticSearchBtn.onclick = async function() {
|
||||
var query = document.getElementById('semanticSearchInput').value.trim();
|
||||
var resultsDiv = document.getElementById('semanticSearchResults');
|
||||
var resultCount = document.getElementById('semanticResultCount');
|
||||
var resultContent = document.getElementById('semanticResultContent');
|
||||
|
||||
if (!query) {
|
||||
showRefreshToast(t('codexlens.enterQuery'), 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
runSemanticSearchBtn.disabled = true;
|
||||
runSemanticSearchBtn.innerHTML = '<span class="animate-pulse">' + t('codexlens.searching') + '</span>';
|
||||
resultsDiv.classList.add('hidden');
|
||||
|
||||
try {
|
||||
var params = new URLSearchParams({
|
||||
query: query,
|
||||
mode: 'semantic',
|
||||
limit: '10'
|
||||
});
|
||||
|
||||
var response = await fetch('/api/codexlens/search?' + params.toString());
|
||||
var result = await response.json();
|
||||
|
||||
console.log('[Semantic Search Test] Result:', result);
|
||||
|
||||
if (result.success) {
|
||||
var results = result.results || [];
|
||||
resultCount.textContent = results.length + ' ' + t('codexlens.resultsCount');
|
||||
resultContent.textContent = JSON.stringify(results, null, 2);
|
||||
resultsDiv.classList.remove('hidden');
|
||||
showRefreshToast(t('codexlens.searchCompleted') + ': ' + results.length + ' ' + t('codexlens.resultsCount'), 'success');
|
||||
} else {
|
||||
resultContent.textContent = t('common.error') + ': ' + (result.error || t('common.unknownError'));
|
||||
resultsDiv.classList.remove('hidden');
|
||||
showRefreshToast(t('codexlens.searchFailed') + ': ' + result.error, 'error');
|
||||
}
|
||||
|
||||
runSemanticSearchBtn.disabled = false;
|
||||
runSemanticSearchBtn.innerHTML = '<i data-lucide="search" class="w-3 h-3"></i> ' + t('semantic.runSearch');
|
||||
if (window.lucide) lucide.createIcons();
|
||||
} catch (err) {
|
||||
console.error('[Semantic Search Test] Error:', err);
|
||||
resultContent.textContent = t('common.exception') + ': ' + err.message;
|
||||
resultsDiv.classList.remove('hidden');
|
||||
showRefreshToast(t('common.error') + ': ' + err.message, 'error');
|
||||
runSemanticSearchBtn.disabled = false;
|
||||
runSemanticSearchBtn.innerHTML = '<i data-lucide="search" class="w-3 h-3"></i> ' + t('semantic.runSearch');
|
||||
if (window.lucide) lucide.createIcons();
|
||||
}
|
||||
};
|
||||
}
|
||||
}, 100);
|
||||
|
||||
var handleEscape = function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
closeSemanticSettingsModal();
|
||||
@@ -713,37 +1161,37 @@ function toggleLlmEnhancement(enabled) {
|
||||
}
|
||||
|
||||
renderCliStatus();
|
||||
showRefreshToast('LLM Enhancement ' + (enabled ? 'enabled' : 'disabled'), 'success');
|
||||
showRefreshToast(t('semantic.llmEnhancement') + ' ' + (enabled ? t('semantic.enabled') : t('semantic.disabled')), 'success');
|
||||
}
|
||||
|
||||
function updateLlmTool(tool) {
|
||||
llmEnhancementSettings.tool = tool;
|
||||
localStorage.setItem('ccw-llm-enhancement-tool', tool);
|
||||
showRefreshToast('Primary LLM tool set to ' + tool, 'success');
|
||||
showRefreshToast(t('semantic.toolSetTo') + ' ' + tool, 'success');
|
||||
}
|
||||
|
||||
function updateLlmFallback(tool) {
|
||||
llmEnhancementSettings.fallbackTool = tool;
|
||||
localStorage.setItem('ccw-llm-enhancement-fallback', tool);
|
||||
showRefreshToast('Fallback tool set to ' + (tool || 'none'), 'success');
|
||||
showRefreshToast(t('semantic.fallbackSetTo') + ' ' + (tool || t('semantic.none')), 'success');
|
||||
}
|
||||
|
||||
function updateLlmBatchSize(size) {
|
||||
llmEnhancementSettings.batchSize = parseInt(size, 10);
|
||||
localStorage.setItem('ccw-llm-enhancement-batch-size', size);
|
||||
showRefreshToast('Batch size set to ' + size + ' files', 'success');
|
||||
showRefreshToast(t('semantic.batchSetTo') + ' ' + size + ' ' + t('semantic.files'), 'success');
|
||||
}
|
||||
|
||||
function updateLlmTimeout(ms) {
|
||||
llmEnhancementSettings.timeoutMs = parseInt(ms, 10);
|
||||
localStorage.setItem('ccw-llm-enhancement-timeout', ms);
|
||||
var mins = parseInt(ms, 10) / 60000;
|
||||
showRefreshToast('Timeout set to ' + mins + ' minute' + (mins > 1 ? 's' : ''), 'success');
|
||||
showRefreshToast(t('semantic.timeoutSetTo') + ' ' + mins + ' ' + (mins > 1 ? t('semantic.minutes') : t('semantic.minute')), 'success');
|
||||
}
|
||||
|
||||
async function runEnhanceCommand() {
|
||||
if (!llmEnhancementSettings.enabled) {
|
||||
showRefreshToast('Please enable LLM Enhancement first', 'warning');
|
||||
showRefreshToast(t('semantic.enableFirst'), 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -142,6 +142,59 @@ async function removeMcpServerFromProject(serverName) {
|
||||
}
|
||||
}
|
||||
|
||||
async function addGlobalMcpServer(serverName, serverConfig) {
|
||||
try {
|
||||
const response = await fetch('/api/mcp-add-global-server', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
serverName: serverName,
|
||||
serverConfig: serverConfig
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to add global MCP server');
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
await loadMcpConfig();
|
||||
renderMcpManager();
|
||||
showRefreshToast(`Global MCP server "${serverName}" added`, 'success');
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error('Failed to add global MCP server:', err);
|
||||
showRefreshToast(`Failed to add global MCP server: ${err.message}`, 'error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function removeGlobalMcpServer(serverName) {
|
||||
try {
|
||||
const response = await fetch('/api/mcp-remove-global-server', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
serverName: serverName
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to remove global MCP server');
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
await loadMcpConfig();
|
||||
renderMcpManager();
|
||||
showRefreshToast(`Global MCP server "${serverName}" removed`, 'success');
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error('Failed to remove global MCP server:', err);
|
||||
showRefreshToast(`Failed to remove global MCP server: ${err.message}`, 'error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Badge Update ==========
|
||||
function updateMcpBadge() {
|
||||
const badge = document.getElementById('badgeMcpServers');
|
||||
@@ -260,8 +313,40 @@ function isServerInCurrentProject(serverName) {
|
||||
return serverName in servers;
|
||||
}
|
||||
|
||||
// Generate install command for MCP server
|
||||
function generateMcpInstallCommand(serverName, serverConfig, scope = 'project') {
|
||||
const command = serverConfig.command || '';
|
||||
const args = serverConfig.args || [];
|
||||
|
||||
// Check if it's an npx-based package
|
||||
if (command === 'npx' && args.length > 0) {
|
||||
const packageName = args[0];
|
||||
// Check if it's a scoped package or standard package
|
||||
if (packageName.startsWith('@') || packageName.includes('/')) {
|
||||
const scopeFlag = scope === 'global' ? ' --global' : '';
|
||||
return `claude mcp add ${packageName}${scopeFlag}`;
|
||||
}
|
||||
}
|
||||
|
||||
// For custom servers, return JSON configuration
|
||||
const scopeFlag = scope === 'global' ? ' --global' : '';
|
||||
return `claude mcp add ${serverName}${scopeFlag}`;
|
||||
}
|
||||
|
||||
// Copy install command to clipboard
|
||||
async function copyMcpInstallCommand(serverName, serverConfig, scope = 'project') {
|
||||
try {
|
||||
const command = generateMcpInstallCommand(serverName, serverConfig, scope);
|
||||
await navigator.clipboard.writeText(command);
|
||||
showRefreshToast(t('mcp.installCmdCopied'), 'success');
|
||||
} catch (error) {
|
||||
console.error('Failed to copy install command:', error);
|
||||
showRefreshToast(t('mcp.installCmdFailed'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ========== MCP Create Modal ==========
|
||||
function openMcpCreateModal() {
|
||||
function openMcpCreateModal(scope = 'project') {
|
||||
const modal = document.getElementById('mcpCreateModal');
|
||||
if (modal) {
|
||||
modal.classList.remove('hidden');
|
||||
@@ -276,6 +361,11 @@ function openMcpCreateModal() {
|
||||
// Clear JSON input
|
||||
document.getElementById('mcpServerJson').value = '';
|
||||
document.getElementById('mcpJsonPreview').classList.add('hidden');
|
||||
// Set scope (global or project)
|
||||
const scopeSelect = document.getElementById('mcpServerScope');
|
||||
if (scopeSelect) {
|
||||
scopeSelect.value = scope;
|
||||
}
|
||||
// Focus on name input
|
||||
document.getElementById('mcpServerName').focus();
|
||||
// Setup JSON input listener
|
||||
@@ -427,6 +517,8 @@ async function submitMcpCreateFromForm() {
|
||||
const command = document.getElementById('mcpServerCommand').value.trim();
|
||||
const argsText = document.getElementById('mcpServerArgs').value.trim();
|
||||
const envText = document.getElementById('mcpServerEnv').value.trim();
|
||||
const scopeSelect = document.getElementById('mcpServerScope');
|
||||
const scope = scopeSelect ? scopeSelect.value : 'project';
|
||||
|
||||
// Validate required fields
|
||||
if (!name) {
|
||||
@@ -471,7 +563,7 @@ async function submitMcpCreateFromForm() {
|
||||
serverConfig.env = env;
|
||||
}
|
||||
|
||||
await createMcpServerWithConfig(name, serverConfig);
|
||||
await createMcpServerWithConfig(name, serverConfig, scope);
|
||||
}
|
||||
|
||||
async function submitMcpCreateFromJson() {
|
||||
@@ -550,18 +642,30 @@ async function submitMcpCreateFromJson() {
|
||||
}
|
||||
}
|
||||
|
||||
async function createMcpServerWithConfig(name, serverConfig) {
|
||||
async function createMcpServerWithConfig(name, serverConfig, scope = 'project') {
|
||||
// Submit to API
|
||||
try {
|
||||
const response = await fetch('/api/mcp-copy-server', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
projectPath: projectPath,
|
||||
serverName: name,
|
||||
serverConfig: serverConfig
|
||||
})
|
||||
});
|
||||
let response;
|
||||
if (scope === 'global') {
|
||||
response = await fetch('/api/mcp-add-global-server', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
serverName: name,
|
||||
serverConfig: serverConfig
|
||||
})
|
||||
});
|
||||
} else {
|
||||
response = await fetch('/api/mcp-copy-server', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
projectPath: projectPath,
|
||||
serverName: name,
|
||||
serverConfig: serverConfig
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if (!response.ok) throw new Error('Failed to create MCP server');
|
||||
|
||||
@@ -570,7 +674,8 @@ async function createMcpServerWithConfig(name, serverConfig) {
|
||||
closeMcpCreateModal();
|
||||
await loadMcpConfig();
|
||||
renderMcpManager();
|
||||
showRefreshToast(`MCP server "${name}" created successfully`, 'success');
|
||||
const scopeLabel = scope === 'global' ? 'global' : 'project';
|
||||
showRefreshToast(`MCP server "${name}" created in ${scopeLabel} scope`, 'success');
|
||||
} else {
|
||||
showRefreshToast(result.error || 'Failed to create MCP server', 'error');
|
||||
}
|
||||
|
||||
@@ -112,6 +112,8 @@ function initNavigation() {
|
||||
renderSkillsManager();
|
||||
} else if (currentView === 'rules-manager') {
|
||||
renderRulesManager();
|
||||
} else if (currentView === 'claude-manager') {
|
||||
renderClaudeManager();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -144,6 +146,8 @@ function updateContentTitle() {
|
||||
titleEl.textContent = t('title.skillsManager');
|
||||
} else if (currentView === 'rules-manager') {
|
||||
titleEl.textContent = t('title.rulesManager');
|
||||
} else if (currentView === 'claude-manager') {
|
||||
titleEl.textContent = t('title.claudeManager');
|
||||
} else if (currentView === 'liteTasks') {
|
||||
const names = { 'lite-plan': t('title.litePlanSessions'), 'lite-fix': t('title.liteFixSessions') };
|
||||
titleEl.textContent = names[currentLiteType] || t('title.liteTasks');
|
||||
|
||||
@@ -214,13 +214,13 @@ function handleNotification(data) {
|
||||
if (typeof handleMemoryUpdated === 'function') {
|
||||
handleMemoryUpdated(payload);
|
||||
}
|
||||
// Force refresh of memory view if active
|
||||
if (getCurrentView && getCurrentView() === 'memory') {
|
||||
if (typeof loadMemoryStats === 'function') {
|
||||
loadMemoryStats().then(function() {
|
||||
if (typeof renderHotspotsColumn === 'function') renderHotspotsColumn();
|
||||
});
|
||||
}
|
||||
// Force refresh of memory view
|
||||
if (typeof loadMemoryStats === 'function') {
|
||||
loadMemoryStats().then(function() {
|
||||
if (typeof renderHotspotsColumn === 'function') renderHotspotsColumn();
|
||||
}).catch(function(err) {
|
||||
console.error('[Memory] Failed to refresh stats:', err);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -254,15 +254,142 @@ function handleNotification(data) {
|
||||
'Memory'
|
||||
);
|
||||
}
|
||||
// Refresh Active Memory status if on memory view
|
||||
if (getCurrentView && getCurrentView() === 'memory') {
|
||||
if (typeof loadActiveMemoryStatus === 'function') {
|
||||
loadActiveMemoryStatus();
|
||||
}
|
||||
// Refresh Active Memory status
|
||||
if (typeof loadActiveMemoryStatus === 'function') {
|
||||
loadActiveMemoryStatus().catch(function(err) {
|
||||
console.error('[Active Memory] Failed to refresh status:', err);
|
||||
});
|
||||
}
|
||||
console.log('[Active Memory] Sync completed:', payload);
|
||||
break;
|
||||
|
||||
case 'CLAUDE_FILE_SYNCED':
|
||||
// Handle CLAUDE.md file sync completion
|
||||
if (typeof addGlobalNotification === 'function') {
|
||||
const { path, level, tool, mode } = payload;
|
||||
const fileName = path.split(/[/\\]/).pop();
|
||||
addGlobalNotification(
|
||||
'success',
|
||||
`${fileName} synced`,
|
||||
{
|
||||
'Level': level,
|
||||
'Tool': tool,
|
||||
'Mode': mode,
|
||||
'Time': new Date(payload.timestamp).toLocaleTimeString()
|
||||
},
|
||||
'CLAUDE.md'
|
||||
);
|
||||
}
|
||||
// Refresh file list
|
||||
if (typeof loadClaudeFiles === 'function') {
|
||||
loadClaudeFiles().then(() => {
|
||||
// Re-render the view to show updated content
|
||||
if (typeof renderClaudeManager === 'function') {
|
||||
renderClaudeManager();
|
||||
}
|
||||
}).catch(err => console.error('[CLAUDE.md] Failed to refresh files:', err));
|
||||
}
|
||||
console.log('[CLAUDE.md] Sync completed:', payload);
|
||||
break;
|
||||
|
||||
case 'CLI_TOOL_INSTALLED':
|
||||
// Handle CLI tool installation completion
|
||||
if (typeof addGlobalNotification === 'function') {
|
||||
const { tool } = payload;
|
||||
addGlobalNotification(
|
||||
'success',
|
||||
`${tool} installed successfully`,
|
||||
{
|
||||
'Tool': tool,
|
||||
'Time': new Date(payload.timestamp).toLocaleTimeString()
|
||||
},
|
||||
'CLI Tools'
|
||||
);
|
||||
}
|
||||
// Refresh CLI manager
|
||||
if (typeof loadCliToolStatus === 'function') {
|
||||
loadCliToolStatus().then(() => {
|
||||
if (typeof renderToolsSection === 'function') {
|
||||
renderToolsSection();
|
||||
}
|
||||
}).catch(err => console.error('[CLI Tools] Failed to refresh status:', err));
|
||||
}
|
||||
console.log('[CLI Tools] Installation completed:', payload);
|
||||
break;
|
||||
|
||||
case 'CLI_TOOL_UNINSTALLED':
|
||||
// Handle CLI tool uninstallation completion
|
||||
if (typeof addGlobalNotification === 'function') {
|
||||
const { tool } = payload;
|
||||
addGlobalNotification(
|
||||
'success',
|
||||
`${tool} uninstalled successfully`,
|
||||
{
|
||||
'Tool': tool,
|
||||
'Time': new Date(payload.timestamp).toLocaleTimeString()
|
||||
},
|
||||
'CLI Tools'
|
||||
);
|
||||
}
|
||||
// Refresh CLI manager
|
||||
if (typeof loadCliToolStatus === 'function') {
|
||||
loadCliToolStatus().then(() => {
|
||||
if (typeof renderToolsSection === 'function') {
|
||||
renderToolsSection();
|
||||
}
|
||||
}).catch(err => console.error('[CLI Tools] Failed to refresh status:', err));
|
||||
}
|
||||
console.log('[CLI Tools] Uninstallation completed:', payload);
|
||||
break;
|
||||
|
||||
case 'CODEXLENS_INSTALLED':
|
||||
// Handle CodexLens installation completion
|
||||
if (typeof addGlobalNotification === 'function') {
|
||||
const { version } = payload;
|
||||
addGlobalNotification(
|
||||
'success',
|
||||
`CodexLens installed successfully`,
|
||||
{
|
||||
'Version': version || 'latest',
|
||||
'Time': new Date(payload.timestamp).toLocaleTimeString()
|
||||
},
|
||||
'CodexLens'
|
||||
);
|
||||
}
|
||||
// Refresh CLI status if active
|
||||
if (typeof loadCodexLensStatus === 'function') {
|
||||
loadCodexLensStatus().then(() => {
|
||||
if (typeof renderCliStatus === 'function') {
|
||||
renderCliStatus();
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log('[CodexLens] Installation completed:', payload);
|
||||
break;
|
||||
|
||||
case 'CODEXLENS_UNINSTALLED':
|
||||
// Handle CodexLens uninstallation completion
|
||||
if (typeof addGlobalNotification === 'function') {
|
||||
addGlobalNotification(
|
||||
'success',
|
||||
`CodexLens uninstalled successfully`,
|
||||
{
|
||||
'Time': new Date(payload.timestamp).toLocaleTimeString()
|
||||
},
|
||||
'CodexLens'
|
||||
);
|
||||
}
|
||||
// Refresh CLI status if active
|
||||
if (typeof loadCodexLensStatus === 'function') {
|
||||
loadCodexLensStatus().then(() => {
|
||||
if (typeof renderCliStatus === 'function') {
|
||||
renderCliStatus();
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log('[CodexLens] Uninstallation completed:', payload);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('[WS] Unknown notification type:', type);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user