mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-13 02:41:50 +08:00
feat: Implement venv status caching with TTL and clear cache on install/uninstall
This commit is contained in:
@@ -88,10 +88,15 @@ export async function handleCodexLensRoutes(ctx: RouteContext): Promise<boolean>
|
|||||||
res.end(JSON.stringify({ success: true, indexes: [], totalSize: 0, totalSizeFormatted: '0 B' }));
|
res.end(JSON.stringify({ success: true, indexes: [], totalSize: 0, totalSizeFormatted: '0 B' }));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Get config for index directory path
|
|
||||||
const configResult = await executeCodexLens(['config', '--json']);
|
// Execute all CLI commands in parallel
|
||||||
|
const [configResult, projectsResult, statusResult] = await Promise.all([
|
||||||
|
executeCodexLens(['config', '--json']),
|
||||||
|
executeCodexLens(['projects', 'list', '--json']),
|
||||||
|
executeCodexLens(['status', '--json'])
|
||||||
|
]);
|
||||||
|
|
||||||
let indexDir = '';
|
let indexDir = '';
|
||||||
|
|
||||||
if (configResult.success) {
|
if (configResult.success) {
|
||||||
try {
|
try {
|
||||||
const config = extractJSON(configResult.output);
|
const config = extractJSON(configResult.output);
|
||||||
@@ -104,8 +109,6 @@ export async function handleCodexLensRoutes(ctx: RouteContext): Promise<boolean>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get project list using 'projects list' command
|
|
||||||
const projectsResult = await executeCodexLens(['projects', 'list', '--json']);
|
|
||||||
let indexes: any[] = [];
|
let indexes: any[] = [];
|
||||||
let totalSize = 0;
|
let totalSize = 0;
|
||||||
let vectorIndexCount = 0;
|
let vectorIndexCount = 0;
|
||||||
@@ -115,7 +118,8 @@ export async function handleCodexLensRoutes(ctx: RouteContext): Promise<boolean>
|
|||||||
try {
|
try {
|
||||||
const projectsData = extractJSON(projectsResult.output);
|
const projectsData = extractJSON(projectsResult.output);
|
||||||
if (projectsData.success && Array.isArray(projectsData.result)) {
|
if (projectsData.success && Array.isArray(projectsData.result)) {
|
||||||
const { statSync, existsSync } = await import('fs');
|
const { stat, readdir } = await import('fs/promises');
|
||||||
|
const { existsSync } = await import('fs');
|
||||||
const { basename, join } = await import('path');
|
const { basename, join } = await import('path');
|
||||||
|
|
||||||
for (const project of projectsData.result) {
|
for (const project of projectsData.result) {
|
||||||
@@ -136,15 +140,14 @@ export async function handleCodexLensRoutes(ctx: RouteContext): Promise<boolean>
|
|||||||
// Try to get actual index size from index_root
|
// Try to get actual index size from index_root
|
||||||
if (project.index_root && existsSync(project.index_root)) {
|
if (project.index_root && existsSync(project.index_root)) {
|
||||||
try {
|
try {
|
||||||
const { readdirSync } = await import('fs');
|
const files = await readdir(project.index_root);
|
||||||
const files = readdirSync(project.index_root);
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
try {
|
try {
|
||||||
const filePath = join(project.index_root, file);
|
const filePath = join(project.index_root, file);
|
||||||
const stat = statSync(filePath);
|
const fileStat = await stat(filePath);
|
||||||
projectSize += stat.size;
|
projectSize += fileStat.size;
|
||||||
if (!lastModified || stat.mtime > lastModified) {
|
if (!lastModified || fileStat.mtime > lastModified) {
|
||||||
lastModified = stat.mtime;
|
lastModified = fileStat.mtime;
|
||||||
}
|
}
|
||||||
// Check for vector/embedding files
|
// Check for vector/embedding files
|
||||||
if (file.includes('vector') || file.includes('embedding') ||
|
if (file.includes('vector') || file.includes('embedding') ||
|
||||||
@@ -194,8 +197,7 @@ export async function handleCodexLensRoutes(ctx: RouteContext): Promise<boolean>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also get summary stats from status command
|
// Parse summary stats from status command (already fetched in parallel)
|
||||||
const statusResult = await executeCodexLens(['status', '--json']);
|
|
||||||
let statusSummary: any = {};
|
let statusSummary: any = {};
|
||||||
|
|
||||||
if (statusResult.success) {
|
if (statusResult.success) {
|
||||||
@@ -250,6 +252,71 @@ export async function handleCodexLensRoutes(ctx: RouteContext): Promise<boolean>
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// API: CodexLens Dashboard Init - Aggregated endpoint for page initialization
|
||||||
|
if (pathname === '/api/codexlens/dashboard-init') {
|
||||||
|
try {
|
||||||
|
const venvStatus = await checkVenvStatus();
|
||||||
|
|
||||||
|
if (!venvStatus.ready) {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({
|
||||||
|
installed: false,
|
||||||
|
status: venvStatus,
|
||||||
|
config: { index_dir: '~/.codexlens/indexes', index_count: 0 },
|
||||||
|
semantic: { available: false }
|
||||||
|
}));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parallel fetch all initialization data
|
||||||
|
const [configResult, statusResult, semanticStatus] = await Promise.all([
|
||||||
|
executeCodexLens(['config', '--json']),
|
||||||
|
executeCodexLens(['status', '--json']),
|
||||||
|
checkSemanticStatus()
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Parse config
|
||||||
|
let config = { index_dir: '~/.codexlens/indexes', index_count: 0 };
|
||||||
|
if (configResult.success) {
|
||||||
|
try {
|
||||||
|
const configData = extractJSON(configResult.output);
|
||||||
|
if (configData.success && configData.result) {
|
||||||
|
config.index_dir = configData.result.index_dir || configData.result.index_root || config.index_dir;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[CodexLens] Failed to parse config for dashboard init:', e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse status
|
||||||
|
let statusData: any = {};
|
||||||
|
if (statusResult.success) {
|
||||||
|
try {
|
||||||
|
const status = extractJSON(statusResult.output);
|
||||||
|
if (status.success && status.result) {
|
||||||
|
config.index_count = status.result.projects_count || 0;
|
||||||
|
statusData = status.result;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[CodexLens] Failed to parse status for dashboard init:', e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({
|
||||||
|
installed: true,
|
||||||
|
status: venvStatus,
|
||||||
|
config,
|
||||||
|
semantic: semanticStatus,
|
||||||
|
statusData
|
||||||
|
}));
|
||||||
|
} catch (err) {
|
||||||
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ success: false, error: err.message }));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// API: CodexLens Bootstrap (Install)
|
// API: CodexLens Bootstrap (Install)
|
||||||
if (pathname === '/api/codexlens/bootstrap' && req.method === 'POST') {
|
if (pathname === '/api/codexlens/bootstrap' && req.method === 'POST') {
|
||||||
handlePostRequest(req, res, async () => {
|
handlePostRequest(req, res, async () => {
|
||||||
|
|||||||
@@ -33,6 +33,14 @@ const VENV_PYTHON =
|
|||||||
let bootstrapChecked = false;
|
let bootstrapChecked = false;
|
||||||
let bootstrapReady = false;
|
let bootstrapReady = false;
|
||||||
|
|
||||||
|
// Venv status cache with TTL
|
||||||
|
interface VenvStatusCache {
|
||||||
|
status: ReadyStatus;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
let venvStatusCache: VenvStatusCache | null = null;
|
||||||
|
const VENV_STATUS_TTL = 5 * 60 * 1000; // 5 minutes TTL
|
||||||
|
|
||||||
// Track running indexing process for cancellation
|
// Track running indexing process for cancellation
|
||||||
let currentIndexingProcess: ReturnType<typeof spawn> | null = null;
|
let currentIndexingProcess: ReturnType<typeof spawn> | null = null;
|
||||||
let currentIndexingAborted = false;
|
let currentIndexingAborted = false;
|
||||||
@@ -116,6 +124,13 @@ interface ProgressInfo {
|
|||||||
totalFiles?: number;
|
totalFiles?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear venv status cache (call after install/uninstall operations)
|
||||||
|
*/
|
||||||
|
function clearVenvStatusCache(): void {
|
||||||
|
venvStatusCache = null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect available Python 3 executable
|
* Detect available Python 3 executable
|
||||||
* @returns Python executable command
|
* @returns Python executable command
|
||||||
@@ -138,17 +153,27 @@ function getSystemPython(): string {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if CodexLens venv exists and has required packages
|
* Check if CodexLens venv exists and has required packages
|
||||||
|
* @param force - Force refresh cache (default: false)
|
||||||
* @returns Ready status
|
* @returns Ready status
|
||||||
*/
|
*/
|
||||||
async function checkVenvStatus(): Promise<ReadyStatus> {
|
async function checkVenvStatus(force = false): Promise<ReadyStatus> {
|
||||||
|
// Use cached result if available and not expired
|
||||||
|
if (!force && venvStatusCache && (Date.now() - venvStatusCache.timestamp < VENV_STATUS_TTL)) {
|
||||||
|
return venvStatusCache.status;
|
||||||
|
}
|
||||||
|
|
||||||
// Check venv exists
|
// Check venv exists
|
||||||
if (!existsSync(CODEXLENS_VENV)) {
|
if (!existsSync(CODEXLENS_VENV)) {
|
||||||
return { ready: false, error: 'Venv not found' };
|
const result = { ready: false, error: 'Venv not found' };
|
||||||
|
venvStatusCache = { status: result, timestamp: Date.now() };
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check python executable exists
|
// Check python executable exists
|
||||||
if (!existsSync(VENV_PYTHON)) {
|
if (!existsSync(VENV_PYTHON)) {
|
||||||
return { ready: false, error: 'Python executable not found in venv' };
|
const result = { ready: false, error: 'Python executable not found in venv' };
|
||||||
|
venvStatusCache = { status: result, timestamp: Date.now() };
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check codexlens is importable
|
// Check codexlens is importable
|
||||||
@@ -169,15 +194,21 @@ async function checkVenvStatus(): Promise<ReadyStatus> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
child.on('close', (code) => {
|
child.on('close', (code) => {
|
||||||
|
let result: ReadyStatus;
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
resolve({ ready: true, version: stdout.trim() });
|
result = { ready: true, version: stdout.trim() };
|
||||||
} else {
|
} else {
|
||||||
resolve({ ready: false, error: `CodexLens not installed: ${stderr}` });
|
result = { ready: false, error: `CodexLens not installed: ${stderr}` };
|
||||||
}
|
}
|
||||||
|
// Cache the result
|
||||||
|
venvStatusCache = { status: result, timestamp: Date.now() };
|
||||||
|
resolve(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
child.on('error', (err) => {
|
child.on('error', (err) => {
|
||||||
resolve({ ready: false, error: `Failed to check venv: ${err.message}` });
|
const result = { ready: false, error: `Failed to check venv: ${err.message}` };
|
||||||
|
venvStatusCache = { status: result, timestamp: Date.now() };
|
||||||
|
resolve(result);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -581,6 +612,8 @@ async function bootstrapVenv(): Promise<BootstrapResult> {
|
|||||||
execSync(`"${pipPath}" install codexlens`, { stdio: 'inherit' });
|
execSync(`"${pipPath}" install codexlens`, { stdio: 'inherit' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear cache after successful installation
|
||||||
|
clearVenvStatusCache();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { success: false, error: `Failed to install codexlens: ${(err as Error).message}` };
|
return { success: false, error: `Failed to install codexlens: ${(err as Error).message}` };
|
||||||
@@ -1300,6 +1333,7 @@ async function uninstallCodexLens(): Promise<BootstrapResult> {
|
|||||||
// Reset bootstrap cache
|
// Reset bootstrap cache
|
||||||
bootstrapChecked = false;
|
bootstrapChecked = false;
|
||||||
bootstrapReady = false;
|
bootstrapReady = false;
|
||||||
|
clearVenvStatusCache();
|
||||||
|
|
||||||
console.log('[CodexLens] CodexLens uninstalled successfully');
|
console.log('[CodexLens] CodexLens uninstalled successfully');
|
||||||
return { success: true, message: 'CodexLens uninstalled successfully' };
|
return { success: true, message: 'CodexLens uninstalled successfully' };
|
||||||
|
|||||||
Reference in New Issue
Block a user