From 11d81872582b7a6a711cf1ea8dac9bf171664994 Mon Sep 17 00:00:00 2001 From: catlog22 Date: Sun, 21 Dec 2025 23:52:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=8F=96=E6=B6=88?= =?UTF-8?q?=E7=B4=A2=E5=BC=95=E5=92=8C=E6=A3=80=E6=9F=A5=E7=B4=A2=E5=BC=95?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E7=9A=84API=EF=BC=8C=E4=BC=98=E5=8C=96CodexL?= =?UTF-8?q?ens=E7=9A=84=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ccw/src/core/routes/codexlens-routes.ts | 29 +++++++- .../dashboard-js/views/codexlens-manager.js | 58 ++++++++++++++++ ccw/src/tools/codex-lens.ts | 69 ++++++++++++++++++- 3 files changed, 154 insertions(+), 2 deletions(-) diff --git a/ccw/src/core/routes/codexlens-routes.ts b/ccw/src/core/routes/codexlens-routes.ts index 49e723e1..ff1754ed 100644 --- a/ccw/src/core/routes/codexlens-routes.ts +++ b/ccw/src/core/routes/codexlens-routes.ts @@ -10,7 +10,9 @@ import { executeCodexLens, checkSemanticStatus, installSemantic, - uninstallCodexLens + uninstallCodexLens, + cancelIndexing, + isIndexingInProgress } from '../../tools/codex-lens.js'; import type { ProgressInfo } from '../../tools/codex-lens.js'; @@ -449,6 +451,31 @@ export async function handleCodexLensRoutes(ctx: RouteContext): Promise return true; } + // API: Cancel CodexLens Indexing + if (pathname === '/api/codexlens/cancel' && req.method === 'POST') { + const result = cancelIndexing(); + + // Broadcast cancellation event + if (result.success) { + broadcastToClients({ + type: 'CODEXLENS_INDEX_PROGRESS', + payload: { stage: 'cancelled', message: 'Indexing cancelled by user', percent: 0 } + }); + } + + res.writeHead(result.success ? 200 : 400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(result)); + return true; + } + + // API: Check if indexing is in progress + if (pathname === '/api/codexlens/indexing-status') { + const inProgress = isIndexingInProgress(); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ success: true, inProgress })); + return true; + } + // API: CodexLens Semantic Search Status if (pathname === '/api/codexlens/semantic/status') { const status = await checkSemanticStatus(); diff --git a/ccw/src/templates/dashboard-js/views/codexlens-manager.js b/ccw/src/templates/dashboard-js/views/codexlens-manager.js index 9cb049f1..74169108 100644 --- a/ccw/src/templates/dashboard-js/views/codexlens-manager.js +++ b/ccw/src/templates/dashboard-js/views/codexlens-manager.js @@ -660,6 +660,9 @@ async function initCodexLensIndex(indexType, embeddingModel) { '
' + '' + '' + + '' + '' + @@ -816,6 +819,61 @@ function closeCodexLensIndexModal() { } } +/** + * Cancel the running indexing process + */ +async function cancelCodexLensIndexing() { + var cancelBtn = document.getElementById('codexlensIndexCancelBtn'); + var statusText = document.getElementById('codexlensIndexStatus'); + + // Disable button to prevent double-click + if (cancelBtn) { + cancelBtn.disabled = true; + cancelBtn.textContent = t('common.canceling') || 'Canceling...'; + } + + try { + var response = await fetch('/api/codexlens/cancel', { + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }); + + var result = await response.json(); + + if (result.success) { + if (statusText) statusText.textContent = t('codexlens.indexCanceled') || 'Indexing canceled'; + showRefreshToast(t('codexlens.indexCanceled') || 'Indexing canceled', 'info'); + + // Close the modal after a short delay + setTimeout(function() { + closeCodexLensIndexModal(); + // Refresh status + if (typeof loadCodexLensStatus === 'function') { + loadCodexLensStatus().then(function() { + renderToolsSection(); + if (window.lucide) lucide.createIcons(); + }); + } + }, 1000); + } else { + showRefreshToast(t('codexlens.cancelFailed') + ': ' + result.error, 'error'); + // Re-enable button on failure + if (cancelBtn) { + cancelBtn.disabled = false; + cancelBtn.textContent = t('common.cancel'); + } + } + } catch (err) { + console.error('[CodexLens] Cancel error:', err); + showRefreshToast(t('common.error') + ': ' + err.message, 'error'); + // Re-enable button on error + if (cancelBtn) { + cancelBtn.disabled = false; + cancelBtn.textContent = t('common.cancel'); + } + } +} + /** * Install CodexLens */ diff --git a/ccw/src/tools/codex-lens.ts b/ccw/src/tools/codex-lens.ts index 3088fb0c..84d2ccbe 100644 --- a/ccw/src/tools/codex-lens.ts +++ b/ccw/src/tools/codex-lens.ts @@ -33,6 +33,10 @@ const VENV_PYTHON = let bootstrapChecked = false; let bootstrapReady = false; +// Track running indexing process for cancellation +let currentIndexingProcess: ReturnType | null = null; +let currentIndexingAborted = false; + // Define Zod schema for validation const ParamsSchema = z.object({ action: z.enum([ @@ -510,6 +514,13 @@ async function executeCodexLens(args: string[], options: ExecuteOptions = {}): P timeout, }); + // Track indexing process for cancellation (only for init commands) + const isIndexingCommand = args.includes('init'); + if (isIndexingCommand) { + currentIndexingProcess = child; + currentIndexingAborted = false; + } + let stdout = ''; let stderr = ''; let stdoutLineBuffer = ''; @@ -525,6 +536,10 @@ async function executeCodexLens(args: string[], options: ExecuteOptions = {}): P clearTimeout(timeoutHandle); timeoutHandle = null; } + // Clear indexing process tracking + if (isIndexingCommand) { + currentIndexingProcess = null; + } resolve(result); }; @@ -1055,11 +1070,63 @@ async function uninstallCodexLens(): Promise { } } +/** + * Cancel the currently running indexing process + * @returns Result indicating if cancellation was successful + */ +function cancelIndexing(): { success: boolean; message?: string; error?: string } { + if (!currentIndexingProcess) { + return { success: false, error: 'No indexing process is currently running' }; + } + + if (currentIndexingAborted) { + return { success: false, error: 'Indexing process is already being cancelled' }; + } + + try { + currentIndexingAborted = true; + + // Send SIGTERM first for graceful shutdown + if (process.platform === 'win32') { + // On Windows, use taskkill to kill the process tree + const { execSync } = require('child_process'); + try { + execSync(`taskkill /pid ${currentIndexingProcess.pid} /T /F`, { stdio: 'ignore' }); + } catch { + // Process may have already exited + } + } else { + // On Unix, send SIGTERM + currentIndexingProcess.kill('SIGTERM'); + + // Force kill after 3 seconds if still running + setTimeout(() => { + if (currentIndexingProcess) { + currentIndexingProcess.kill('SIGKILL'); + } + }, 3000); + } + + console.log('[CodexLens] Indexing process cancelled'); + return { success: true, message: 'Indexing cancelled successfully' }; + } catch (err) { + return { success: false, error: `Failed to cancel indexing: ${(err as Error).message}` }; + } +} + +/** + * Check if an indexing process is currently running + * @returns True if indexing is in progress + */ +function isIndexingInProgress(): boolean { + return currentIndexingProcess !== null && !currentIndexingAborted; +} + // Export types export type { ProgressInfo, ExecuteOptions }; // Export for direct usage -export { ensureReady, executeCodexLens, checkVenvStatus, bootstrapVenv, checkSemanticStatus, installSemantic, uninstallCodexLens }; +export { ensureReady, executeCodexLens, checkVenvStatus, bootstrapVenv, checkSemanticStatus, installSemantic, uninstallCodexLens, cancelIndexing, isIndexingInProgress }; // Backward-compatible export for tests export const codexLensTool = {