claude-code-workflow 6.2.4 → 6.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/ccw/dist/core/lite-scanner-complete.d.ts.map +1 -1
  2. package/ccw/dist/core/lite-scanner-complete.js +4 -1
  3. package/ccw/dist/core/lite-scanner-complete.js.map +1 -1
  4. package/ccw/dist/core/lite-scanner.d.ts.map +1 -1
  5. package/ccw/dist/core/lite-scanner.js +4 -1
  6. package/ccw/dist/core/lite-scanner.js.map +1 -1
  7. package/ccw/dist/core/routes/claude-routes.d.ts.map +1 -1
  8. package/ccw/dist/core/routes/claude-routes.js +3 -5
  9. package/ccw/dist/core/routes/claude-routes.js.map +1 -1
  10. package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -1
  11. package/ccw/dist/core/routes/cli-routes.js +2 -1
  12. package/ccw/dist/core/routes/cli-routes.js.map +1 -1
  13. package/ccw/dist/core/routes/codexlens-routes.d.ts.map +1 -1
  14. package/ccw/dist/core/routes/codexlens-routes.js +31 -6
  15. package/ccw/dist/core/routes/codexlens-routes.js.map +1 -1
  16. package/ccw/dist/core/routes/rules-routes.d.ts.map +1 -1
  17. package/ccw/dist/core/routes/rules-routes.js +4 -3
  18. package/ccw/dist/core/routes/rules-routes.js.map +1 -1
  19. package/ccw/dist/core/routes/skills-routes.d.ts.map +1 -1
  20. package/ccw/dist/core/routes/skills-routes.js +124 -6
  21. package/ccw/dist/core/routes/skills-routes.js.map +1 -1
  22. package/ccw/dist/tools/cli-executor.d.ts +4 -1
  23. package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
  24. package/ccw/dist/tools/cli-executor.js +54 -2
  25. package/ccw/dist/tools/cli-executor.js.map +1 -1
  26. package/ccw/dist/tools/codex-lens.d.ts +20 -3
  27. package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
  28. package/ccw/dist/tools/codex-lens.js +166 -37
  29. package/ccw/dist/tools/codex-lens.js.map +1 -1
  30. package/ccw/package.json +1 -1
  31. package/ccw/src/core/lite-scanner-complete.ts +5 -1
  32. package/ccw/src/core/lite-scanner.ts +5 -1
  33. package/ccw/src/core/routes/claude-routes.ts +3 -5
  34. package/ccw/src/core/routes/cli-routes.ts +2 -1
  35. package/ccw/src/core/routes/codexlens-routes.ts +34 -6
  36. package/ccw/src/core/routes/rules-routes.ts +4 -3
  37. package/ccw/src/core/routes/skills-routes.ts +144 -6
  38. package/ccw/src/templates/dashboard-js/components/mcp-manager.js +7 -12
  39. package/ccw/src/templates/dashboard-js/i18n.js +167 -5
  40. package/ccw/src/templates/dashboard-js/views/claude-manager.js +18 -4
  41. package/ccw/src/templates/dashboard-js/views/cli-manager.js +5 -3
  42. package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +790 -25
  43. package/ccw/src/templates/dashboard-js/views/rules-manager.js +35 -6
  44. package/ccw/src/templates/dashboard-js/views/skills-manager.js +385 -21
  45. package/ccw/src/tools/cli-executor.ts +70 -2
  46. package/ccw/src/tools/codex-lens.ts +183 -35
  47. package/codex-lens/pyproject.toml +66 -48
  48. package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
  49. package/codex-lens/src/codexlens/cli/__pycache__/embedding_manager.cpython-313.pyc +0 -0
  50. package/codex-lens/src/codexlens/cli/__pycache__/model_manager.cpython-313.pyc +0 -0
  51. package/codex-lens/src/codexlens/cli/embedding_manager.py +3 -3
  52. package/codex-lens/src/codexlens/cli/model_manager.py +24 -2
  53. package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
  54. package/codex-lens/src/codexlens/search/hybrid_search.py +313 -313
  55. package/codex-lens/src/codexlens/semantic/__init__.py +76 -39
  56. package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
  57. package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
  58. package/codex-lens/src/codexlens/semantic/__pycache__/gpu_support.cpython-313.pyc +0 -0
  59. package/codex-lens/src/codexlens/semantic/__pycache__/ollama_backend.cpython-313.pyc +0 -0
  60. package/codex-lens/src/codexlens/semantic/embedder.py +244 -185
  61. package/codex-lens/src/codexlens/semantic/gpu_support.py +192 -0
  62. package/package.json +1 -1
@@ -126,10 +126,10 @@ function buildCodexLensConfigContent(config) {
126
126
  '<button class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md border border-border bg-background hover:bg-muted/50 transition-colors" onclick="cleanCodexLensIndexes()">' +
127
127
  '<i data-lucide="trash" class="w-3.5 h-3.5"></i> ' + t('codexlens.cleanAllIndexes') +
128
128
  '</button>' +
129
- '<button class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md border border-destructive/30 bg-destructive/5 text-destructive hover:bg-destructive/10 transition-colors" onclick="uninstallCodexLens()">' +
129
+ '<button class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md border border-destructive/30 bg-destructive/5 text-destructive hover:bg-destructive/10 transition-colors" onclick="uninstallCodexLensFromManager()">' +
130
130
  '<i data-lucide="trash-2" class="w-3.5 h-3.5"></i> ' + t('cli.uninstall') +
131
131
  '</button>'
132
- : '<button class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors" onclick="installCodexLens()">' +
132
+ : '<button class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors" onclick="installCodexLensFromManager()">' +
133
133
  '<i data-lucide="download" class="w-3.5 h-3.5"></i> ' + t('codexlens.installCodexLens') +
134
134
  '</button>') +
135
135
  '</div>' +
@@ -335,6 +335,26 @@ function initCodexLensConfigEvents(currentConfig) {
335
335
  // SEMANTIC DEPENDENCIES MANAGEMENT
336
336
  // ============================================================
337
337
 
338
+ // Store detected GPU info
339
+ var detectedGpuInfo = null;
340
+
341
+ /**
342
+ * Detect GPU support
343
+ */
344
+ async function detectGpuSupport() {
345
+ try {
346
+ var response = await fetch('/api/codexlens/gpu/detect');
347
+ var result = await response.json();
348
+ if (result.success) {
349
+ detectedGpuInfo = result;
350
+ return result;
351
+ }
352
+ } catch (err) {
353
+ console.error('GPU detection failed:', err);
354
+ }
355
+ return { mode: 'cpu', available: ['cpu'], info: 'CPU only' };
356
+ }
357
+
338
358
  /**
339
359
  * Load semantic dependencies status
340
360
  */
@@ -343,24 +363,58 @@ async function loadSemanticDepsStatus() {
343
363
  if (!container) return;
344
364
 
345
365
  try {
366
+ // Detect GPU support in parallel
367
+ var gpuPromise = detectGpuSupport();
346
368
  var response = await fetch('/api/codexlens/semantic/status');
347
369
  var result = await response.json();
370
+ var gpuInfo = await gpuPromise;
348
371
 
349
372
  if (result.available) {
373
+ // Build accelerator badge
374
+ var accelerator = result.accelerator || 'CPU';
375
+ var acceleratorIcon = 'cpu';
376
+ var acceleratorClass = 'bg-muted text-muted-foreground';
377
+
378
+ if (accelerator === 'CUDA') {
379
+ acceleratorIcon = 'zap';
380
+ acceleratorClass = 'bg-green-500/20 text-green-600';
381
+ } else if (accelerator === 'DirectML') {
382
+ acceleratorIcon = 'gpu-card';
383
+ acceleratorClass = 'bg-blue-500/20 text-blue-600';
384
+ } else if (accelerator === 'ROCm') {
385
+ acceleratorIcon = 'flame';
386
+ acceleratorClass = 'bg-red-500/20 text-red-600';
387
+ }
388
+
350
389
  container.innerHTML =
351
- '<div class="flex items-center gap-2 text-sm">' +
352
- '<i data-lucide="check-circle" class="w-4 h-4 text-success"></i>' +
353
- '<span>' + t('codexlens.semanticInstalled') + '</span>' +
354
- '<span class="text-muted-foreground">(' + (result.backend || 'fastembed') + ')</span>' +
390
+ '<div class="space-y-2">' +
391
+ '<div class="flex items-center gap-2 text-sm">' +
392
+ '<i data-lucide="check-circle" class="w-4 h-4 text-success"></i>' +
393
+ '<span>' + t('codexlens.semanticInstalled') + '</span>' +
394
+ '<span class="text-muted-foreground">(' + (result.backend || 'fastembed') + ')</span>' +
395
+ '</div>' +
396
+ '<div class="flex items-center gap-2">' +
397
+ '<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium ' + acceleratorClass + '">' +
398
+ '<i data-lucide="' + acceleratorIcon + '" class="w-3 h-3"></i>' +
399
+ accelerator +
400
+ '</span>' +
401
+ (result.providers && result.providers.length > 0
402
+ ? '<span class="text-xs text-muted-foreground">' + result.providers.join(', ') + '</span>'
403
+ : '') +
404
+ '</div>' +
355
405
  '</div>';
356
406
  } else {
407
+ // Build GPU mode options
408
+ var gpuOptions = buildGpuModeSelector(gpuInfo);
409
+
357
410
  container.innerHTML =
358
- '<div class="space-y-2">' +
411
+ '<div class="space-y-3">' +
359
412
  '<div class="flex items-center gap-2 text-sm text-muted-foreground">' +
360
413
  '<i data-lucide="alert-circle" class="w-4 h-4"></i>' +
361
414
  '<span>' + t('codexlens.semanticNotInstalled') + '</span>' +
362
415
  '</div>' +
363
- '<button class="btn-sm btn-outline" onclick="installSemanticDeps()">' +
416
+ gpuOptions +
417
+ '<button class="btn-sm btn-primary w-full" onclick="installSemanticDepsWithGpu()">' +
364
418
  '<i data-lucide="download" class="w-3 h-3"></i> ' + t('codexlens.installDeps') +
365
419
  '</button>' +
366
420
  '</div>';
@@ -373,21 +427,120 @@ async function loadSemanticDepsStatus() {
373
427
  }
374
428
 
375
429
  /**
376
- * Install semantic dependencies
430
+ * Build GPU mode selector HTML
377
431
  */
378
- async function installSemanticDeps() {
432
+ function buildGpuModeSelector(gpuInfo) {
433
+ var modes = [
434
+ {
435
+ id: 'cpu',
436
+ label: 'CPU',
437
+ desc: t('codexlens.cpuModeDesc') || 'Standard CPU processing',
438
+ icon: 'cpu',
439
+ available: true
440
+ },
441
+ {
442
+ id: 'directml',
443
+ label: 'DirectML',
444
+ desc: t('codexlens.directmlModeDesc') || 'Windows GPU (NVIDIA/AMD/Intel)',
445
+ icon: 'gpu-card',
446
+ available: gpuInfo.available.includes('directml'),
447
+ recommended: gpuInfo.mode === 'directml'
448
+ },
449
+ {
450
+ id: 'cuda',
451
+ label: 'CUDA',
452
+ desc: t('codexlens.cudaModeDesc') || 'NVIDIA GPU (requires CUDA Toolkit)',
453
+ icon: 'zap',
454
+ available: gpuInfo.available.includes('cuda'),
455
+ recommended: gpuInfo.mode === 'cuda'
456
+ }
457
+ ];
458
+
459
+ var html =
460
+ '<div class="space-y-2">' +
461
+ '<div class="text-xs font-medium text-muted-foreground flex items-center gap-1">' +
462
+ '<i data-lucide="settings" class="w-3 h-3"></i>' +
463
+ (t('codexlens.selectGpuMode') || 'Select acceleration mode') +
464
+ '</div>' +
465
+ '<div class="text-xs text-muted-foreground bg-muted/50 rounded px-2 py-1">' +
466
+ '<i data-lucide="info" class="w-3 h-3 inline"></i> ' + gpuInfo.info +
467
+ '</div>' +
468
+ '<div class="space-y-1">';
469
+
470
+ modes.forEach(function(mode) {
471
+ var isDisabled = !mode.available;
472
+ var isRecommended = mode.recommended;
473
+ var isDefault = mode.id === gpuInfo.mode;
474
+
475
+ html +=
476
+ '<label class="flex items-center gap-3 p-2 rounded border cursor-pointer hover:bg-muted/50 transition-colors ' +
477
+ (isDisabled ? 'opacity-50 cursor-not-allowed' : '') + '">' +
478
+ '<input type="radio" name="gpuMode" value="' + mode.id + '" ' +
479
+ (isDefault ? 'checked' : '') +
480
+ (isDisabled ? ' disabled' : '') +
481
+ ' class="accent-primary">' +
482
+ '<div class="flex-1">' +
483
+ '<div class="flex items-center gap-2">' +
484
+ '<i data-lucide="' + mode.icon + '" class="w-4 h-4"></i>' +
485
+ '<span class="font-medium text-sm">' + mode.label + '</span>' +
486
+ (isRecommended ? '<span class="text-xs bg-primary/20 text-primary px-1.5 py-0.5 rounded">' + (t('common.recommended') || 'Recommended') + '</span>' : '') +
487
+ (isDisabled ? '<span class="text-xs text-muted-foreground">(' + (t('common.unavailable') || 'Unavailable') + ')</span>' : '') +
488
+ '</div>' +
489
+ '<div class="text-xs text-muted-foreground">' + mode.desc + '</div>' +
490
+ '</div>' +
491
+ '</label>';
492
+ });
493
+
494
+ html +=
495
+ '</div>' +
496
+ '</div>';
497
+
498
+ return html;
499
+ }
500
+
501
+ /**
502
+ * Get selected GPU mode
503
+ */
504
+ function getSelectedGpuMode() {
505
+ var selected = document.querySelector('input[name="gpuMode"]:checked');
506
+ return selected ? selected.value : 'cpu';
507
+ }
508
+
509
+ /**
510
+ * Install semantic dependencies with GPU mode
511
+ */
512
+ async function installSemanticDepsWithGpu() {
379
513
  var container = document.getElementById('semanticDepsStatus');
380
514
  if (!container) return;
381
515
 
516
+ var gpuMode = getSelectedGpuMode();
517
+ var modeLabels = {
518
+ cpu: 'CPU',
519
+ cuda: 'NVIDIA CUDA',
520
+ directml: 'DirectML'
521
+ };
522
+
382
523
  container.innerHTML =
383
- '<div class="text-sm text-muted-foreground animate-pulse">' + t('codexlens.installingDeps') + '</div>';
524
+ '<div class="space-y-2">' +
525
+ '<div class="flex items-center gap-2 text-sm text-muted-foreground">' +
526
+ '<div class="animate-spin w-4 h-4 border-2 border-primary border-t-transparent rounded-full"></div>' +
527
+ '<span>' + t('codexlens.installingDeps') + '</span>' +
528
+ '</div>' +
529
+ '<div class="text-xs text-muted-foreground">' +
530
+ (t('codexlens.installingMode') || 'Installing with') + ': ' + modeLabels[gpuMode] +
531
+ '</div>' +
532
+ '</div>';
384
533
 
385
534
  try {
386
- var response = await fetch('/api/codexlens/semantic/install', { method: 'POST' });
535
+ var response = await fetch('/api/codexlens/semantic/install', {
536
+ method: 'POST',
537
+ headers: { 'Content-Type': 'application/json' },
538
+ body: JSON.stringify({ gpuMode: gpuMode })
539
+ });
387
540
  var result = await response.json();
388
541
 
389
542
  if (result.success) {
390
- showRefreshToast(t('codexlens.depsInstalled'), 'success');
543
+ showRefreshToast(t('codexlens.depsInstalled') + ' (' + modeLabels[gpuMode] + ')', 'success');
391
544
  await loadSemanticDepsStatus();
392
545
  await loadModelList();
393
546
  } else {
@@ -400,10 +553,164 @@ async function installSemanticDeps() {
400
553
  }
401
554
  }
402
555
 
556
+ /**
557
+ * Install semantic dependencies (legacy, defaults to CPU)
558
+ */
559
+ async function installSemanticDeps() {
560
+ await installSemanticDepsWithGpu();
561
+ }
562
+
403
563
  // ============================================================
404
564
  // MODEL MANAGEMENT
405
565
  // ============================================================
406
566
 
567
+ /**
568
+ * Build manual download guide HTML
569
+ */
570
+ function buildManualDownloadGuide() {
571
+ var modelData = [
572
+ { profile: 'code', name: 'jinaai/jina-embeddings-v2-base-code', size: '~150 MB' },
573
+ { profile: 'fast', name: 'BAAI/bge-small-en-v1.5', size: '~80 MB' },
574
+ { profile: 'balanced', name: 'mixedbread-ai/mxbai-embed-large-v1', size: '~600 MB' },
575
+ { profile: 'multilingual', name: 'intfloat/multilingual-e5-large', size: '~1 GB' }
576
+ ];
577
+
578
+ var html =
579
+ '<div class="mt-4 border-t pt-4">' +
580
+ '<button class="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground w-full" onclick="toggleManualDownloadGuide()" id="manualDownloadToggle">' +
581
+ '<i data-lucide="chevron-right" class="w-4 h-4 transition-transform" id="manualDownloadChevron"></i>' +
582
+ '<i data-lucide="terminal" class="w-4 h-4"></i>' +
583
+ '<span>' + (t('codexlens.manualDownloadGuide') || 'Manual Download Guide') + '</span>' +
584
+ '</button>' +
585
+ '<div id="manualDownloadContent" class="hidden mt-3 space-y-3">' +
586
+ // Method 1: CLI
587
+ '<div class="bg-muted/50 rounded-lg p-3 space-y-2">' +
588
+ '<div class="flex items-center gap-2 text-sm font-medium">' +
589
+ '<span class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-primary/20 text-primary text-xs">1</span>' +
590
+ '<span>' + (t('codexlens.cliMethod') || 'Command Line (Recommended)') + '</span>' +
591
+ '</div>' +
592
+ '<div class="text-xs text-muted-foreground mb-2">' +
593
+ (t('codexlens.cliMethodDesc') || 'Run in terminal with progress display:') +
594
+ '</div>' +
595
+ '<div class="space-y-1">';
596
+
597
+ modelData.forEach(function(m) {
598
+ html +=
599
+ '<div class="flex items-center justify-between bg-background rounded px-2 py-1.5">' +
600
+ '<code class="text-xs font-mono">codexlens model-download ' + m.profile + '</code>' +
601
+ '<button class="text-xs text-primary hover:underline" onclick="copyToClipboard(\'codexlens model-download ' + m.profile + '\')">' +
602
+ '<i data-lucide="copy" class="w-3 h-3"></i>' +
603
+ '</button>' +
604
+ '</div>';
605
+ });
606
+
607
+ html +=
608
+ '</div>' +
609
+ '</div>' +
610
+
611
+ // Method 2: Python
612
+ '<div class="bg-muted/50 rounded-lg p-3 space-y-2">' +
613
+ '<div class="flex items-center gap-2 text-sm font-medium">' +
614
+ '<span class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-primary/20 text-primary text-xs">2</span>' +
615
+ '<span>' + (t('codexlens.pythonMethod') || 'Python Script') + '</span>' +
616
+ '</div>' +
617
+ '<div class="text-xs text-muted-foreground mb-2">' +
618
+ (t('codexlens.pythonMethodDesc') || 'Pre-download model using Python:') +
619
+ '</div>' +
620
+ '<div class="bg-background rounded p-2">' +
621
+ '<pre class="text-xs font-mono whitespace-pre-wrap">' +
622
+ '# Install fastembed first\n' +
623
+ 'pip install fastembed\n\n' +
624
+ '# Download model (choose one)\n' +
625
+ 'from fastembed import TextEmbedding\n\n' +
626
+ '# Code model (recommended for code search)\n' +
627
+ 'model = TextEmbedding("jinaai/jina-embeddings-v2-base-code")\n\n' +
628
+ '# Fast model (lightweight)\n' +
629
+ '# model = TextEmbedding("BAAI/bge-small-en-v1.5")' +
630
+ '</pre>' +
631
+ '</div>' +
632
+ '</div>' +
633
+
634
+ // Method 3: Hugging Face Hub
635
+ '<div class="bg-muted/50 rounded-lg p-3 space-y-2">' +
636
+ '<div class="flex items-center gap-2 text-sm font-medium">' +
637
+ '<span class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-primary/20 text-primary text-xs">3</span>' +
638
+ '<span>' + (t('codexlens.hfHubMethod') || 'Hugging Face Hub CLI') + '</span>' +
639
+ '</div>' +
640
+ '<div class="text-xs text-muted-foreground mb-2">' +
641
+ (t('codexlens.hfHubMethodDesc') || 'Download using huggingface-cli with resume support:') +
642
+ '</div>' +
643
+ '<div class="bg-background rounded p-2 space-y-2">' +
644
+ '<pre class="text-xs font-mono whitespace-pre-wrap">' +
645
+ '# Install huggingface_hub\n' +
646
+ 'pip install huggingface_hub\n\n' +
647
+ '# Download model (supports resume on failure)\n' +
648
+ 'huggingface-cli download jinaai/jina-embeddings-v2-base-code' +
649
+ '</pre>' +
650
+ '</div>' +
651
+ '</div>' +
652
+
653
+ // Model Links
654
+ '<div class="bg-muted/50 rounded-lg p-3 space-y-2">' +
655
+ '<div class="flex items-center gap-2 text-sm font-medium">' +
656
+ '<i data-lucide="external-link" class="w-4 h-4"></i>' +
657
+ '<span>' + (t('codexlens.modelLinks') || 'Direct Model Links') + '</span>' +
658
+ '</div>' +
659
+ '<div class="grid grid-cols-2 gap-2">';
660
+
661
+ modelData.forEach(function(m) {
662
+ html +=
663
+ '<a href="https://huggingface.co/' + m.name + '" target="_blank" class="flex items-center justify-between bg-background rounded px-2 py-1.5 hover:bg-muted transition-colors">' +
664
+ '<span class="text-xs font-medium">' + m.profile + '</span>' +
665
+ '<span class="text-xs text-muted-foreground">' + m.size + '</span>' +
666
+ '</a>';
667
+ });
668
+
669
+ html +=
670
+ '</div>' +
671
+ '</div>' +
672
+
673
+ // Cache location info
674
+ '<div class="text-xs text-muted-foreground bg-muted/30 rounded p-2">' +
675
+ '<div class="flex items-start gap-1.5">' +
676
+ '<i data-lucide="info" class="w-3.5 h-3.5 mt-0.5 flex-shrink-0"></i>' +
677
+ '<div>' +
678
+ '<strong>' + (t('codexlens.cacheLocation') || 'Cache Location') + ':</strong><br>' +
679
+ '<code class="text-xs">Windows: %LOCALAPPDATA%\\Temp\\fastembed_cache</code><br>' +
680
+ '<code class="text-xs">Linux/Mac: ~/.cache/fastembed</code>' +
681
+ '</div>' +
682
+ '</div>' +
683
+ '</div>' +
684
+ '</div>' +
685
+ '</div>';
686
+
687
+ return html;
688
+ }
689
+
690
+ /**
691
+ * Toggle manual download guide visibility
692
+ */
693
+ function toggleManualDownloadGuide() {
694
+ var content = document.getElementById('manualDownloadContent');
695
+ var chevron = document.getElementById('manualDownloadChevron');
696
+
697
+ if (content && chevron) {
698
+ content.classList.toggle('hidden');
699
+ chevron.style.transform = content.classList.contains('hidden') ? '' : 'rotate(90deg)';
700
+ }
701
+ }
702
+
703
+ /**
704
+ * Copy text to clipboard
705
+ */
706
+ function copyToClipboard(text) {
707
+ navigator.clipboard.writeText(text).then(function() {
708
+ showRefreshToast(t('common.copied') || 'Copied to clipboard', 'success');
709
+ }).catch(function(err) {
710
+ console.error('Failed to copy:', err);
711
+ });
712
+ }
713
+
407
714
  /**
408
715
  * Load model list
409
716
  */
@@ -476,6 +783,10 @@ async function loadModelList() {
476
783
  });
477
784
 
478
785
  html += '</div>';
786
+
787
+ // Add manual download guide section
788
+ html += buildManualDownloadGuide();
789
+
479
790
  container.innerHTML = html;
480
791
  if (window.lucide) lucide.createIcons();
481
792
  } catch (err) {
@@ -485,18 +796,94 @@ async function loadModelList() {
485
796
  }
486
797
 
487
798
  /**
488
- * Download model
799
+ * Download model with progress simulation and manual download info
489
800
  */
490
801
  async function downloadModel(profile) {
491
802
  var modelCard = document.getElementById('model-' + profile);
492
803
  if (!modelCard) return;
493
804
 
494
805
  var originalHTML = modelCard.innerHTML;
806
+
807
+ // Get model info for size estimation
808
+ var modelSizes = {
809
+ 'fast': { size: 80, time: '1-2' },
810
+ 'code': { size: 150, time: '2-5' },
811
+ 'multilingual': { size: 1000, time: '5-15' },
812
+ 'balanced': { size: 600, time: '3-10' }
813
+ };
814
+
815
+ var modelInfo = modelSizes[profile] || { size: 100, time: '2-5' };
816
+
817
+ // Show detailed download UI with progress simulation
495
818
  modelCard.innerHTML =
496
- '<div class="flex items-center justify-center p-3">' +
497
- '<span class="text-sm text-muted-foreground animate-pulse">' + t('codexlens.downloading') + '</span>' +
819
+ '<div class="p-3 space-y-3">' +
820
+ '<div class="flex items-center gap-2">' +
821
+ '<div class="animate-spin w-4 h-4 border-2 border-primary border-t-transparent rounded-full flex-shrink-0"></div>' +
822
+ '<span class="text-sm font-medium">' + (t('codexlens.downloadingModel') || 'Downloading') + ' ' + profile + '</span>' +
823
+ '</div>' +
824
+ '<div class="space-y-1">' +
825
+ '<div class="h-2 bg-muted rounded-full overflow-hidden">' +
826
+ '<div id="model-progress-' + profile + '" class="h-full bg-primary transition-all duration-1000 ease-out model-download-progress" style="width: 0%"></div>' +
827
+ '</div>' +
828
+ '<div class="flex justify-between text-xs text-muted-foreground">' +
829
+ '<span id="model-status-' + profile + '">' + (t('codexlens.connectingToHuggingFace') || 'Connecting to Hugging Face...') + '</span>' +
830
+ '<span>~' + modelInfo.size + ' MB</span>' +
831
+ '</div>' +
832
+ '</div>' +
833
+ '<div class="text-xs text-muted-foreground bg-muted/50 rounded p-2 space-y-1">' +
834
+ '<div class="flex items-start gap-1">' +
835
+ '<i data-lucide="info" class="w-3 h-3 mt-0.5 flex-shrink-0"></i>' +
836
+ '<span>' + (t('codexlens.downloadTimeEstimate') || 'Estimated time') + ': ' + modelInfo.time + ' ' + (t('common.minutes') || 'minutes') + '</span>' +
837
+ '</div>' +
838
+ '<div class="flex items-start gap-1">' +
839
+ '<i data-lucide="terminal" class="w-3 h-3 mt-0.5 flex-shrink-0"></i>' +
840
+ '<span>' + (t('codexlens.manualDownloadHint') || 'Manual download') + ': <code class="bg-background px-1 rounded">codexlens model-download ' + profile + '</code></span>' +
841
+ '</div>' +
842
+ '</div>' +
843
+ '<button class="text-xs text-muted-foreground hover:text-foreground underline" onclick="cancelModelDownload(\'' + profile + '\')">' +
844
+ (t('common.cancel') || 'Cancel') +
845
+ '</button>' +
498
846
  '</div>';
499
847
 
848
+ if (window.lucide) lucide.createIcons();
849
+
850
+ // Start progress simulation
851
+ var progressBar = document.getElementById('model-progress-' + profile);
852
+ var statusText = document.getElementById('model-status-' + profile);
853
+ var simulatedProgress = 0;
854
+ var progressInterval = null;
855
+ var downloadAborted = false;
856
+
857
+ // Store abort controller for cancellation
858
+ window['modelDownloadAbort_' + profile] = function() {
859
+ downloadAborted = true;
860
+ if (progressInterval) clearInterval(progressInterval);
861
+ };
862
+
863
+ // Simulate progress based on model size
864
+ var progressStages = [
865
+ { percent: 10, msg: t('codexlens.downloadingModelFiles') || 'Downloading model files...' },
866
+ { percent: 30, msg: t('codexlens.downloadingWeights') || 'Downloading model weights...' },
867
+ { percent: 60, msg: t('codexlens.downloadingTokenizer') || 'Downloading tokenizer...' },
868
+ { percent: 80, msg: t('codexlens.verifyingModel') || 'Verifying model...' },
869
+ { percent: 95, msg: t('codexlens.finalizingDownload') || 'Finalizing...' }
870
+ ];
871
+
872
+ var stageIndex = 0;
873
+ var baseInterval = Math.max(2000, modelInfo.size * 30); // Slower for larger models
874
+
875
+ progressInterval = setInterval(function() {
876
+ if (downloadAborted) return;
877
+
878
+ if (stageIndex < progressStages.length) {
879
+ var stage = progressStages[stageIndex];
880
+ simulatedProgress = stage.percent;
881
+ if (progressBar) progressBar.style.width = simulatedProgress + '%';
882
+ if (statusText) statusText.textContent = stage.msg;
883
+ stageIndex++;
884
+ }
885
+ }, baseInterval);
886
+
500
887
  try {
501
888
  var response = await fetch('/api/codexlens/models/download', {
502
889
  method: 'POST',
@@ -504,20 +891,99 @@ async function downloadModel(profile) {
504
891
  body: JSON.stringify({ profile: profile })
505
892
  });
506
893
 
894
+ // Clear simulation
895
+ if (progressInterval) clearInterval(progressInterval);
896
+
897
+ if (downloadAborted) {
898
+ modelCard.innerHTML = originalHTML;
899
+ if (window.lucide) lucide.createIcons();
900
+ return;
901
+ }
902
+
507
903
  var result = await response.json();
508
904
 
509
905
  if (result.success) {
906
+ // Show completion
907
+ if (progressBar) progressBar.style.width = '100%';
908
+ if (statusText) statusText.textContent = t('codexlens.downloadComplete') || 'Download complete!';
909
+
510
910
  showRefreshToast(t('codexlens.modelDownloaded') + ': ' + profile, 'success');
511
- await loadModelList();
911
+
912
+ // Refresh model list after short delay
913
+ setTimeout(function() {
914
+ loadModelList();
915
+ }, 500);
512
916
  } else {
513
917
  showRefreshToast(t('codexlens.modelDownloadFailed') + ': ' + result.error, 'error');
514
- modelCard.innerHTML = originalHTML;
515
- if (window.lucide) lucide.createIcons();
918
+ showModelDownloadError(modelCard, profile, result.error, originalHTML);
516
919
  }
517
920
  } catch (err) {
921
+ if (progressInterval) clearInterval(progressInterval);
518
922
  showRefreshToast(t('common.error') + ': ' + err.message, 'error');
519
- modelCard.innerHTML = originalHTML;
520
- if (window.lucide) lucide.createIcons();
923
+ showModelDownloadError(modelCard, profile, err.message, originalHTML);
924
+ }
925
+
926
+ // Cleanup abort function
927
+ delete window['modelDownloadAbort_' + profile];
928
+ }
929
+
930
+ /**
931
+ * Show model download error with manual download instructions
932
+ */
933
+ function showModelDownloadError(modelCard, profile, error, originalHTML) {
934
+ var modelNames = {
935
+ 'fast': 'BAAI/bge-small-en-v1.5',
936
+ 'code': 'jinaai/jina-embeddings-v2-base-code',
937
+ 'multilingual': 'intfloat/multilingual-e5-large',
938
+ 'balanced': 'mixedbread-ai/mxbai-embed-large-v1'
939
+ };
940
+
941
+ var modelName = modelNames[profile] || profile;
942
+ var hfUrl = 'https://huggingface.co/' + modelName;
943
+
944
+ modelCard.innerHTML =
945
+ '<div class="p-3 space-y-3">' +
946
+ '<div class="flex items-start gap-2 text-destructive">' +
947
+ '<i data-lucide="alert-circle" class="w-4 h-4 mt-0.5 flex-shrink-0"></i>' +
948
+ '<div class="text-sm">' +
949
+ '<div class="font-medium">' + (t('codexlens.downloadFailed') || 'Download failed') + '</div>' +
950
+ '<div class="text-xs text-muted-foreground mt-1">' + error + '</div>' +
951
+ '</div>' +
952
+ '</div>' +
953
+ '<div class="bg-muted/50 rounded p-2 space-y-2 text-xs">' +
954
+ '<div class="font-medium">' + (t('codexlens.manualDownloadOptions') || 'Manual download options') + ':</div>' +
955
+ '<div class="space-y-1.5">' +
956
+ '<div class="flex items-start gap-1">' +
957
+ '<span class="text-muted-foreground">1.</span>' +
958
+ '<span>' + (t('codexlens.cliDownload') || 'CLI') + ': <code class="bg-background px-1 rounded">codexlens model-download ' + profile + '</code></span>' +
959
+ '</div>' +
960
+ '<div class="flex items-start gap-1">' +
961
+ '<span class="text-muted-foreground">2.</span>' +
962
+ '<span>' + (t('codexlens.huggingfaceDownload') || 'Hugging Face') + ': <a href="' + hfUrl + '" target="_blank" class="text-primary hover:underline">' + modelName + '</a></span>' +
963
+ '</div>' +
964
+ '</div>' +
965
+ '</div>' +
966
+ '<div class="flex gap-2">' +
967
+ '<button class="btn-sm btn-outline flex-1" onclick="loadModelList()">' +
968
+ '<i data-lucide="refresh-cw" class="w-3 h-3"></i> ' + (t('common.refresh') || 'Refresh') +
969
+ '</button>' +
970
+ '<button class="btn-sm btn-primary flex-1" onclick="downloadModel(\'' + profile + '\')">' +
971
+ '<i data-lucide="download" class="w-3 h-3"></i> ' + (t('common.retry') || 'Retry') +
972
+ '</button>' +
973
+ '</div>' +
974
+ '</div>';
975
+
976
+ if (window.lucide) lucide.createIcons();
977
+ }
978
+
979
+ /**
980
+ * Cancel model download
981
+ */
982
+ function cancelModelDownload(profile) {
983
+ if (window['modelDownloadAbort_' + profile]) {
984
+ window['modelDownloadAbort_' + profile]();
985
+ showRefreshToast(t('codexlens.downloadCanceled') || 'Download canceled', 'info');
986
+ loadModelList();
521
987
  }
522
988
  }
523
989
 
@@ -876,16 +1342,315 @@ async function cancelCodexLensIndexing() {
876
1342
 
877
1343
  /**
878
1344
  * Install CodexLens
1345
+ * Note: Uses CodexLens-specific install wizard from cli-status.js
1346
+ * which calls /api/codexlens/bootstrap (Python venv), not the generic
1347
+ * CLI install that uses npm install -g (NPM packages)
1348
+ */
1349
+ function installCodexLensFromManager() {
1350
+ // Use the CodexLens-specific install wizard from cli-status.js
1351
+ if (typeof openCodexLensInstallWizard === 'function') {
1352
+ openCodexLensInstallWizard();
1353
+ } else {
1354
+ // Fallback: inline install wizard if cli-status.js not loaded
1355
+ showCodexLensInstallDialog();
1356
+ }
1357
+ }
1358
+
1359
+ /**
1360
+ * Fallback install dialog when cli-status.js is not loaded
879
1361
  */
880
- function installCodexLens() {
881
- openCliInstallWizard('codexlens');
1362
+ function showCodexLensInstallDialog() {
1363
+ var modal = document.createElement('div');
1364
+ modal.id = 'codexlensInstallModalFallback';
1365
+ modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50';
1366
+ modal.innerHTML =
1367
+ '<div class="bg-card rounded-lg shadow-xl w-full max-w-md mx-4 overflow-hidden">' +
1368
+ '<div class="p-6">' +
1369
+ '<div class="flex items-center gap-3 mb-4">' +
1370
+ '<div class="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center">' +
1371
+ '<i data-lucide="database" class="w-5 h-5 text-primary"></i>' +
1372
+ '</div>' +
1373
+ '<div>' +
1374
+ '<h3 class="text-lg font-semibold">' + t('codexlens.installCodexLens') + '</h3>' +
1375
+ '<p class="text-sm text-muted-foreground">' + t('codexlens.installDesc') + '</p>' +
1376
+ '</div>' +
1377
+ '</div>' +
1378
+ '<div class="space-y-4">' +
1379
+ '<div class="bg-muted/50 rounded-lg p-4">' +
1380
+ '<h4 class="font-medium mb-2">' + t('codexlens.whatWillBeInstalled') + '</h4>' +
1381
+ '<ul class="text-sm space-y-2 text-muted-foreground">' +
1382
+ '<li class="flex items-start gap-2">' +
1383
+ '<i data-lucide="check" class="w-4 h-4 text-success mt-0.5"></i>' +
1384
+ '<span><strong>' + t('codexlens.pythonVenv') + '</strong> - ' + t('codexlens.pythonVenvDesc') + '</span>' +
1385
+ '</li>' +
1386
+ '<li class="flex items-start gap-2">' +
1387
+ '<i data-lucide="check" class="w-4 h-4 text-success mt-0.5"></i>' +
1388
+ '<span><strong>' + t('codexlens.codexlensPackage') + '</strong> - ' + t('codexlens.codexlensPackageDesc') + '</span>' +
1389
+ '</li>' +
1390
+ '<li class="flex items-start gap-2">' +
1391
+ '<i data-lucide="check" class="w-4 h-4 text-success mt-0.5"></i>' +
1392
+ '<span><strong>SQLite FTS5</strong> - ' + t('codexlens.sqliteFtsDesc') + '</span>' +
1393
+ '</li>' +
1394
+ '</ul>' +
1395
+ '</div>' +
1396
+ '<div class="bg-primary/5 border border-primary/20 rounded-lg p-3">' +
1397
+ '<div class="flex items-start gap-2">' +
1398
+ '<i data-lucide="info" class="w-4 h-4 text-primary mt-0.5"></i>' +
1399
+ '<div class="text-sm text-muted-foreground">' +
1400
+ '<p class="font-medium text-foreground">' + t('codexlens.installLocation') + '</p>' +
1401
+ '<p class="mt-1"><code class="bg-muted px-1 rounded">~/.codexlens/venv</code></p>' +
1402
+ '<p class="mt-1">' + t('codexlens.installTime') + '</p>' +
1403
+ '</div>' +
1404
+ '</div>' +
1405
+ '</div>' +
1406
+ '<div id="codexlensInstallProgressFallback" class="hidden">' +
1407
+ '<div class="flex items-center gap-3">' +
1408
+ '<div class="animate-spin w-5 h-5 border-2 border-primary border-t-transparent rounded-full"></div>' +
1409
+ '<span class="text-sm" id="codexlensInstallStatusFallback">' + t('codexlens.startingInstall') + '</span>' +
1410
+ '</div>' +
1411
+ '<div class="mt-2 h-2 bg-muted rounded-full overflow-hidden">' +
1412
+ '<div id="codexlensInstallProgressBarFallback" class="h-full bg-primary transition-all duration-300" style="width: 0%"></div>' +
1413
+ '</div>' +
1414
+ '</div>' +
1415
+ '</div>' +
1416
+ '</div>' +
1417
+ '<div class="border-t border-border p-4 flex justify-end gap-3 bg-muted/30">' +
1418
+ '<button class="btn-outline px-4 py-2" onclick="closeCodexLensInstallDialogFallback()">' + t('common.cancel') + '</button>' +
1419
+ '<button id="codexlensInstallBtnFallback" class="btn-primary px-4 py-2" onclick="startCodexLensInstallFallback()">' +
1420
+ '<i data-lucide="download" class="w-4 h-4 mr-2"></i>' +
1421
+ t('codexlens.installNow') +
1422
+ '</button>' +
1423
+ '</div>' +
1424
+ '</div>';
1425
+
1426
+ document.body.appendChild(modal);
1427
+ if (window.lucide) lucide.createIcons();
1428
+ }
1429
+
1430
+ function closeCodexLensInstallDialogFallback() {
1431
+ var modal = document.getElementById('codexlensInstallModalFallback');
1432
+ if (modal) modal.remove();
1433
+ }
1434
+
1435
+ async function startCodexLensInstallFallback() {
1436
+ var progressDiv = document.getElementById('codexlensInstallProgressFallback');
1437
+ var installBtn = document.getElementById('codexlensInstallBtnFallback');
1438
+ var statusText = document.getElementById('codexlensInstallStatusFallback');
1439
+ var progressBar = document.getElementById('codexlensInstallProgressBarFallback');
1440
+
1441
+ progressDiv.classList.remove('hidden');
1442
+ installBtn.disabled = true;
1443
+ installBtn.innerHTML = '<span class="animate-pulse">' + t('codexlens.installing') + '</span>';
1444
+
1445
+ var stages = [
1446
+ { progress: 10, text: t('codexlens.creatingVenv') },
1447
+ { progress: 30, text: t('codexlens.installingPip') },
1448
+ { progress: 50, text: t('codexlens.installingPackage') },
1449
+ { progress: 70, text: t('codexlens.settingUpDeps') },
1450
+ { progress: 90, text: t('codexlens.finalizing') }
1451
+ ];
1452
+
1453
+ var currentStage = 0;
1454
+ var progressInterval = setInterval(function() {
1455
+ if (currentStage < stages.length) {
1456
+ statusText.textContent = stages[currentStage].text;
1457
+ progressBar.style.width = stages[currentStage].progress + '%';
1458
+ currentStage++;
1459
+ }
1460
+ }, 1500);
1461
+
1462
+ try {
1463
+ var response = await fetch('/api/codexlens/bootstrap', {
1464
+ method: 'POST',
1465
+ headers: { 'Content-Type': 'application/json' },
1466
+ body: JSON.stringify({})
1467
+ });
1468
+
1469
+ clearInterval(progressInterval);
1470
+ var result = await response.json();
1471
+
1472
+ if (result.success) {
1473
+ progressBar.style.width = '100%';
1474
+ statusText.textContent = t('codexlens.installComplete');
1475
+
1476
+ setTimeout(function() {
1477
+ closeCodexLensInstallDialogFallback();
1478
+ showRefreshToast(t('codexlens.installSuccess'), 'success');
1479
+ // Refresh the page to update status
1480
+ if (typeof loadCodexLensStatus === 'function') {
1481
+ loadCodexLensStatus().then(function() {
1482
+ if (typeof renderCodexLensManager === 'function') renderCodexLensManager();
1483
+ });
1484
+ } else {
1485
+ location.reload();
1486
+ }
1487
+ }, 1000);
1488
+ } else {
1489
+ statusText.textContent = t('common.error') + ': ' + result.error;
1490
+ progressBar.classList.add('bg-destructive');
1491
+ installBtn.disabled = false;
1492
+ installBtn.innerHTML = '<i data-lucide="refresh-cw" class="w-4 h-4 mr-2"></i> ' + t('common.retry');
1493
+ if (window.lucide) lucide.createIcons();
1494
+ }
1495
+ } catch (err) {
1496
+ clearInterval(progressInterval);
1497
+ statusText.textContent = t('common.error') + ': ' + err.message;
1498
+ progressBar.classList.add('bg-destructive');
1499
+ installBtn.disabled = false;
1500
+ installBtn.innerHTML = '<i data-lucide="refresh-cw" class="w-4 h-4 mr-2"></i> ' + t('common.retry');
1501
+ if (window.lucide) lucide.createIcons();
1502
+ }
882
1503
  }
883
1504
 
884
1505
  /**
885
1506
  * Uninstall CodexLens
1507
+ * Note: Uses CodexLens-specific uninstall wizard from cli-status.js
1508
+ * which calls /api/codexlens/uninstall (Python venv), not the generic
1509
+ * CLI uninstall that uses /api/cli/uninstall (NPM packages)
886
1510
  */
887
- function uninstallCodexLens() {
888
- openCliUninstallWizard('codexlens');
1511
+ function uninstallCodexLensFromManager() {
1512
+ // Use the CodexLens-specific uninstall wizard from cli-status.js
1513
+ if (typeof openCodexLensUninstallWizard === 'function') {
1514
+ openCodexLensUninstallWizard();
1515
+ } else {
1516
+ // Fallback: inline uninstall wizard if cli-status.js not loaded
1517
+ showCodexLensUninstallDialog();
1518
+ }
1519
+ }
1520
+
1521
+ /**
1522
+ * Fallback uninstall dialog when cli-status.js is not loaded
1523
+ */
1524
+ function showCodexLensUninstallDialog() {
1525
+ var modal = document.createElement('div');
1526
+ modal.id = 'codexlensUninstallModalFallback';
1527
+ modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50';
1528
+ modal.innerHTML =
1529
+ '<div class="bg-card rounded-lg shadow-xl w-full max-w-md mx-4 overflow-hidden">' +
1530
+ '<div class="p-6">' +
1531
+ '<div class="flex items-center gap-3 mb-4">' +
1532
+ '<div class="w-10 h-10 rounded-full bg-destructive/10 flex items-center justify-center">' +
1533
+ '<i data-lucide="trash-2" class="w-5 h-5 text-destructive"></i>' +
1534
+ '</div>' +
1535
+ '<div>' +
1536
+ '<h3 class="text-lg font-semibold">' + t('codexlens.uninstall') + '</h3>' +
1537
+ '<p class="text-sm text-muted-foreground">' + t('codexlens.uninstallDesc') + '</p>' +
1538
+ '</div>' +
1539
+ '</div>' +
1540
+ '<div class="space-y-4">' +
1541
+ '<div class="bg-destructive/5 border border-destructive/20 rounded-lg p-4">' +
1542
+ '<h4 class="font-medium text-destructive mb-2">' + t('codexlens.whatWillBeRemoved') + '</h4>' +
1543
+ '<ul class="text-sm space-y-2 text-muted-foreground">' +
1544
+ '<li class="flex items-start gap-2">' +
1545
+ '<i data-lucide="x" class="w-4 h-4 text-destructive mt-0.5"></i>' +
1546
+ '<span>' + t('codexlens.removeVenv') + '</span>' +
1547
+ '</li>' +
1548
+ '<li class="flex items-start gap-2">' +
1549
+ '<i data-lucide="x" class="w-4 h-4 text-destructive mt-0.5"></i>' +
1550
+ '<span>' + t('codexlens.removeData') + '</span>' +
1551
+ '</li>' +
1552
+ '<li class="flex items-start gap-2">' +
1553
+ '<i data-lucide="x" class="w-4 h-4 text-destructive mt-0.5"></i>' +
1554
+ '<span>' + t('codexlens.removeConfig') + '</span>' +
1555
+ '</li>' +
1556
+ '</ul>' +
1557
+ '</div>' +
1558
+ '<div id="codexlensUninstallProgressFallback" class="hidden">' +
1559
+ '<div class="flex items-center gap-3">' +
1560
+ '<div class="animate-spin w-5 h-5 border-2 border-destructive border-t-transparent rounded-full"></div>' +
1561
+ '<span class="text-sm" id="codexlensUninstallStatusFallback">' + t('codexlens.removing') + '</span>' +
1562
+ '</div>' +
1563
+ '<div class="mt-2 h-2 bg-muted rounded-full overflow-hidden">' +
1564
+ '<div id="codexlensUninstallProgressBarFallback" class="h-full bg-destructive transition-all duration-300" style="width: 0%"></div>' +
1565
+ '</div>' +
1566
+ '</div>' +
1567
+ '</div>' +
1568
+ '</div>' +
1569
+ '<div class="border-t border-border p-4 flex justify-end gap-3 bg-muted/30">' +
1570
+ '<button class="btn-outline px-4 py-2" onclick="closeCodexLensUninstallDialogFallback()">' + t('common.cancel') + '</button>' +
1571
+ '<button id="codexlensUninstallBtnFallback" class="btn-destructive px-4 py-2" onclick="startCodexLensUninstallFallback()">' +
1572
+ '<i data-lucide="trash-2" class="w-4 h-4 mr-2"></i>' +
1573
+ t('codexlens.uninstall') +
1574
+ '</button>' +
1575
+ '</div>' +
1576
+ '</div>';
1577
+
1578
+ document.body.appendChild(modal);
1579
+ if (window.lucide) lucide.createIcons();
1580
+ }
1581
+
1582
+ function closeCodexLensUninstallDialogFallback() {
1583
+ var modal = document.getElementById('codexlensUninstallModalFallback');
1584
+ if (modal) modal.remove();
1585
+ }
1586
+
1587
+ async function startCodexLensUninstallFallback() {
1588
+ var progressDiv = document.getElementById('codexlensUninstallProgressFallback');
1589
+ var uninstallBtn = document.getElementById('codexlensUninstallBtnFallback');
1590
+ var statusText = document.getElementById('codexlensUninstallStatusFallback');
1591
+ var progressBar = document.getElementById('codexlensUninstallProgressBarFallback');
1592
+
1593
+ progressDiv.classList.remove('hidden');
1594
+ uninstallBtn.disabled = true;
1595
+ uninstallBtn.innerHTML = '<span class="animate-pulse">' + t('codexlens.uninstalling') + '</span>';
1596
+
1597
+ var stages = [
1598
+ { progress: 25, text: t('codexlens.removingVenv') },
1599
+ { progress: 50, text: t('codexlens.removingData') },
1600
+ { progress: 75, text: t('codexlens.removingConfig') },
1601
+ { progress: 90, text: t('codexlens.finalizing') }
1602
+ ];
1603
+
1604
+ var currentStage = 0;
1605
+ var progressInterval = setInterval(function() {
1606
+ if (currentStage < stages.length) {
1607
+ statusText.textContent = stages[currentStage].text;
1608
+ progressBar.style.width = stages[currentStage].progress + '%';
1609
+ currentStage++;
1610
+ }
1611
+ }, 500);
1612
+
1613
+ try {
1614
+ var response = await fetch('/api/codexlens/uninstall', {
1615
+ method: 'POST',
1616
+ headers: { 'Content-Type': 'application/json' },
1617
+ body: JSON.stringify({})
1618
+ });
1619
+
1620
+ clearInterval(progressInterval);
1621
+ var result = await response.json();
1622
+
1623
+ if (result.success) {
1624
+ progressBar.style.width = '100%';
1625
+ statusText.textContent = t('codexlens.uninstallComplete');
1626
+
1627
+ setTimeout(function() {
1628
+ closeCodexLensUninstallDialogFallback();
1629
+ showRefreshToast(t('codexlens.uninstallSuccess'), 'success');
1630
+ // Refresh the page to update status
1631
+ if (typeof loadCodexLensStatus === 'function') {
1632
+ loadCodexLensStatus().then(function() {
1633
+ if (typeof renderCodexLensManager === 'function') renderCodexLensManager();
1634
+ });
1635
+ } else {
1636
+ location.reload();
1637
+ }
1638
+ }, 1000);
1639
+ } else {
1640
+ statusText.textContent = t('common.error') + ': ' + result.error;
1641
+ progressBar.classList.add('bg-destructive');
1642
+ uninstallBtn.disabled = false;
1643
+ uninstallBtn.innerHTML = '<i data-lucide="refresh-cw" class="w-4 h-4 mr-2"></i> ' + t('common.retry');
1644
+ if (window.lucide) lucide.createIcons();
1645
+ }
1646
+ } catch (err) {
1647
+ clearInterval(progressInterval);
1648
+ statusText.textContent = t('common.error') + ': ' + err.message;
1649
+ progressBar.classList.add('bg-destructive');
1650
+ uninstallBtn.disabled = false;
1651
+ uninstallBtn.innerHTML = '<i data-lucide="refresh-cw" class="w-4 h-4 mr-2"></i> ' + t('common.retry');
1652
+ if (window.lucide) lucide.createIcons();
1653
+ }
889
1654
  }
890
1655
 
891
1656
  /**