claude-code-workflow 6.2.7 → 6.3.0
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.
- package/.claude/CLAUDE.md +16 -1
- package/.claude/workflows/cli-templates/protocols/analysis-protocol.md +11 -4
- package/.claude/workflows/cli-templates/protocols/write-protocol.md +10 -75
- package/.claude/workflows/cli-tools-usage.md +14 -24
- package/.codex/AGENTS.md +51 -1
- package/.codex/prompts/compact.md +378 -0
- package/.gemini/GEMINI.md +57 -20
- package/ccw/dist/cli.d.ts.map +1 -1
- package/ccw/dist/cli.js +21 -8
- package/ccw/dist/cli.js.map +1 -1
- package/ccw/dist/commands/cli.d.ts +2 -0
- package/ccw/dist/commands/cli.d.ts.map +1 -1
- package/ccw/dist/commands/cli.js +129 -8
- package/ccw/dist/commands/cli.js.map +1 -1
- package/ccw/dist/commands/hook.d.ts.map +1 -1
- package/ccw/dist/commands/hook.js +3 -2
- package/ccw/dist/commands/hook.js.map +1 -1
- package/ccw/dist/config/litellm-api-config-manager.d.ts +180 -0
- package/ccw/dist/config/litellm-api-config-manager.d.ts.map +1 -0
- package/ccw/dist/config/litellm-api-config-manager.js +770 -0
- package/ccw/dist/config/litellm-api-config-manager.js.map +1 -0
- package/ccw/dist/config/provider-models.d.ts +73 -0
- package/ccw/dist/config/provider-models.d.ts.map +1 -0
- package/ccw/dist/config/provider-models.js +172 -0
- package/ccw/dist/config/provider-models.js.map +1 -0
- package/ccw/dist/core/cache-manager.d.ts.map +1 -1
- package/ccw/dist/core/cache-manager.js +3 -5
- package/ccw/dist/core/cache-manager.js.map +1 -1
- package/ccw/dist/core/dashboard-generator.d.ts.map +1 -1
- package/ccw/dist/core/dashboard-generator.js +3 -1
- package/ccw/dist/core/dashboard-generator.js.map +1 -1
- package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/cli-routes.js +169 -0
- package/ccw/dist/core/routes/cli-routes.js.map +1 -1
- package/ccw/dist/core/routes/codexlens-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/codexlens-routes.js +234 -18
- package/ccw/dist/core/routes/codexlens-routes.js.map +1 -1
- package/ccw/dist/core/routes/hooks-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/hooks-routes.js +30 -32
- package/ccw/dist/core/routes/hooks-routes.js.map +1 -1
- package/ccw/dist/core/routes/litellm-api-routes.d.ts +21 -0
- package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/litellm-api-routes.js +780 -0
- package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -0
- package/ccw/dist/core/routes/litellm-routes.d.ts +20 -0
- package/ccw/dist/core/routes/litellm-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/litellm-routes.js +85 -0
- package/ccw/dist/core/routes/litellm-routes.js.map +1 -0
- package/ccw/dist/core/routes/mcp-routes.js +2 -2
- package/ccw/dist/core/routes/mcp-routes.js.map +1 -1
- package/ccw/dist/core/routes/status-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/status-routes.js +39 -0
- package/ccw/dist/core/routes/status-routes.js.map +1 -1
- package/ccw/dist/core/routes/system-routes.js +1 -1
- package/ccw/dist/core/routes/system-routes.js.map +1 -1
- package/ccw/dist/core/server.d.ts.map +1 -1
- package/ccw/dist/core/server.js +15 -1
- package/ccw/dist/core/server.js.map +1 -1
- package/ccw/dist/mcp-server/index.js +1 -1
- package/ccw/dist/mcp-server/index.js.map +1 -1
- package/ccw/dist/tools/claude-cli-tools.d.ts +82 -0
- package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -0
- package/ccw/dist/tools/claude-cli-tools.js +216 -0
- package/ccw/dist/tools/claude-cli-tools.js.map +1 -0
- package/ccw/dist/tools/cli-executor.d.ts.map +1 -1
- package/ccw/dist/tools/cli-executor.js +76 -14
- package/ccw/dist/tools/cli-executor.js.map +1 -1
- package/ccw/dist/tools/codex-lens.d.ts +9 -2
- package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
- package/ccw/dist/tools/codex-lens.js +114 -9
- package/ccw/dist/tools/codex-lens.js.map +1 -1
- package/ccw/dist/tools/context-cache-store.d.ts +136 -0
- package/ccw/dist/tools/context-cache-store.d.ts.map +1 -0
- package/ccw/dist/tools/context-cache-store.js +256 -0
- package/ccw/dist/tools/context-cache-store.js.map +1 -0
- package/ccw/dist/tools/context-cache.d.ts +56 -0
- package/ccw/dist/tools/context-cache.d.ts.map +1 -0
- package/ccw/dist/tools/context-cache.js +294 -0
- package/ccw/dist/tools/context-cache.js.map +1 -0
- package/ccw/dist/tools/core-memory.d.ts.map +1 -1
- package/ccw/dist/tools/core-memory.js +33 -19
- package/ccw/dist/tools/core-memory.js.map +1 -1
- package/ccw/dist/tools/index.d.ts.map +1 -1
- package/ccw/dist/tools/index.js +2 -0
- package/ccw/dist/tools/index.js.map +1 -1
- package/ccw/dist/tools/litellm-client.d.ts +85 -0
- package/ccw/dist/tools/litellm-client.d.ts.map +1 -0
- package/ccw/dist/tools/litellm-client.js +188 -0
- package/ccw/dist/tools/litellm-client.js.map +1 -0
- package/ccw/dist/tools/litellm-executor.d.ts +34 -0
- package/ccw/dist/tools/litellm-executor.d.ts.map +1 -0
- package/ccw/dist/tools/litellm-executor.js +192 -0
- package/ccw/dist/tools/litellm-executor.js.map +1 -0
- package/ccw/dist/tools/pattern-parser.d.ts +55 -0
- package/ccw/dist/tools/pattern-parser.d.ts.map +1 -0
- package/ccw/dist/tools/pattern-parser.js +237 -0
- package/ccw/dist/tools/pattern-parser.js.map +1 -0
- package/ccw/dist/tools/smart-search.d.ts +1 -0
- package/ccw/dist/tools/smart-search.d.ts.map +1 -1
- package/ccw/dist/tools/smart-search.js +117 -41
- package/ccw/dist/tools/smart-search.js.map +1 -1
- package/ccw/dist/types/litellm-api-config.d.ts +294 -0
- package/ccw/dist/types/litellm-api-config.d.ts.map +1 -0
- package/ccw/dist/types/litellm-api-config.js +8 -0
- package/ccw/dist/types/litellm-api-config.js.map +1 -0
- package/ccw/src/cli.ts +258 -244
- package/ccw/src/commands/cli.ts +153 -9
- package/ccw/src/commands/hook.ts +3 -2
- package/ccw/src/config/.litellm-api-config-manager.ts.2025-12-23T11-57-43-727Z.bak +441 -0
- package/ccw/src/config/litellm-api-config-manager.ts +1012 -0
- package/ccw/src/config/provider-models.ts +222 -0
- package/ccw/src/core/cache-manager.ts +292 -294
- package/ccw/src/core/dashboard-generator.ts +3 -1
- package/ccw/src/core/routes/cli-routes.ts +192 -0
- package/ccw/src/core/routes/codexlens-routes.ts +241 -19
- package/ccw/src/core/routes/hooks-routes.ts +399 -405
- package/ccw/src/core/routes/litellm-api-routes.ts +930 -0
- package/ccw/src/core/routes/litellm-routes.ts +107 -0
- package/ccw/src/core/routes/mcp-routes.ts +1271 -1271
- package/ccw/src/core/routes/status-routes.ts +51 -0
- package/ccw/src/core/routes/system-routes.ts +1 -1
- package/ccw/src/core/server.ts +15 -1
- package/ccw/src/mcp-server/index.ts +1 -1
- package/ccw/src/templates/dashboard-css/12-cli-legacy.css +44 -0
- package/ccw/src/templates/dashboard-css/31-api-settings.css +2265 -0
- package/ccw/src/templates/dashboard-js/components/cli-history.js +15 -8
- package/ccw/src/templates/dashboard-js/components/cli-status.js +323 -9
- package/ccw/src/templates/dashboard-js/components/navigation.js +329 -313
- package/ccw/src/templates/dashboard-js/i18n.js +583 -1
- package/ccw/src/templates/dashboard-js/views/api-settings.js +3362 -0
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +199 -24
- package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +1265 -27
- package/ccw/src/templates/dashboard.html +840 -831
- package/ccw/src/tools/claude-cli-tools.ts +300 -0
- package/ccw/src/tools/cli-executor.ts +83 -14
- package/ccw/src/tools/codex-lens.ts +146 -9
- package/ccw/src/tools/context-cache-store.ts +368 -0
- package/ccw/src/tools/context-cache.ts +393 -0
- package/ccw/src/tools/core-memory.ts +33 -19
- package/ccw/src/tools/index.ts +2 -0
- package/ccw/src/tools/litellm-client.ts +246 -0
- package/ccw/src/tools/litellm-executor.ts +241 -0
- package/ccw/src/tools/pattern-parser.ts +329 -0
- package/ccw/src/tools/smart-search.ts +142 -41
- package/ccw/src/types/litellm-api-config.ts +402 -0
- package/ccw-litellm/README.md +180 -0
- package/ccw-litellm/pyproject.toml +35 -0
- package/ccw-litellm/src/ccw_litellm/__init__.py +47 -0
- package/ccw-litellm/src/ccw_litellm/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/__pycache__/cli.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/cli.py +108 -0
- package/ccw-litellm/src/ccw_litellm/clients/__init__.py +12 -0
- package/ccw-litellm/src/ccw_litellm/clients/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/clients/__pycache__/litellm_llm.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/clients/litellm_embedder.py +251 -0
- package/ccw-litellm/src/ccw_litellm/clients/litellm_llm.py +165 -0
- package/ccw-litellm/src/ccw_litellm/config/__init__.py +22 -0
- package/ccw-litellm/src/ccw_litellm/config/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/config/__pycache__/loader.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/config/__pycache__/models.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/config/loader.py +316 -0
- package/ccw-litellm/src/ccw_litellm/config/models.py +130 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__init__.py +14 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/__init__.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/embedder.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/__pycache__/llm.cpython-313.pyc +0 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/embedder.py +52 -0
- package/ccw-litellm/src/ccw_litellm/interfaces/llm.py +45 -0
- package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/commands.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/embedding_manager.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/model_manager.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/__pycache__/output.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/cli/commands.py +378 -23
- package/codex-lens/src/codexlens/cli/embedding_manager.py +660 -56
- package/codex-lens/src/codexlens/cli/model_manager.py +31 -18
- package/codex-lens/src/codexlens/cli/output.py +12 -1
- package/codex-lens/src/codexlens/config.py +93 -0
- package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/chain_search.py +6 -2
- package/codex-lens/src/codexlens/search/hybrid_search.py +44 -21
- package/codex-lens/src/codexlens/search/ranking.py +1 -1
- package/codex-lens/src/codexlens/semantic/__init__.py +42 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/base.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/factory.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/gpu_support.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/litellm_embedder.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/base.py +61 -0
- package/codex-lens/src/codexlens/semantic/chunker.py +43 -20
- package/codex-lens/src/codexlens/semantic/embedder.py +60 -13
- package/codex-lens/src/codexlens/semantic/factory.py +98 -0
- package/codex-lens/src/codexlens/semantic/gpu_support.py +225 -3
- package/codex-lens/src/codexlens/semantic/litellm_embedder.py +144 -0
- package/codex-lens/src/codexlens/semantic/rotational_embedder.py +434 -0
- package/codex-lens/src/codexlens/semantic/vector_store.py +33 -8
- package/codex-lens/src/codexlens/storage/__pycache__/path_mapper.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_004_dual_fts.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/path_mapper.py +27 -1
- package/package.json +15 -5
- package/.codex/prompts.zip +0 -0
- package/ccw/package.json +0 -65
|
@@ -271,6 +271,9 @@ function initCodexLensConfigEvents(currentConfig) {
|
|
|
271
271
|
var searchType = document.getElementById('searchTypeSelect').value;
|
|
272
272
|
var searchMode = document.getElementById('searchModeSelect').value;
|
|
273
273
|
var query = document.getElementById('searchQueryInput').value.trim();
|
|
274
|
+
var searchLimit = document.getElementById('searchLimitInput')?.value || '5';
|
|
275
|
+
var contentLength = document.getElementById('contentLengthInput')?.value || '200';
|
|
276
|
+
var extraFiles = document.getElementById('extraFilesInput')?.value || '10';
|
|
274
277
|
var resultsDiv = document.getElementById('searchResults');
|
|
275
278
|
var resultCount = document.getElementById('searchResultCount');
|
|
276
279
|
var resultContent = document.getElementById('searchResultContent');
|
|
@@ -286,7 +289,12 @@ function initCodexLensConfigEvents(currentConfig) {
|
|
|
286
289
|
|
|
287
290
|
try {
|
|
288
291
|
var endpoint = '/api/codexlens/' + searchType;
|
|
289
|
-
var params = new URLSearchParams({
|
|
292
|
+
var params = new URLSearchParams({
|
|
293
|
+
query: query,
|
|
294
|
+
limit: searchLimit,
|
|
295
|
+
max_content_length: contentLength,
|
|
296
|
+
extra_files_count: extraFiles
|
|
297
|
+
});
|
|
290
298
|
// Add mode parameter for search and search_files (not for symbol search)
|
|
291
299
|
if (searchType === 'search' || searchType === 'search_files') {
|
|
292
300
|
params.append('mode', searchMode);
|
|
@@ -337,6 +345,8 @@ function initCodexLensConfigEvents(currentConfig) {
|
|
|
337
345
|
|
|
338
346
|
// Store detected GPU info
|
|
339
347
|
var detectedGpuInfo = null;
|
|
348
|
+
// Store available GPU devices
|
|
349
|
+
var availableGpuDevices = null;
|
|
340
350
|
|
|
341
351
|
/**
|
|
342
352
|
* Detect GPU support
|
|
@@ -363,11 +373,13 @@ async function loadSemanticDepsStatus() {
|
|
|
363
373
|
if (!container) return;
|
|
364
374
|
|
|
365
375
|
try {
|
|
366
|
-
// Detect GPU support in parallel
|
|
376
|
+
// Detect GPU support and load GPU devices in parallel
|
|
367
377
|
var gpuPromise = detectGpuSupport();
|
|
378
|
+
var gpuDevicesPromise = loadGpuDevices();
|
|
368
379
|
var response = await fetch('/api/codexlens/semantic/status');
|
|
369
380
|
var result = await response.json();
|
|
370
381
|
var gpuInfo = await gpuPromise;
|
|
382
|
+
var gpuDevices = await gpuDevicesPromise;
|
|
371
383
|
|
|
372
384
|
if (result.available) {
|
|
373
385
|
// Build accelerator badge
|
|
@@ -379,13 +391,16 @@ async function loadSemanticDepsStatus() {
|
|
|
379
391
|
acceleratorIcon = 'zap';
|
|
380
392
|
acceleratorClass = 'bg-green-500/20 text-green-600';
|
|
381
393
|
} else if (accelerator === 'DirectML') {
|
|
382
|
-
acceleratorIcon = '
|
|
394
|
+
acceleratorIcon = 'cpu';
|
|
383
395
|
acceleratorClass = 'bg-blue-500/20 text-blue-600';
|
|
384
396
|
} else if (accelerator === 'ROCm') {
|
|
385
397
|
acceleratorIcon = 'flame';
|
|
386
398
|
acceleratorClass = 'bg-red-500/20 text-red-600';
|
|
387
399
|
}
|
|
388
400
|
|
|
401
|
+
// Build GPU device selector if multiple GPUs available
|
|
402
|
+
var gpuDeviceSelector = buildGpuDeviceSelector(gpuDevices);
|
|
403
|
+
|
|
389
404
|
container.innerHTML =
|
|
390
405
|
'<div class="space-y-2">' +
|
|
391
406
|
'<div class="flex items-center gap-2 text-sm">' +
|
|
@@ -402,6 +417,7 @@ async function loadSemanticDepsStatus() {
|
|
|
402
417
|
? '<span class="text-xs text-muted-foreground">' + result.providers.join(', ') + '</span>'
|
|
403
418
|
: '') +
|
|
404
419
|
'</div>' +
|
|
420
|
+
gpuDeviceSelector +
|
|
405
421
|
'</div>';
|
|
406
422
|
} else {
|
|
407
423
|
// Build GPU mode options
|
|
@@ -442,7 +458,7 @@ function buildGpuModeSelector(gpuInfo) {
|
|
|
442
458
|
id: 'directml',
|
|
443
459
|
label: 'DirectML',
|
|
444
460
|
desc: t('codexlens.directmlModeDesc') || 'Windows GPU (NVIDIA/AMD/Intel)',
|
|
445
|
-
icon: '
|
|
461
|
+
icon: 'cpu',
|
|
446
462
|
available: gpuInfo.available.includes('directml'),
|
|
447
463
|
recommended: gpuInfo.mode === 'directml'
|
|
448
464
|
},
|
|
@@ -506,6 +522,134 @@ function getSelectedGpuMode() {
|
|
|
506
522
|
return selected ? selected.value : 'cpu';
|
|
507
523
|
}
|
|
508
524
|
|
|
525
|
+
/**
|
|
526
|
+
* Load available GPU devices
|
|
527
|
+
*/
|
|
528
|
+
async function loadGpuDevices() {
|
|
529
|
+
try {
|
|
530
|
+
var response = await fetch('/api/codexlens/gpu/list');
|
|
531
|
+
var result = await response.json();
|
|
532
|
+
if (result.success && result.result) {
|
|
533
|
+
availableGpuDevices = result.result;
|
|
534
|
+
return result.result;
|
|
535
|
+
}
|
|
536
|
+
} catch (err) {
|
|
537
|
+
console.error('GPU devices load failed:', err);
|
|
538
|
+
}
|
|
539
|
+
return { devices: [], selected_device_id: null };
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Build GPU device selector HTML
|
|
544
|
+
*/
|
|
545
|
+
function buildGpuDeviceSelector(gpuDevices) {
|
|
546
|
+
if (!gpuDevices || !gpuDevices.devices || gpuDevices.devices.length === 0) {
|
|
547
|
+
return '';
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Only show selector if there are multiple GPUs
|
|
551
|
+
if (gpuDevices.devices.length < 2) {
|
|
552
|
+
return '';
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
var html =
|
|
556
|
+
'<div class="mt-3 p-3 bg-muted/30 rounded-lg border border-border">' +
|
|
557
|
+
'<div class="text-xs font-medium text-muted-foreground flex items-center gap-1 mb-2">' +
|
|
558
|
+
'<i data-lucide="cpu" class="w-3 h-3"></i>' +
|
|
559
|
+
(t('codexlens.selectGpuDevice') || 'Select GPU Device') +
|
|
560
|
+
'</div>' +
|
|
561
|
+
'<div class="space-y-1">';
|
|
562
|
+
|
|
563
|
+
gpuDevices.devices.forEach(function(device) {
|
|
564
|
+
var isSelected = device.is_selected;
|
|
565
|
+
var vendorIcon = device.vendor === 'nvidia' ? 'zap' : (device.vendor === 'amd' ? 'flame' : 'cpu');
|
|
566
|
+
var vendorColor = device.vendor === 'nvidia' ? 'text-green-500' : (device.vendor === 'amd' ? 'text-red-500' : 'text-blue-500');
|
|
567
|
+
var typeLabel = device.is_discrete ? (t('codexlens.discrete') || 'Discrete') : (t('codexlens.integrated') || 'Integrated');
|
|
568
|
+
|
|
569
|
+
html +=
|
|
570
|
+
'<label class="flex items-center gap-3 p-2 rounded border cursor-pointer hover:bg-muted/50 transition-colors ' +
|
|
571
|
+
(isSelected ? 'border-primary bg-primary/5' : 'border-transparent') + '">' +
|
|
572
|
+
'<input type="radio" name="gpuDevice" value="' + device.device_id + '" ' +
|
|
573
|
+
(isSelected ? 'checked' : '') +
|
|
574
|
+
' class="accent-primary" onchange="selectGpuDevice(' + device.device_id + ')">' +
|
|
575
|
+
'<div class="flex-1">' +
|
|
576
|
+
'<div class="flex items-center gap-2">' +
|
|
577
|
+
'<i data-lucide="' + vendorIcon + '" class="w-4 h-4 ' + vendorColor + '"></i>' +
|
|
578
|
+
'<span class="font-medium text-sm">' + device.name + '</span>' +
|
|
579
|
+
'</div>' +
|
|
580
|
+
'<div class="flex items-center gap-2 mt-0.5">' +
|
|
581
|
+
'<span class="text-xs text-muted-foreground">' + device.vendor.toUpperCase() + '</span>' +
|
|
582
|
+
'<span class="text-xs px-1.5 py-0.5 rounded ' +
|
|
583
|
+
(device.is_discrete ? 'bg-green-500/20 text-green-600' : 'bg-muted text-muted-foreground') + '">' +
|
|
584
|
+
typeLabel +
|
|
585
|
+
'</span>' +
|
|
586
|
+
(device.is_preferred ? '<span class="text-xs bg-primary/20 text-primary px-1.5 py-0.5 rounded">' + (t('common.auto') || 'Auto') + '</span>' : '') +
|
|
587
|
+
'</div>' +
|
|
588
|
+
'</div>' +
|
|
589
|
+
'</label>';
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
html +=
|
|
593
|
+
'</div>' +
|
|
594
|
+
'<button class="btn-xs text-muted-foreground hover:text-foreground mt-2" onclick="resetGpuDevice()">' +
|
|
595
|
+
'<i data-lucide="rotate-ccw" class="w-3 h-3"></i> ' + (t('codexlens.resetToAuto') || 'Reset to Auto') +
|
|
596
|
+
'</button>' +
|
|
597
|
+
'</div>';
|
|
598
|
+
|
|
599
|
+
return html;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Select a GPU device
|
|
604
|
+
*/
|
|
605
|
+
async function selectGpuDevice(deviceId) {
|
|
606
|
+
try {
|
|
607
|
+
showRefreshToast(t('codexlens.selectingGpu') || 'Selecting GPU...', 'info');
|
|
608
|
+
|
|
609
|
+
var response = await fetch('/api/codexlens/gpu/select', {
|
|
610
|
+
method: 'POST',
|
|
611
|
+
headers: { 'Content-Type': 'application/json' },
|
|
612
|
+
body: JSON.stringify({ device_id: deviceId })
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
var result = await response.json();
|
|
616
|
+
if (result.success) {
|
|
617
|
+
showRefreshToast(t('codexlens.gpuSelected') || 'GPU selected', 'success');
|
|
618
|
+
// Reload semantic status to reflect change
|
|
619
|
+
loadSemanticDepsStatus();
|
|
620
|
+
} else {
|
|
621
|
+
showRefreshToast(result.error || 'Failed to select GPU', 'error');
|
|
622
|
+
}
|
|
623
|
+
} catch (err) {
|
|
624
|
+
showRefreshToast(err.message, 'error');
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Reset GPU device selection to auto
|
|
630
|
+
*/
|
|
631
|
+
async function resetGpuDevice() {
|
|
632
|
+
try {
|
|
633
|
+
showRefreshToast(t('codexlens.resettingGpu') || 'Resetting GPU selection...', 'info');
|
|
634
|
+
|
|
635
|
+
var response = await fetch('/api/codexlens/gpu/reset', {
|
|
636
|
+
method: 'POST',
|
|
637
|
+
headers: { 'Content-Type': 'application/json' }
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
var result = await response.json();
|
|
641
|
+
if (result.success) {
|
|
642
|
+
showRefreshToast(t('codexlens.gpuReset') || 'GPU selection reset to auto', 'success');
|
|
643
|
+
// Reload semantic status to reflect change
|
|
644
|
+
loadSemanticDepsStatus();
|
|
645
|
+
} else {
|
|
646
|
+
showRefreshToast(result.error || 'Failed to reset GPU', 'error');
|
|
647
|
+
}
|
|
648
|
+
} catch (err) {
|
|
649
|
+
showRefreshToast(err.message, 'error');
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
509
653
|
/**
|
|
510
654
|
* Install semantic dependencies with GPU mode
|
|
511
655
|
*/
|
|
@@ -570,9 +714,7 @@ async function installSemanticDeps() {
|
|
|
570
714
|
function buildManualDownloadGuide() {
|
|
571
715
|
var modelData = [
|
|
572
716
|
{ 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' }
|
|
717
|
+
{ profile: 'fast', name: 'BAAI/bge-small-en-v1.5', size: '~80 MB' }
|
|
576
718
|
];
|
|
577
719
|
|
|
578
720
|
var html =
|
|
@@ -676,8 +818,8 @@ function buildManualDownloadGuide() {
|
|
|
676
818
|
'<i data-lucide="info" class="w-3.5 h-3.5 mt-0.5 flex-shrink-0"></i>' +
|
|
677
819
|
'<div>' +
|
|
678
820
|
'<strong>' + (t('codexlens.cacheLocation') || 'Cache Location') + ':</strong><br>' +
|
|
679
|
-
'<code class="text-xs">
|
|
680
|
-
'<code class="text-xs">
|
|
821
|
+
'<code class="text-xs">Default: ~/.cache/huggingface</code><br>' +
|
|
822
|
+
'<code class="text-xs text-muted-foreground">(Check HF_HOME env var if set)</code>' +
|
|
681
823
|
'</div>' +
|
|
682
824
|
'</div>' +
|
|
683
825
|
'</div>' +
|
|
@@ -807,9 +949,7 @@ async function downloadModel(profile) {
|
|
|
807
949
|
// Get model info for size estimation
|
|
808
950
|
var modelSizes = {
|
|
809
951
|
'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' }
|
|
952
|
+
'code': { size: 150, time: '2-5' }
|
|
813
953
|
};
|
|
814
954
|
|
|
815
955
|
var modelInfo = modelSizes[profile] || { size: 100, time: '2-5' };
|
|
@@ -933,9 +1073,7 @@ async function downloadModel(profile) {
|
|
|
933
1073
|
function showModelDownloadError(modelCard, profile, error, originalHTML) {
|
|
934
1074
|
var modelNames = {
|
|
935
1075
|
'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'
|
|
1076
|
+
'code': 'jinaai/jina-embeddings-v2-base-code'
|
|
939
1077
|
};
|
|
940
1078
|
|
|
941
1079
|
var modelName = modelNames[profile] || profile;
|
|
@@ -1035,14 +1173,19 @@ async function deleteModel(profile) {
|
|
|
1035
1173
|
/**
|
|
1036
1174
|
* Initialize CodexLens index with bottom floating progress bar
|
|
1037
1175
|
* @param {string} indexType - 'vector' (with embeddings), 'normal' (FTS only), or 'full' (FTS + Vector)
|
|
1038
|
-
* @param {string} embeddingModel - Model profile: 'code', 'fast'
|
|
1176
|
+
* @param {string} embeddingModel - Model profile: 'code', 'fast'
|
|
1177
|
+
* @param {string} embeddingBackend - Backend: 'fastembed' (local) or 'litellm' (API)
|
|
1178
|
+
* @param {number} maxWorkers - Max concurrent API calls for embedding generation (default: 1)
|
|
1039
1179
|
*/
|
|
1040
|
-
async function initCodexLensIndex(indexType, embeddingModel) {
|
|
1180
|
+
async function initCodexLensIndex(indexType, embeddingModel, embeddingBackend, maxWorkers) {
|
|
1041
1181
|
indexType = indexType || 'vector';
|
|
1042
1182
|
embeddingModel = embeddingModel || 'code';
|
|
1183
|
+
embeddingBackend = embeddingBackend || 'fastembed';
|
|
1184
|
+
maxWorkers = maxWorkers || 1;
|
|
1043
1185
|
|
|
1044
|
-
// For vector
|
|
1045
|
-
|
|
1186
|
+
// For vector/full index with local backend, check if semantic dependencies are available
|
|
1187
|
+
// LiteLLM backend uses remote embeddings and does not require fastembed/ONNX deps.
|
|
1188
|
+
if ((indexType === 'vector' || indexType === 'full') && embeddingBackend !== 'litellm') {
|
|
1046
1189
|
try {
|
|
1047
1190
|
var semanticResponse = await fetch('/api/codexlens/semantic/status');
|
|
1048
1191
|
var semanticStatus = await semanticResponse.json();
|
|
@@ -1104,8 +1247,9 @@ async function initCodexLensIndex(indexType, embeddingModel) {
|
|
|
1104
1247
|
// Add model info for vector indexes
|
|
1105
1248
|
var modelLabel = '';
|
|
1106
1249
|
if (indexType !== 'normal') {
|
|
1107
|
-
var modelNames = { code: 'Code', fast: 'Fast'
|
|
1108
|
-
|
|
1250
|
+
var modelNames = { code: 'Code', fast: 'Fast' };
|
|
1251
|
+
var backendLabel = embeddingBackend === 'litellm' ? 'API: ' : '';
|
|
1252
|
+
modelLabel = ' [' + backendLabel + (modelNames[embeddingModel] || embeddingModel) + ']';
|
|
1109
1253
|
}
|
|
1110
1254
|
|
|
1111
1255
|
progressBar.innerHTML =
|
|
@@ -1142,17 +1286,21 @@ async function initCodexLensIndex(indexType, embeddingModel) {
|
|
|
1142
1286
|
var apiIndexType = (indexType === 'full') ? 'vector' : indexType;
|
|
1143
1287
|
|
|
1144
1288
|
// Start indexing with specified type and model
|
|
1145
|
-
startCodexLensIndexing(apiIndexType, embeddingModel);
|
|
1289
|
+
startCodexLensIndexing(apiIndexType, embeddingModel, embeddingBackend, maxWorkers);
|
|
1146
1290
|
}
|
|
1147
1291
|
|
|
1148
1292
|
/**
|
|
1149
1293
|
* Start the indexing process
|
|
1150
1294
|
* @param {string} indexType - 'vector' or 'normal'
|
|
1151
|
-
* @param {string} embeddingModel - Model profile: 'code', 'fast'
|
|
1295
|
+
* @param {string} embeddingModel - Model profile: 'code', 'fast'
|
|
1296
|
+
* @param {string} embeddingBackend - Backend: 'fastembed' (local) or 'litellm' (API)
|
|
1297
|
+
* @param {number} maxWorkers - Max concurrent API calls for embedding generation (default: 1)
|
|
1152
1298
|
*/
|
|
1153
|
-
async function startCodexLensIndexing(indexType, embeddingModel) {
|
|
1299
|
+
async function startCodexLensIndexing(indexType, embeddingModel, embeddingBackend, maxWorkers) {
|
|
1154
1300
|
indexType = indexType || 'vector';
|
|
1155
1301
|
embeddingModel = embeddingModel || 'code';
|
|
1302
|
+
embeddingBackend = embeddingBackend || 'fastembed';
|
|
1303
|
+
maxWorkers = maxWorkers || 1;
|
|
1156
1304
|
var statusText = document.getElementById('codexlensIndexStatus');
|
|
1157
1305
|
var progressBar = document.getElementById('codexlensIndexProgressBar');
|
|
1158
1306
|
var percentText = document.getElementById('codexlensIndexPercent');
|
|
@@ -1184,11 +1332,11 @@ async function startCodexLensIndexing(indexType, embeddingModel) {
|
|
|
1184
1332
|
}
|
|
1185
1333
|
|
|
1186
1334
|
try {
|
|
1187
|
-
console.log('[CodexLens] Starting index for:', projectPath, 'type:', indexType, 'model:', embeddingModel);
|
|
1335
|
+
console.log('[CodexLens] Starting index for:', projectPath, 'type:', indexType, 'model:', embeddingModel, 'backend:', embeddingBackend, 'maxWorkers:', maxWorkers);
|
|
1188
1336
|
var response = await fetch('/api/codexlens/init', {
|
|
1189
1337
|
method: 'POST',
|
|
1190
1338
|
headers: { 'Content-Type': 'application/json' },
|
|
1191
|
-
body: JSON.stringify({ path: projectPath, indexType: indexType, embeddingModel: embeddingModel })
|
|
1339
|
+
body: JSON.stringify({ path: projectPath, indexType: indexType, embeddingModel: embeddingModel, embeddingBackend: embeddingBackend, maxWorkers: maxWorkers })
|
|
1192
1340
|
});
|
|
1193
1341
|
|
|
1194
1342
|
var result = await response.json();
|
|
@@ -1196,7 +1344,15 @@ async function startCodexLensIndexing(indexType, embeddingModel) {
|
|
|
1196
1344
|
|
|
1197
1345
|
// Check if completed successfully (WebSocket might have already reported)
|
|
1198
1346
|
if (result.success) {
|
|
1199
|
-
|
|
1347
|
+
// For vector index, check if embeddings were actually generated
|
|
1348
|
+
var embeddingsResult = result.result && result.result.embeddings;
|
|
1349
|
+
if (indexType === 'vector' && embeddingsResult && !embeddingsResult.generated) {
|
|
1350
|
+
// FTS succeeded but embeddings failed - show partial success
|
|
1351
|
+
var errorMsg = embeddingsResult.error || t('codexlens.embeddingsFailed');
|
|
1352
|
+
handleIndexComplete(false, t('codexlens.ftsSuccessEmbeddingsFailed') || 'FTS index created, but embeddings failed: ' + errorMsg);
|
|
1353
|
+
} else {
|
|
1354
|
+
handleIndexComplete(true, t('codexlens.indexComplete'));
|
|
1355
|
+
}
|
|
1200
1356
|
} else if (!result.success) {
|
|
1201
1357
|
handleIndexComplete(false, result.error || t('common.unknownError'));
|
|
1202
1358
|
}
|
|
@@ -1727,3 +1883,1085 @@ async function cleanCodexLensIndexes() {
|
|
|
1727
1883
|
showRefreshToast(t('common.error') + ': ' + err.message, 'error');
|
|
1728
1884
|
}
|
|
1729
1885
|
}
|
|
1886
|
+
|
|
1887
|
+
// ============================================================
|
|
1888
|
+
// CODEXLENS MANAGER PAGE (Independent View)
|
|
1889
|
+
// ============================================================
|
|
1890
|
+
|
|
1891
|
+
/**
|
|
1892
|
+
* Render CodexLens Manager as an independent page view
|
|
1893
|
+
*/
|
|
1894
|
+
async function renderCodexLensManager() {
|
|
1895
|
+
var container = document.getElementById('mainContent');
|
|
1896
|
+
if (!container) return;
|
|
1897
|
+
|
|
1898
|
+
// Hide stats grid and search
|
|
1899
|
+
var statsGrid = document.getElementById('statsGrid');
|
|
1900
|
+
var searchContainer = document.querySelector('.search-container');
|
|
1901
|
+
if (statsGrid) statsGrid.style.display = 'none';
|
|
1902
|
+
if (searchContainer) searchContainer.style.display = 'none';
|
|
1903
|
+
|
|
1904
|
+
container.innerHTML = '<div class="flex items-center justify-center py-12"><div class="animate-spin w-6 h-6 border-2 border-primary border-t-transparent rounded-full"></div><span class="ml-3">' + t('common.loading') + '</span></div>';
|
|
1905
|
+
|
|
1906
|
+
try {
|
|
1907
|
+
// Use aggregated endpoint for faster page load (single API call)
|
|
1908
|
+
var dashboardData = null;
|
|
1909
|
+
var config = { index_dir: '~/.codexlens/indexes', index_count: 0 };
|
|
1910
|
+
|
|
1911
|
+
if (typeof loadCodexLensDashboardInit === 'function') {
|
|
1912
|
+
console.log('[CodexLens] Using aggregated dashboard-init endpoint...');
|
|
1913
|
+
dashboardData = await loadCodexLensDashboardInit();
|
|
1914
|
+
if (dashboardData && dashboardData.config) {
|
|
1915
|
+
config = dashboardData.config;
|
|
1916
|
+
console.log('[CodexLens] Dashboard init loaded, config:', config);
|
|
1917
|
+
}
|
|
1918
|
+
} else if (typeof loadCodexLensStatus === 'function') {
|
|
1919
|
+
// Fallback to legacy individual calls
|
|
1920
|
+
console.log('[CodexLens] Fallback to legacy loadCodexLensStatus...');
|
|
1921
|
+
await loadCodexLensStatus();
|
|
1922
|
+
var response = await fetch('/api/codexlens/config');
|
|
1923
|
+
config = await response.json();
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
// Load LiteLLM API config for embedding backend options (parallel with page render)
|
|
1927
|
+
var litellmPromise = (async () => {
|
|
1928
|
+
try {
|
|
1929
|
+
console.log('[CodexLens] Loading LiteLLM config...');
|
|
1930
|
+
var litellmResponse = await fetch('/api/litellm-api/config');
|
|
1931
|
+
if (litellmResponse.ok) {
|
|
1932
|
+
window.litellmApiConfig = await litellmResponse.json();
|
|
1933
|
+
console.log('[CodexLens] LiteLLM config loaded, providers:', window.litellmApiConfig?.providers?.length || 0);
|
|
1934
|
+
}
|
|
1935
|
+
} catch (e) {
|
|
1936
|
+
console.warn('[CodexLens] Could not load LiteLLM config:', e);
|
|
1937
|
+
}
|
|
1938
|
+
})();
|
|
1939
|
+
|
|
1940
|
+
container.innerHTML = buildCodexLensManagerPage(config);
|
|
1941
|
+
if (window.lucide) lucide.createIcons();
|
|
1942
|
+
initCodexLensManagerPageEvents(config);
|
|
1943
|
+
|
|
1944
|
+
// Load additional data in parallel (non-blocking)
|
|
1945
|
+
var isInstalled = window.cliToolsStatus?.codexlens?.installed || dashboardData?.installed;
|
|
1946
|
+
|
|
1947
|
+
// Wait for LiteLLM config before loading semantic deps (it may need provider info)
|
|
1948
|
+
await litellmPromise;
|
|
1949
|
+
|
|
1950
|
+
// Always load semantic deps status - it needs GPU detection and device list
|
|
1951
|
+
// which are not included in the aggregated endpoint
|
|
1952
|
+
loadSemanticDepsStatus();
|
|
1953
|
+
|
|
1954
|
+
loadModelList();
|
|
1955
|
+
|
|
1956
|
+
// Load index stats for the Index Manager section
|
|
1957
|
+
if (isInstalled) {
|
|
1958
|
+
loadIndexStatsForPage();
|
|
1959
|
+
}
|
|
1960
|
+
} catch (err) {
|
|
1961
|
+
container.innerHTML = '<div class="text-center py-12 text-destructive"><i data-lucide="alert-circle" class="w-8 h-8 mx-auto mb-2"></i><p>' + t('common.error') + ': ' + err.message + '</p></div>';
|
|
1962
|
+
if (window.lucide) lucide.createIcons();
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
/**
|
|
1967
|
+
* Build CodexLens Manager page content
|
|
1968
|
+
*/
|
|
1969
|
+
function buildCodexLensManagerPage(config) {
|
|
1970
|
+
var indexDir = config.index_dir || '~/.codexlens/indexes';
|
|
1971
|
+
var indexCount = config.index_count || 0;
|
|
1972
|
+
var isInstalled = window.cliToolsStatus?.codexlens?.installed || false;
|
|
1973
|
+
|
|
1974
|
+
// Build model options for vector indexing
|
|
1975
|
+
var modelOptions = buildModelSelectOptionsForPage();
|
|
1976
|
+
|
|
1977
|
+
return '<div class="codexlens-manager-page space-y-6">' +
|
|
1978
|
+
// Header with status
|
|
1979
|
+
'<div class="bg-card border border-border rounded-lg p-6">' +
|
|
1980
|
+
'<div class="flex items-center justify-between flex-wrap gap-4">' +
|
|
1981
|
+
'<div class="flex items-center gap-4">' +
|
|
1982
|
+
'<div class="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center">' +
|
|
1983
|
+
'<i data-lucide="database" class="w-6 h-6 text-primary"></i>' +
|
|
1984
|
+
'</div>' +
|
|
1985
|
+
'<div>' +
|
|
1986
|
+
'<h2 class="text-xl font-bold">' + t('codexlens.config') + '</h2>' +
|
|
1987
|
+
'<p class="text-sm text-muted-foreground">' + t('codexlens.configDesc') + '</p>' +
|
|
1988
|
+
'</div>' +
|
|
1989
|
+
'</div>' +
|
|
1990
|
+
'<div class="flex items-center gap-4">' +
|
|
1991
|
+
(isInstalled
|
|
1992
|
+
? '<span class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-sm font-medium bg-success/10 text-success border border-success/20"><i data-lucide="check-circle" class="w-4 h-4"></i> ' + t('codexlens.installed') + '</span>'
|
|
1993
|
+
: '<span class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-sm font-medium bg-muted text-muted-foreground border border-border"><i data-lucide="circle" class="w-4 h-4"></i> ' + t('codexlens.notInstalled') + '</span>') +
|
|
1994
|
+
'<div class="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-primary/5 border border-primary/20">' +
|
|
1995
|
+
'<span class="text-sm text-muted-foreground">' + t('codexlens.indexes') + ':</span>' +
|
|
1996
|
+
'<span class="text-lg font-bold text-primary">' + indexCount + '</span>' +
|
|
1997
|
+
'</div>' +
|
|
1998
|
+
'</div>' +
|
|
1999
|
+
'</div>' +
|
|
2000
|
+
'</div>' +
|
|
2001
|
+
|
|
2002
|
+
(isInstalled
|
|
2003
|
+
? // Installed: Show full management UI
|
|
2004
|
+
'<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">' +
|
|
2005
|
+
// Left Column
|
|
2006
|
+
'<div class="space-y-6">' +
|
|
2007
|
+
// Create Index Section
|
|
2008
|
+
'<div class="bg-card border border-border rounded-lg p-5">' +
|
|
2009
|
+
'<h4 class="text-lg font-semibold mb-4 flex items-center gap-2"><i data-lucide="layers" class="w-5 h-5 text-primary"></i> ' + t('codexlens.createIndex') + '</h4>' +
|
|
2010
|
+
'<div class="space-y-4">' +
|
|
2011
|
+
// Backend selector (fastembed local or litellm API)
|
|
2012
|
+
'<div class="mb-4">' +
|
|
2013
|
+
'<label class="block text-sm font-medium mb-1.5">' + (t('codexlens.embeddingBackend') || 'Embedding Backend') + '</label>' +
|
|
2014
|
+
'<select id="pageBackendSelect" class="w-full px-3 py-2 border border-border rounded-lg bg-background text-sm" onchange="onEmbeddingBackendChange()">' +
|
|
2015
|
+
'<option value="fastembed">' + (t('codexlens.localFastembed') || 'Local (FastEmbed)') + '</option>' +
|
|
2016
|
+
'<option value="litellm">' + (t('codexlens.apiLitellm') || 'API (LiteLLM)') + '</option>' +
|
|
2017
|
+
'</select>' +
|
|
2018
|
+
'<p class="text-xs text-muted-foreground mt-1">' + (t('codexlens.backendHint') || 'Select local model or remote API endpoint') + '</p>' +
|
|
2019
|
+
'</div>' +
|
|
2020
|
+
// Model selector
|
|
2021
|
+
'<div>' +
|
|
2022
|
+
'<label class="block text-sm font-medium mb-1.5">' + t('codexlens.embeddingModel') + '</label>' +
|
|
2023
|
+
'<select id="pageModelSelect" class="w-full px-3 py-2 border border-border rounded-lg bg-background text-sm">' +
|
|
2024
|
+
modelOptions +
|
|
2025
|
+
'</select>' +
|
|
2026
|
+
'<p class="text-xs text-muted-foreground mt-1">' + t('codexlens.modelHint') + '</p>' +
|
|
2027
|
+
'</div>' +
|
|
2028
|
+
// Concurrency selector (only for LiteLLM backend)
|
|
2029
|
+
'<div id="concurrencySelector" class="hidden">' +
|
|
2030
|
+
'<label class="block text-sm font-medium mb-1.5">' + t('codexlens.concurrency') + '</label>' +
|
|
2031
|
+
'<div class="flex items-center gap-2">' +
|
|
2032
|
+
'<input type="number" id="pageConcurrencyInput" min="1" value="4" ' +
|
|
2033
|
+
'class="w-24 px-3 py-2 border border-border rounded-lg bg-background text-sm" ' +
|
|
2034
|
+
'onchange="validateConcurrencyInput(this)" />' +
|
|
2035
|
+
'<span class="text-sm text-muted-foreground">workers</span>' +
|
|
2036
|
+
'<span class="text-xs text-primary ml-2">(4 = recommended)</span>' +
|
|
2037
|
+
'</div>' +
|
|
2038
|
+
'<p class="text-xs text-muted-foreground mt-1">' + t('codexlens.concurrencyHint') + '</p>' +
|
|
2039
|
+
'</div>' +
|
|
2040
|
+
// Multi-Provider Rotation (only for LiteLLM backend) - Simplified, config in API Settings
|
|
2041
|
+
'<div id="rotationSection" class="hidden">' +
|
|
2042
|
+
'<div class="border border-border rounded-lg p-3 bg-muted/30">' +
|
|
2043
|
+
'<div class="flex items-center justify-between mb-2">' +
|
|
2044
|
+
'<div class="flex items-center gap-2">' +
|
|
2045
|
+
'<i data-lucide="rotate-cw" class="w-4 h-4 text-primary"></i>' +
|
|
2046
|
+
'<span class="text-sm font-medium">' + t('codexlens.rotation') + '</span>' +
|
|
2047
|
+
'</div>' +
|
|
2048
|
+
'<div id="rotationStatusBadge" class="text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground">' +
|
|
2049
|
+
t('common.disabled') +
|
|
2050
|
+
'</div>' +
|
|
2051
|
+
'</div>' +
|
|
2052
|
+
'<p class="text-xs text-muted-foreground mb-2">' + t('codexlens.rotationDesc') + '</p>' +
|
|
2053
|
+
'<div id="rotationDetails" class="text-xs text-muted-foreground mb-3 hidden">' +
|
|
2054
|
+
'<span id="rotationModelName"></span> · <span id="rotationEndpointCount"></span>' +
|
|
2055
|
+
'</div>' +
|
|
2056
|
+
'<div class="flex items-center gap-2">' +
|
|
2057
|
+
'<a href="#" class="btn-sm btn-outline flex items-center gap-1.5" onclick="navigateToApiSettingsEmbeddingPool(); return false;">' +
|
|
2058
|
+
'<i data-lucide="external-link" class="w-3.5 h-3.5"></i>' +
|
|
2059
|
+
t('codexlens.configureInApiSettings') +
|
|
2060
|
+
'</a>' +
|
|
2061
|
+
'</div>' +
|
|
2062
|
+
'</div>' +
|
|
2063
|
+
'</div>' +
|
|
2064
|
+
// Index buttons - two modes: full (FTS + Vector) or FTS only
|
|
2065
|
+
'<div class="grid grid-cols-2 gap-3">' +
|
|
2066
|
+
'<button class="btn btn-primary flex items-center justify-center gap-2 py-3" onclick="initCodexLensIndexFromPage(\'full\')" title="' + t('codexlens.fullIndexDesc') + '">' +
|
|
2067
|
+
'<i data-lucide="layers" class="w-4 h-4"></i>' +
|
|
2068
|
+
'<span>' + t('codexlens.fullIndex') + '</span>' +
|
|
2069
|
+
'</button>' +
|
|
2070
|
+
'<button class="btn btn-outline flex items-center justify-center gap-2 py-3" onclick="initCodexLensIndexFromPage(\'normal\')" title="' + t('codexlens.ftsIndexDesc') + '">' +
|
|
2071
|
+
'<i data-lucide="file-text" class="w-4 h-4"></i>' +
|
|
2072
|
+
'<span>' + t('codexlens.ftsIndex') + '</span>' +
|
|
2073
|
+
'</button>' +
|
|
2074
|
+
'</div>' +
|
|
2075
|
+
'<p class="text-xs text-muted-foreground">' + t('codexlens.indexTypeHint') + '</p>' +
|
|
2076
|
+
'</div>' +
|
|
2077
|
+
'</div>' +
|
|
2078
|
+
// Storage Path Section
|
|
2079
|
+
'<div class="bg-card border border-border rounded-lg p-5">' +
|
|
2080
|
+
'<h4 class="text-lg font-semibold mb-4 flex items-center gap-2"><i data-lucide="folder" class="w-5 h-5 text-primary"></i> ' + t('codexlens.indexStoragePath') + '</h4>' +
|
|
2081
|
+
'<div class="space-y-3">' +
|
|
2082
|
+
'<div>' +
|
|
2083
|
+
'<label class="block text-sm font-medium mb-1.5">' + t('codexlens.currentPath') + '</label>' +
|
|
2084
|
+
'<div class="text-sm text-muted-foreground bg-muted/50 rounded-lg px-3 py-2 font-mono border border-border truncate" title="' + indexDir + '">' + indexDir + '</div>' +
|
|
2085
|
+
'</div>' +
|
|
2086
|
+
'<div>' +
|
|
2087
|
+
'<label class="block text-sm font-medium mb-1.5">' + t('codexlens.newStoragePath') + '</label>' +
|
|
2088
|
+
'<div class="flex gap-2">' +
|
|
2089
|
+
'<input type="text" id="indexDirInput" value="' + indexDir + '" class="flex-1 px-3 py-2 border border-border rounded-lg bg-background text-foreground text-sm" />' +
|
|
2090
|
+
'<button class="btn-sm btn-primary" id="saveIndexPathBtn"><i data-lucide="save" class="w-3.5 h-3.5"></i></button>' +
|
|
2091
|
+
'</div>' +
|
|
2092
|
+
'<p class="text-xs text-muted-foreground mt-1">' + t('codexlens.pathInfo') + '</p>' +
|
|
2093
|
+
'</div>' +
|
|
2094
|
+
'</div>' +
|
|
2095
|
+
'</div>' +
|
|
2096
|
+
// Maintenance Section
|
|
2097
|
+
'<div class="bg-card border border-border rounded-lg p-5">' +
|
|
2098
|
+
'<h4 class="text-lg font-semibold mb-4 flex items-center gap-2"><i data-lucide="settings" class="w-5 h-5 text-primary"></i> ' + t('codexlens.maintenance') + '</h4>' +
|
|
2099
|
+
'<div class="flex flex-wrap gap-2">' +
|
|
2100
|
+
'<button class="btn-sm btn-outline" onclick="cleanCurrentWorkspaceIndex()"><i data-lucide="folder-x" class="w-3.5 h-3.5"></i> ' + t('codexlens.cleanCurrentWorkspace') + '</button>' +
|
|
2101
|
+
'<button class="btn-sm btn-outline" onclick="cleanCodexLensIndexes()"><i data-lucide="trash" class="w-3.5 h-3.5"></i> ' + t('codexlens.cleanAllIndexes') + '</button>' +
|
|
2102
|
+
'<button class="btn-sm btn-destructive" onclick="uninstallCodexLensFromManager()"><i data-lucide="trash-2" class="w-3.5 h-3.5"></i> ' + t('cli.uninstall') + '</button>' +
|
|
2103
|
+
'</div>' +
|
|
2104
|
+
'</div>' +
|
|
2105
|
+
'</div>' +
|
|
2106
|
+
// Right Column
|
|
2107
|
+
'<div class="space-y-6">' +
|
|
2108
|
+
// Semantic Dependencies
|
|
2109
|
+
'<div class="bg-card border border-border rounded-lg p-5">' +
|
|
2110
|
+
'<h4 class="text-lg font-semibold mb-4 flex items-center gap-2"><i data-lucide="cpu" class="w-5 h-5 text-primary"></i> ' + t('codexlens.semanticDeps') + '</h4>' +
|
|
2111
|
+
'<div id="semanticDepsStatus" class="space-y-3">' +
|
|
2112
|
+
'<div class="flex items-center gap-2 text-sm text-muted-foreground">' +
|
|
2113
|
+
'<div class="animate-spin w-4 h-4 border-2 border-primary border-t-transparent rounded-full"></div> ' + t('codexlens.checkingDeps') +
|
|
2114
|
+
'</div>' +
|
|
2115
|
+
'</div>' +
|
|
2116
|
+
'</div>' +
|
|
2117
|
+
// Model Management
|
|
2118
|
+
'<div class="bg-card border border-border rounded-lg p-5">' +
|
|
2119
|
+
'<h4 class="text-lg font-semibold mb-4 flex items-center gap-2"><i data-lucide="box" class="w-5 h-5 text-primary"></i> ' + t('codexlens.modelManagement') + '</h4>' +
|
|
2120
|
+
'<div id="modelListContainer" class="space-y-3">' +
|
|
2121
|
+
'<div class="flex items-center gap-2 text-sm text-muted-foreground">' +
|
|
2122
|
+
'<div class="animate-spin w-4 h-4 border-2 border-primary border-t-transparent rounded-full"></div> ' + t('codexlens.loadingModels') +
|
|
2123
|
+
'</div>' +
|
|
2124
|
+
'</div>' +
|
|
2125
|
+
'</div>' +
|
|
2126
|
+
'</div>' +
|
|
2127
|
+
'</div>' +
|
|
2128
|
+
// Index Manager Section
|
|
2129
|
+
'<div class="bg-card border border-border rounded-lg overflow-hidden" id="indexManagerSection">' +
|
|
2130
|
+
'<div class="bg-muted/30 border-b border-border px-4 py-3 flex items-center justify-between">' +
|
|
2131
|
+
'<div class="flex items-center gap-2">' +
|
|
2132
|
+
'<i data-lucide="database" class="w-4 h-4 text-primary"></i>' +
|
|
2133
|
+
'<span class="font-medium text-foreground">' + t('index.manager') + '</span>' +
|
|
2134
|
+
'<span class="text-xs px-2 py-0.5 bg-muted rounded-full text-muted-foreground" id="indexTotalSize">-</span>' +
|
|
2135
|
+
'</div>' +
|
|
2136
|
+
'<div class="flex items-center gap-2">' +
|
|
2137
|
+
'<button onclick="loadIndexStatsForPage()" class="text-xs px-2 py-1 text-muted-foreground hover:text-foreground hover:bg-muted rounded transition-colors" title="' + t('common.refresh') + '">' +
|
|
2138
|
+
'<i data-lucide="refresh-cw" class="w-3.5 h-3.5"></i>' +
|
|
2139
|
+
'</button>' +
|
|
2140
|
+
'</div>' +
|
|
2141
|
+
'</div>' +
|
|
2142
|
+
'<div class="p-4">' +
|
|
2143
|
+
'<div class="flex items-center gap-2 mb-3 text-xs text-muted-foreground">' +
|
|
2144
|
+
'<i data-lucide="folder" class="w-3.5 h-3.5"></i>' +
|
|
2145
|
+
'<span class="font-mono truncate" id="indexDirDisplay" title="' + indexDir + '">' + indexDir + '</span>' +
|
|
2146
|
+
'</div>' +
|
|
2147
|
+
'<div class="grid grid-cols-4 gap-3 mb-4">' +
|
|
2148
|
+
'<div class="bg-muted/30 rounded-lg p-3 text-center">' +
|
|
2149
|
+
'<div class="text-lg font-semibold text-foreground" id="indexProjectCount">-</div>' +
|
|
2150
|
+
'<div class="text-xs text-muted-foreground">' + t('index.projects') + '</div>' +
|
|
2151
|
+
'</div>' +
|
|
2152
|
+
'<div class="bg-muted/30 rounded-lg p-3 text-center">' +
|
|
2153
|
+
'<div class="text-lg font-semibold text-foreground" id="indexTotalSizeVal">-</div>' +
|
|
2154
|
+
'<div class="text-xs text-muted-foreground">' + t('index.totalSize') + '</div>' +
|
|
2155
|
+
'</div>' +
|
|
2156
|
+
'<div class="bg-muted/30 rounded-lg p-3 text-center">' +
|
|
2157
|
+
'<div class="text-lg font-semibold text-foreground" id="indexVectorCount">-</div>' +
|
|
2158
|
+
'<div class="text-xs text-muted-foreground">' + t('index.vectorIndexes') + '</div>' +
|
|
2159
|
+
'</div>' +
|
|
2160
|
+
'<div class="bg-muted/30 rounded-lg p-3 text-center">' +
|
|
2161
|
+
'<div class="text-lg font-semibold text-foreground" id="indexFtsCount">-</div>' +
|
|
2162
|
+
'<div class="text-xs text-muted-foreground">' + t('index.ftsIndexes') + '</div>' +
|
|
2163
|
+
'</div>' +
|
|
2164
|
+
'</div>' +
|
|
2165
|
+
'<div class="border border-border rounded-lg overflow-hidden">' +
|
|
2166
|
+
'<table class="w-full text-sm">' +
|
|
2167
|
+
'<thead class="bg-muted/50">' +
|
|
2168
|
+
'<tr class="text-xs text-muted-foreground">' +
|
|
2169
|
+
'<th class="py-2 px-2 text-left font-medium">' + t('index.projectId') + '</th>' +
|
|
2170
|
+
'<th class="py-2 px-2 text-right font-medium">' + t('index.size') + '</th>' +
|
|
2171
|
+
'<th class="py-2 px-2 text-center font-medium">' + t('index.type') + '</th>' +
|
|
2172
|
+
'<th class="py-2 px-2 text-right font-medium">' + t('index.lastModified') + '</th>' +
|
|
2173
|
+
'<th class="py-2 px-1 w-8"></th>' +
|
|
2174
|
+
'</tr>' +
|
|
2175
|
+
'</thead>' +
|
|
2176
|
+
'<tbody id="indexTableBody">' +
|
|
2177
|
+
'<tr><td colspan="5" class="py-4 text-center text-muted-foreground text-sm">' + t('common.loading') + '</td></tr>' +
|
|
2178
|
+
'</tbody>' +
|
|
2179
|
+
'</table>' +
|
|
2180
|
+
'</div>' +
|
|
2181
|
+
'<div class="mt-4 flex justify-end">' +
|
|
2182
|
+
'<button onclick="cleanAllIndexesFromPage()" class="text-xs px-3 py-1.5 bg-destructive/10 text-destructive hover:bg-destructive/20 rounded transition-colors flex items-center gap-1.5">' +
|
|
2183
|
+
'<i data-lucide="trash" class="w-3.5 h-3.5"></i>' +
|
|
2184
|
+
t('index.cleanAll') +
|
|
2185
|
+
'</button>' +
|
|
2186
|
+
'</div>' +
|
|
2187
|
+
'</div>' +
|
|
2188
|
+
'</div>' +
|
|
2189
|
+
// Test Search Section
|
|
2190
|
+
'<div class="bg-card border border-border rounded-lg p-5">' +
|
|
2191
|
+
'<h4 class="text-lg font-semibold mb-4 flex items-center gap-2"><i data-lucide="search" class="w-5 h-5 text-primary"></i> ' + t('codexlens.testSearch') + '</h4>' +
|
|
2192
|
+
'<div class="space-y-4">' +
|
|
2193
|
+
'<div class="flex gap-3">' +
|
|
2194
|
+
'<select id="searchTypeSelect" class="flex-1 px-3 py-2 border border-border rounded-lg bg-background text-sm">' +
|
|
2195
|
+
'<option value="search">' + t('codexlens.textSearch') + '</option>' +
|
|
2196
|
+
'<option value="search_files">' + t('codexlens.fileSearch') + '</option>' +
|
|
2197
|
+
'<option value="symbol">' + t('codexlens.symbolSearch') + '</option>' +
|
|
2198
|
+
'</select>' +
|
|
2199
|
+
'<select id="searchModeSelect" class="flex-1 px-3 py-2 border border-border rounded-lg bg-background text-sm">' +
|
|
2200
|
+
'<option value="exact">' + t('codexlens.exactMode') + '</option>' +
|
|
2201
|
+
'<option value="fuzzy">' + t('codexlens.fuzzyMode') + '</option>' +
|
|
2202
|
+
'<option value="hybrid">' + t('codexlens.hybridMode') + '</option>' +
|
|
2203
|
+
'<option value="vector">' + t('codexlens.vectorMode') + '</option>' +
|
|
2204
|
+
'</select>' +
|
|
2205
|
+
'</div>' +
|
|
2206
|
+
'<div class="flex gap-3 items-center">' +
|
|
2207
|
+
'<div class="flex items-center gap-2">' +
|
|
2208
|
+
'<label class="text-xs text-muted-foreground whitespace-nowrap">' + t('codexlens.resultLimit') + '</label>' +
|
|
2209
|
+
'<input type="number" id="searchLimitInput" class="w-16 px-2 py-1.5 border border-border rounded-lg bg-background text-sm text-center" value="5" min="1" max="50" />' +
|
|
2210
|
+
'</div>' +
|
|
2211
|
+
'<div class="flex items-center gap-2">' +
|
|
2212
|
+
'<label class="text-xs text-muted-foreground whitespace-nowrap">' + t('codexlens.contentLength') + '</label>' +
|
|
2213
|
+
'<input type="number" id="contentLengthInput" class="w-20 px-2 py-1.5 border border-border rounded-lg bg-background text-sm text-center" value="200" min="50" max="2000" />' +
|
|
2214
|
+
'</div>' +
|
|
2215
|
+
'<div class="flex items-center gap-2">' +
|
|
2216
|
+
'<label class="text-xs text-muted-foreground whitespace-nowrap">' + t('codexlens.extraFiles') + '</label>' +
|
|
2217
|
+
'<input type="number" id="extraFilesInput" class="w-16 px-2 py-1.5 border border-border rounded-lg bg-background text-sm text-center" value="10" min="0" max="50" />' +
|
|
2218
|
+
'</div>' +
|
|
2219
|
+
'</div>' +
|
|
2220
|
+
'<div class="flex gap-3">' +
|
|
2221
|
+
'<input type="text" id="searchQueryInput" class="flex-1 px-3 py-2 border border-border rounded-lg bg-background text-sm" placeholder="' + t('codexlens.searchPlaceholder') + '" />' +
|
|
2222
|
+
'<button class="btn-sm btn-primary" id="runSearchBtn"><i data-lucide="search" class="w-3.5 h-3.5"></i> ' + t('codexlens.runSearch') + '</button>' +
|
|
2223
|
+
'</div>' +
|
|
2224
|
+
'<div id="searchResults" class="hidden">' +
|
|
2225
|
+
'<div class="flex items-center justify-between mb-2">' +
|
|
2226
|
+
'<span class="text-sm font-medium">' + t('codexlens.results') + ':</span>' +
|
|
2227
|
+
'<span id="searchResultCount" class="text-xs text-muted-foreground"></span>' +
|
|
2228
|
+
'</div>' +
|
|
2229
|
+
'<pre id="searchResultContent" class="bg-muted/50 border border-border p-3 rounded-lg text-xs overflow-auto max-h-64 font-mono"></pre>' +
|
|
2230
|
+
'</div>' +
|
|
2231
|
+
'</div>' +
|
|
2232
|
+
'</div>'
|
|
2233
|
+
|
|
2234
|
+
: // Not installed: Show install prompt
|
|
2235
|
+
'<div class="bg-card border border-border rounded-lg p-8">' +
|
|
2236
|
+
'<div class="text-center max-w-md mx-auto">' +
|
|
2237
|
+
'<div class="w-16 h-16 rounded-full bg-primary/10 flex items-center justify-center mx-auto mb-4">' +
|
|
2238
|
+
'<i data-lucide="database" class="w-8 h-8 text-primary"></i>' +
|
|
2239
|
+
'</div>' +
|
|
2240
|
+
'<h3 class="text-lg font-semibold mb-2">' + t('codexlens.installCodexLens') + '</h3>' +
|
|
2241
|
+
'<p class="text-sm text-muted-foreground mb-6">' + t('codexlens.installFirst') + '</p>' +
|
|
2242
|
+
'<button class="btn btn-primary" onclick="installCodexLensFromManager()">' +
|
|
2243
|
+
'<i data-lucide="download" class="w-4 h-4"></i> ' + t('codexlens.installCodexLens') +
|
|
2244
|
+
'</button>' +
|
|
2245
|
+
'</div>' +
|
|
2246
|
+
'</div>'
|
|
2247
|
+
) +
|
|
2248
|
+
'</div>';
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
/**
|
|
2252
|
+
* Build model select options for the page
|
|
2253
|
+
*/
|
|
2254
|
+
function buildModelSelectOptionsForPage() {
|
|
2255
|
+
var installedModels = window.cliToolsStatus?.codexlens?.installedModels || [];
|
|
2256
|
+
var allModels = window.cliToolsStatus?.codexlens?.allModels || [];
|
|
2257
|
+
|
|
2258
|
+
if (allModels.length === 0) {
|
|
2259
|
+
// Fallback to default models if not loaded
|
|
2260
|
+
return '<option value="code">code (default)</option>' +
|
|
2261
|
+
'<option value="fast">fast</option>';
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
var options = '';
|
|
2265
|
+
allModels.forEach(function(model) {
|
|
2266
|
+
var isInstalled = model.installed || installedModels.includes(model.profile);
|
|
2267
|
+
var label = model.profile + (isInstalled ? ' ✓' : '');
|
|
2268
|
+
var selected = model.profile === 'code' ? ' selected' : '';
|
|
2269
|
+
options += '<option value="' + model.profile + '"' + selected + '>' + label + '</option>';
|
|
2270
|
+
});
|
|
2271
|
+
return options;
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
/**
|
|
2275
|
+
* Validate concurrency input value (min 1, no max limit)
|
|
2276
|
+
*/
|
|
2277
|
+
function validateConcurrencyInput(input) {
|
|
2278
|
+
var value = parseInt(input.value, 10);
|
|
2279
|
+
if (isNaN(value) || value < 1) {
|
|
2280
|
+
input.value = 1;
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
/**
|
|
2285
|
+
* Handle embedding backend change
|
|
2286
|
+
*/
|
|
2287
|
+
function onEmbeddingBackendChange() {
|
|
2288
|
+
var backendSelect = document.getElementById('pageBackendSelect');
|
|
2289
|
+
var modelSelect = document.getElementById('pageModelSelect');
|
|
2290
|
+
var concurrencySelector = document.getElementById('concurrencySelector');
|
|
2291
|
+
var rotationSection = document.getElementById('rotationSection');
|
|
2292
|
+
if (!backendSelect || !modelSelect) {
|
|
2293
|
+
console.warn('[CodexLens] Backend or model select not found');
|
|
2294
|
+
return;
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
var backend = backendSelect.value;
|
|
2298
|
+
console.log('[CodexLens] Backend changed to:', backend);
|
|
2299
|
+
console.log('[CodexLens] Current litellmApiConfig:', window.litellmApiConfig);
|
|
2300
|
+
|
|
2301
|
+
if (backend === 'litellm') {
|
|
2302
|
+
// Load LiteLLM embedding models
|
|
2303
|
+
console.log('[CodexLens] Building LiteLLM model options...');
|
|
2304
|
+
var options = buildLiteLLMModelOptions();
|
|
2305
|
+
console.log('[CodexLens] Built options HTML:', options);
|
|
2306
|
+
modelSelect.innerHTML = options;
|
|
2307
|
+
// Show concurrency selector for API backend
|
|
2308
|
+
if (concurrencySelector) {
|
|
2309
|
+
concurrencySelector.classList.remove('hidden');
|
|
2310
|
+
}
|
|
2311
|
+
// Show rotation section and load status
|
|
2312
|
+
if (rotationSection) {
|
|
2313
|
+
rotationSection.classList.remove('hidden');
|
|
2314
|
+
loadRotationStatus();
|
|
2315
|
+
}
|
|
2316
|
+
} else {
|
|
2317
|
+
// Load local fastembed models
|
|
2318
|
+
modelSelect.innerHTML = buildModelSelectOptionsForPage();
|
|
2319
|
+
// Hide concurrency selector for local backend
|
|
2320
|
+
if (concurrencySelector) {
|
|
2321
|
+
concurrencySelector.classList.add('hidden');
|
|
2322
|
+
}
|
|
2323
|
+
// Hide rotation section for local backend
|
|
2324
|
+
if (rotationSection) {
|
|
2325
|
+
rotationSection.classList.add('hidden');
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
/**
|
|
2331
|
+
* Build LiteLLM model options from config
|
|
2332
|
+
*/
|
|
2333
|
+
function buildLiteLLMModelOptions() {
|
|
2334
|
+
var litellmConfig = window.litellmApiConfig || {};
|
|
2335
|
+
console.log('[CodexLens] litellmApiConfig:', litellmConfig);
|
|
2336
|
+
|
|
2337
|
+
var providers = litellmConfig.providers || [];
|
|
2338
|
+
console.log('[CodexLens] providers count:', providers.length);
|
|
2339
|
+
|
|
2340
|
+
var options = '';
|
|
2341
|
+
|
|
2342
|
+
providers.forEach(function(provider) {
|
|
2343
|
+
console.log('[CodexLens] Processing provider:', provider.id, 'enabled:', provider.enabled);
|
|
2344
|
+
if (!provider.enabled) return;
|
|
2345
|
+
|
|
2346
|
+
// Check embeddingModels array (config structure)
|
|
2347
|
+
var models = provider.embeddingModels || provider.models || [];
|
|
2348
|
+
console.log('[CodexLens] Provider', provider.id, 'embeddingModels:', models.length, models);
|
|
2349
|
+
|
|
2350
|
+
models.forEach(function(model) {
|
|
2351
|
+
console.log('[CodexLens] Processing model:', model.id, 'type:', model.type, 'enabled:', model.enabled);
|
|
2352
|
+
// Accept embedding type or models from embeddingModels array
|
|
2353
|
+
if (model.type && model.type !== 'embedding') return;
|
|
2354
|
+
if (!model.enabled) return;
|
|
2355
|
+
var label = model.name || model.id;
|
|
2356
|
+
var providerName = provider.name || provider.id;
|
|
2357
|
+
var selected = options === '' ? ' selected' : '';
|
|
2358
|
+
options += '<option value="' + model.id + '"' + selected + '>' + label + ' (' + providerName + ')</option>';
|
|
2359
|
+
console.log('[CodexLens] Added option:', label, 'from', providerName);
|
|
2360
|
+
});
|
|
2361
|
+
});
|
|
2362
|
+
|
|
2363
|
+
if (options === '') {
|
|
2364
|
+
console.warn('[CodexLens] No embedding models found in LiteLLM config');
|
|
2365
|
+
options = '<option value="" disabled selected>' + (t('codexlens.noApiModels') || 'No API embedding models configured') + '</option>';
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
return options;
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
// Make functions globally accessible
|
|
2372
|
+
window.onEmbeddingBackendChange = onEmbeddingBackendChange;
|
|
2373
|
+
|
|
2374
|
+
/**
|
|
2375
|
+
* Initialize index from page with selected model
|
|
2376
|
+
*/
|
|
2377
|
+
function initCodexLensIndexFromPage(indexType) {
|
|
2378
|
+
var backendSelect = document.getElementById('pageBackendSelect');
|
|
2379
|
+
var modelSelect = document.getElementById('pageModelSelect');
|
|
2380
|
+
var concurrencyInput = document.getElementById('pageConcurrencyInput');
|
|
2381
|
+
var selectedBackend = backendSelect ? backendSelect.value : 'fastembed';
|
|
2382
|
+
var selectedModel = modelSelect ? modelSelect.value : 'code';
|
|
2383
|
+
var selectedConcurrency = concurrencyInput ? Math.max(1, parseInt(concurrencyInput.value, 10) || 4) : 4;
|
|
2384
|
+
|
|
2385
|
+
// For FTS-only index, model is not needed
|
|
2386
|
+
if (indexType === 'normal') {
|
|
2387
|
+
initCodexLensIndex(indexType);
|
|
2388
|
+
} else {
|
|
2389
|
+
// Pass concurrency only for litellm backend
|
|
2390
|
+
var maxWorkers = selectedBackend === 'litellm' ? selectedConcurrency : 1;
|
|
2391
|
+
initCodexLensIndex(indexType, selectedModel, selectedBackend, maxWorkers);
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
/**
|
|
2396
|
+
* Initialize CodexLens Manager page event handlers
|
|
2397
|
+
*/
|
|
2398
|
+
function initCodexLensManagerPageEvents(currentConfig) {
|
|
2399
|
+
var saveBtn = document.getElementById('saveIndexPathBtn');
|
|
2400
|
+
if (saveBtn) {
|
|
2401
|
+
saveBtn.onclick = async function() {
|
|
2402
|
+
var indexDirInput = document.getElementById('indexDirInput');
|
|
2403
|
+
var newIndexDir = indexDirInput ? indexDirInput.value.trim() : '';
|
|
2404
|
+
if (!newIndexDir) { showRefreshToast(t('codexlens.pathEmpty'), 'error'); return; }
|
|
2405
|
+
if (newIndexDir === currentConfig.index_dir) { showRefreshToast(t('codexlens.pathUnchanged'), 'info'); return; }
|
|
2406
|
+
saveBtn.disabled = true;
|
|
2407
|
+
saveBtn.innerHTML = '<span class="animate-pulse">' + t('common.saving') + '</span>';
|
|
2408
|
+
try {
|
|
2409
|
+
var response = await fetch('/api/codexlens/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ index_dir: newIndexDir }) });
|
|
2410
|
+
var result = await response.json();
|
|
2411
|
+
if (result.success) { showRefreshToast(t('codexlens.configSaved'), 'success'); renderCodexLensManager(); }
|
|
2412
|
+
else { showRefreshToast(t('common.saveFailed') + ': ' + result.error, 'error'); }
|
|
2413
|
+
} catch (err) { showRefreshToast(t('common.error') + ': ' + err.message, 'error'); }
|
|
2414
|
+
saveBtn.disabled = false;
|
|
2415
|
+
saveBtn.innerHTML = '<i data-lucide="save" class="w-3.5 h-3.5"></i> ' + t('codexlens.saveConfig');
|
|
2416
|
+
if (window.lucide) lucide.createIcons();
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
var runSearchBtn = document.getElementById('runSearchBtn');
|
|
2421
|
+
if (runSearchBtn) {
|
|
2422
|
+
runSearchBtn.onclick = async function() {
|
|
2423
|
+
var searchType = document.getElementById('searchTypeSelect').value;
|
|
2424
|
+
var searchMode = document.getElementById('searchModeSelect').value;
|
|
2425
|
+
var query = document.getElementById('searchQueryInput').value.trim();
|
|
2426
|
+
var resultsDiv = document.getElementById('searchResults');
|
|
2427
|
+
var resultCount = document.getElementById('searchResultCount');
|
|
2428
|
+
var resultContent = document.getElementById('searchResultContent');
|
|
2429
|
+
if (!query) { showRefreshToast(t('codexlens.enterQuery'), 'warning'); return; }
|
|
2430
|
+
runSearchBtn.disabled = true;
|
|
2431
|
+
runSearchBtn.innerHTML = '<span class="animate-pulse">' + t('codexlens.searching') + '</span>';
|
|
2432
|
+
resultsDiv.classList.add('hidden');
|
|
2433
|
+
try {
|
|
2434
|
+
var endpoint = '/api/codexlens/' + searchType;
|
|
2435
|
+
var params = new URLSearchParams({ query: query, limit: '20' });
|
|
2436
|
+
if (searchType === 'search' || searchType === 'search_files') { params.append('mode', searchMode); }
|
|
2437
|
+
var response = await fetch(endpoint + '?' + params.toString());
|
|
2438
|
+
var result = await response.json();
|
|
2439
|
+
if (result.success) {
|
|
2440
|
+
var results = result.results || result.files || [];
|
|
2441
|
+
resultCount.textContent = results.length + ' ' + t('codexlens.resultsCount');
|
|
2442
|
+
resultContent.textContent = JSON.stringify(results, null, 2);
|
|
2443
|
+
resultsDiv.classList.remove('hidden');
|
|
2444
|
+
} else {
|
|
2445
|
+
resultContent.textContent = t('common.error') + ': ' + (result.error || t('common.unknownError'));
|
|
2446
|
+
resultsDiv.classList.remove('hidden');
|
|
2447
|
+
}
|
|
2448
|
+
} catch (err) {
|
|
2449
|
+
resultContent.textContent = t('common.exception') + ': ' + err.message;
|
|
2450
|
+
resultsDiv.classList.remove('hidden');
|
|
2451
|
+
}
|
|
2452
|
+
runSearchBtn.disabled = false;
|
|
2453
|
+
runSearchBtn.innerHTML = '<i data-lucide="search" class="w-3.5 h-3.5"></i> ' + t('codexlens.runSearch');
|
|
2454
|
+
if (window.lucide) lucide.createIcons();
|
|
2455
|
+
};
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
var searchInput = document.getElementById('searchQueryInput');
|
|
2459
|
+
if (searchInput) { searchInput.onkeypress = function(e) { if (e.key === 'Enter' && runSearchBtn) { runSearchBtn.click(); } }; }
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
/**
|
|
2463
|
+
* Show index initialization modal
|
|
2464
|
+
*/
|
|
2465
|
+
function showIndexInitModal() {
|
|
2466
|
+
// Use initCodexLensIndex with default settings
|
|
2467
|
+
initCodexLensIndex('vector', 'code');
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
/**
|
|
2471
|
+
* Load index stats for the CodexLens Manager page
|
|
2472
|
+
*/
|
|
2473
|
+
async function loadIndexStatsForPage() {
|
|
2474
|
+
try {
|
|
2475
|
+
var response = await fetch('/api/codexlens/indexes');
|
|
2476
|
+
if (!response.ok) throw new Error('Failed to load index stats');
|
|
2477
|
+
var data = await response.json();
|
|
2478
|
+
renderIndexStatsForPage(data);
|
|
2479
|
+
} catch (err) {
|
|
2480
|
+
console.error('[CodexLens] Failed to load index stats:', err);
|
|
2481
|
+
var tbody = document.getElementById('indexTableBody');
|
|
2482
|
+
if (tbody) {
|
|
2483
|
+
tbody.innerHTML = '<tr><td colspan="5" class="py-4 text-center text-destructive text-sm">' + err.message + '</td></tr>';
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
/**
|
|
2489
|
+
* Render index stats in the CodexLens Manager page
|
|
2490
|
+
*/
|
|
2491
|
+
function renderIndexStatsForPage(data) {
|
|
2492
|
+
var summary = data.summary || {};
|
|
2493
|
+
var indexes = data.indexes || [];
|
|
2494
|
+
var indexDir = data.indexDir || '';
|
|
2495
|
+
|
|
2496
|
+
// Update summary stats
|
|
2497
|
+
var totalSizeEl = document.getElementById('indexTotalSize');
|
|
2498
|
+
var projectCountEl = document.getElementById('indexProjectCount');
|
|
2499
|
+
var totalSizeValEl = document.getElementById('indexTotalSizeVal');
|
|
2500
|
+
var vectorCountEl = document.getElementById('indexVectorCount');
|
|
2501
|
+
var ftsCountEl = document.getElementById('indexFtsCount');
|
|
2502
|
+
var indexDirEl = document.getElementById('indexDirDisplay');
|
|
2503
|
+
|
|
2504
|
+
if (totalSizeEl) totalSizeEl.textContent = summary.totalSizeFormatted || '0 B';
|
|
2505
|
+
if (projectCountEl) projectCountEl.textContent = summary.totalProjects || 0;
|
|
2506
|
+
if (totalSizeValEl) totalSizeValEl.textContent = summary.totalSizeFormatted || '0 B';
|
|
2507
|
+
if (vectorCountEl) vectorCountEl.textContent = summary.vectorIndexCount || 0;
|
|
2508
|
+
if (ftsCountEl) ftsCountEl.textContent = summary.normalIndexCount || 0;
|
|
2509
|
+
if (indexDirEl && indexDir) {
|
|
2510
|
+
indexDirEl.textContent = indexDir;
|
|
2511
|
+
indexDirEl.title = indexDir;
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
// Render table rows
|
|
2515
|
+
var tbody = document.getElementById('indexTableBody');
|
|
2516
|
+
if (!tbody) return;
|
|
2517
|
+
|
|
2518
|
+
if (indexes.length === 0) {
|
|
2519
|
+
tbody.innerHTML = '<tr><td colspan="5" class="py-4 text-center text-muted-foreground text-sm">' + (t('index.noIndexes') || 'No indexes yet') + '</td></tr>';
|
|
2520
|
+
return;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
var rows = '';
|
|
2524
|
+
indexes.forEach(function(idx) {
|
|
2525
|
+
var vectorBadge = idx.hasVectorIndex
|
|
2526
|
+
? '<span class="text-xs px-1.5 py-0.5 bg-primary/10 text-primary rounded">' + (t('index.vector') || 'Vector') + '</span>'
|
|
2527
|
+
: '';
|
|
2528
|
+
var normalBadge = idx.hasNormalIndex
|
|
2529
|
+
? '<span class="text-xs px-1.5 py-0.5 bg-muted text-muted-foreground rounded">' + (t('index.fts') || 'FTS') + '</span>'
|
|
2530
|
+
: '';
|
|
2531
|
+
|
|
2532
|
+
rows += '<tr class="border-t border-border hover:bg-muted/30 transition-colors">' +
|
|
2533
|
+
'<td class="py-2 px-2 text-foreground">' +
|
|
2534
|
+
'<span class="font-mono text-xs truncate max-w-[250px] inline-block" title="' + escapeHtml(idx.id) + '">' + escapeHtml(idx.id) + '</span>' +
|
|
2535
|
+
'</td>' +
|
|
2536
|
+
'<td class="py-2 px-2 text-right text-muted-foreground">' + (idx.sizeFormatted || '-') + '</td>' +
|
|
2537
|
+
'<td class="py-2 px-2 text-center"><div class="flex items-center justify-center gap-1">' + vectorBadge + normalBadge + '</div></td>' +
|
|
2538
|
+
'<td class="py-2 px-2 text-right text-muted-foreground">' + formatTimeAgoSimple(idx.lastModified) + '</td>' +
|
|
2539
|
+
'<td class="py-2 px-1 text-center">' +
|
|
2540
|
+
'<button onclick="cleanIndexProjectFromPage(\'' + escapeHtml(idx.id) + '\')" ' +
|
|
2541
|
+
'class="text-destructive/70 hover:text-destructive p-1 rounded hover:bg-destructive/10 transition-colors" ' +
|
|
2542
|
+
'title="' + (t('index.cleanProject') || 'Clean Index') + '">' +
|
|
2543
|
+
'<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>' +
|
|
2544
|
+
'</button>' +
|
|
2545
|
+
'</td>' +
|
|
2546
|
+
'</tr>';
|
|
2547
|
+
});
|
|
2548
|
+
|
|
2549
|
+
tbody.innerHTML = rows;
|
|
2550
|
+
if (window.lucide) lucide.createIcons();
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
/**
|
|
2554
|
+
* Simple time ago formatter
|
|
2555
|
+
*/
|
|
2556
|
+
function formatTimeAgoSimple(isoString) {
|
|
2557
|
+
if (!isoString) return t('common.never') || 'Never';
|
|
2558
|
+
var date = new Date(isoString);
|
|
2559
|
+
var now = new Date();
|
|
2560
|
+
var diffMs = now - date;
|
|
2561
|
+
var diffMins = Math.floor(diffMs / 60000);
|
|
2562
|
+
var diffHours = Math.floor(diffMins / 60);
|
|
2563
|
+
var diffDays = Math.floor(diffHours / 24);
|
|
2564
|
+
if (diffMins < 1) return t('common.justNow') || 'Just now';
|
|
2565
|
+
if (diffMins < 60) return diffMins + 'm ' + (t('common.ago') || 'ago');
|
|
2566
|
+
if (diffHours < 24) return diffHours + 'h ' + (t('common.ago') || 'ago');
|
|
2567
|
+
if (diffDays < 30) return diffDays + 'd ' + (t('common.ago') || 'ago');
|
|
2568
|
+
return date.toLocaleDateString();
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
/**
|
|
2572
|
+
* Clean a specific project's index from the page
|
|
2573
|
+
*/
|
|
2574
|
+
async function cleanIndexProjectFromPage(projectId) {
|
|
2575
|
+
if (!confirm((t('index.cleanProjectConfirm') || 'Clean index for') + ' ' + projectId + '?')) {
|
|
2576
|
+
return;
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
try {
|
|
2580
|
+
showRefreshToast(t('index.cleaning') || 'Cleaning index...', 'info');
|
|
2581
|
+
|
|
2582
|
+
var response = await fetch('/api/codexlens/clean', {
|
|
2583
|
+
method: 'POST',
|
|
2584
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2585
|
+
body: JSON.stringify({ projectId: projectId })
|
|
2586
|
+
});
|
|
2587
|
+
|
|
2588
|
+
var result = await response.json();
|
|
2589
|
+
|
|
2590
|
+
if (result.success) {
|
|
2591
|
+
showRefreshToast(t('index.cleanSuccess') || 'Index cleaned successfully', 'success');
|
|
2592
|
+
await loadIndexStatsForPage();
|
|
2593
|
+
} else {
|
|
2594
|
+
showRefreshToast((t('index.cleanFailed') || 'Clean failed') + ': ' + result.error, 'error');
|
|
2595
|
+
}
|
|
2596
|
+
} catch (err) {
|
|
2597
|
+
showRefreshToast((t('common.error') || 'Error') + ': ' + err.message, 'error');
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
/**
|
|
2602
|
+
* Clean all indexes from the page
|
|
2603
|
+
*/
|
|
2604
|
+
async function cleanAllIndexesFromPage() {
|
|
2605
|
+
if (!confirm(t('index.cleanAllConfirm') || 'Are you sure you want to clean ALL indexes? This cannot be undone.')) {
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
try {
|
|
2610
|
+
showRefreshToast(t('index.cleaning') || 'Cleaning indexes...', 'info');
|
|
2611
|
+
|
|
2612
|
+
var response = await fetch('/api/codexlens/clean', {
|
|
2613
|
+
method: 'POST',
|
|
2614
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2615
|
+
body: JSON.stringify({ all: true })
|
|
2616
|
+
});
|
|
2617
|
+
|
|
2618
|
+
var result = await response.json();
|
|
2619
|
+
|
|
2620
|
+
if (result.success) {
|
|
2621
|
+
showRefreshToast(t('index.cleanAllSuccess') || 'All indexes cleaned', 'success');
|
|
2622
|
+
await loadIndexStatsForPage();
|
|
2623
|
+
} else {
|
|
2624
|
+
showRefreshToast((t('index.cleanFailed') || 'Clean failed') + ': ' + result.error, 'error');
|
|
2625
|
+
}
|
|
2626
|
+
} catch (err) {
|
|
2627
|
+
showRefreshToast((t('common.error') || 'Error') + ': ' + err.message, 'error');
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
// ============================================================
|
|
2632
|
+
// MULTI-PROVIDER ROTATION CONFIGURATION
|
|
2633
|
+
// ============================================================
|
|
2634
|
+
|
|
2635
|
+
/**
|
|
2636
|
+
* Load and display rotation status in the page
|
|
2637
|
+
*/
|
|
2638
|
+
async function loadRotationStatus() {
|
|
2639
|
+
try {
|
|
2640
|
+
// Load from unified embedding-pool API (handles both new and legacy config)
|
|
2641
|
+
var response = await fetch('/api/litellm-api/embedding-pool');
|
|
2642
|
+
if (!response.ok) {
|
|
2643
|
+
console.warn('[CodexLens] Failed to load embedding pool config:', response.status);
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
var data = await response.json();
|
|
2647
|
+
window.embeddingPoolConfig = data.poolConfig;
|
|
2648
|
+
window.embeddingPoolAvailableModels = data.availableModels || [];
|
|
2649
|
+
|
|
2650
|
+
// Also get endpoint count
|
|
2651
|
+
var endpointsResponse = await fetch('/api/litellm-api/codexlens/rotation/endpoints');
|
|
2652
|
+
var endpointsData = endpointsResponse.ok ? await endpointsResponse.json() : { count: 0 };
|
|
2653
|
+
|
|
2654
|
+
updateRotationStatusDisplay(data.poolConfig, endpointsData.count);
|
|
2655
|
+
} catch (err) {
|
|
2656
|
+
console.error('[CodexLens] Error loading rotation status:', err);
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
/**
|
|
2661
|
+
* Update the rotation status display in the page
|
|
2662
|
+
* @param {Object} poolConfig - The embedding pool configuration
|
|
2663
|
+
* @param {number} endpointCount - Number of active endpoints
|
|
2664
|
+
*/
|
|
2665
|
+
function updateRotationStatusDisplay(poolConfig, endpointCount) {
|
|
2666
|
+
var badge = document.getElementById('rotationStatusBadge');
|
|
2667
|
+
var detailsEl = document.getElementById('rotationDetails');
|
|
2668
|
+
var modelNameEl = document.getElementById('rotationModelName');
|
|
2669
|
+
var countEl = document.getElementById('rotationEndpointCount');
|
|
2670
|
+
|
|
2671
|
+
if (!badge) return;
|
|
2672
|
+
|
|
2673
|
+
if (poolConfig && poolConfig.enabled) {
|
|
2674
|
+
badge.textContent = t('common.enabled');
|
|
2675
|
+
badge.className = 'text-xs px-2 py-0.5 rounded-full bg-success/10 text-success';
|
|
2676
|
+
|
|
2677
|
+
// Show details
|
|
2678
|
+
if (detailsEl) {
|
|
2679
|
+
detailsEl.classList.remove('hidden');
|
|
2680
|
+
if (modelNameEl) modelNameEl.textContent = poolConfig.targetModel || '';
|
|
2681
|
+
if (countEl) countEl.textContent = (endpointCount || 0) + ' ' + t('codexlens.totalEndpoints').toLowerCase();
|
|
2682
|
+
}
|
|
2683
|
+
} else {
|
|
2684
|
+
badge.textContent = t('common.disabled');
|
|
2685
|
+
badge.className = 'text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground';
|
|
2686
|
+
if (detailsEl) detailsEl.classList.add('hidden');
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
/**
|
|
2691
|
+
* Navigate to API Settings Embedding Pool tab
|
|
2692
|
+
*/
|
|
2693
|
+
function navigateToApiSettingsEmbeddingPool() {
|
|
2694
|
+
// Navigate to API Settings page with embedding-pool tab
|
|
2695
|
+
if (typeof switchView === 'function') {
|
|
2696
|
+
switchView('api-settings');
|
|
2697
|
+
// Give time for page to render, then switch to embedding-pool tab
|
|
2698
|
+
setTimeout(function() {
|
|
2699
|
+
if (typeof switchSidebarTab === 'function') {
|
|
2700
|
+
switchSidebarTab('embedding-pool');
|
|
2701
|
+
}
|
|
2702
|
+
}, 100);
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
/**
|
|
2707
|
+
* Show the rotation configuration modal
|
|
2708
|
+
*/
|
|
2709
|
+
async function showRotationConfigModal() {
|
|
2710
|
+
try {
|
|
2711
|
+
// Load current config if not already loaded
|
|
2712
|
+
if (!window.rotationConfig) {
|
|
2713
|
+
await loadRotationStatus();
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
var rotationConfig = window.rotationConfig || {
|
|
2717
|
+
enabled: false,
|
|
2718
|
+
strategy: 'round_robin',
|
|
2719
|
+
defaultCooldown: 60,
|
|
2720
|
+
targetModel: 'qwen3-embedding',
|
|
2721
|
+
providers: []
|
|
2722
|
+
};
|
|
2723
|
+
var availableProviders = window.availableRotationProviders || [];
|
|
2724
|
+
|
|
2725
|
+
var modalHtml = buildRotationConfigModal(rotationConfig, availableProviders);
|
|
2726
|
+
|
|
2727
|
+
var tempContainer = document.createElement('div');
|
|
2728
|
+
tempContainer.innerHTML = modalHtml;
|
|
2729
|
+
var modal = tempContainer.firstElementChild;
|
|
2730
|
+
document.body.appendChild(modal);
|
|
2731
|
+
|
|
2732
|
+
if (window.lucide) lucide.createIcons();
|
|
2733
|
+
initRotationConfigEvents(rotationConfig, availableProviders);
|
|
2734
|
+
} catch (err) {
|
|
2735
|
+
showRefreshToast(t('common.error') + ': ' + err.message, 'error');
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
/**
|
|
2740
|
+
* Build the rotation configuration modal HTML
|
|
2741
|
+
*/
|
|
2742
|
+
function buildRotationConfigModal(rotationConfig, availableProviders) {
|
|
2743
|
+
var isEnabled = rotationConfig.enabled || false;
|
|
2744
|
+
var strategy = rotationConfig.strategy || 'round_robin';
|
|
2745
|
+
var cooldown = rotationConfig.defaultCooldown || 60;
|
|
2746
|
+
var targetModel = rotationConfig.targetModel || 'qwen3-embedding';
|
|
2747
|
+
var configuredProviders = rotationConfig.providers || [];
|
|
2748
|
+
|
|
2749
|
+
// Build provider list HTML
|
|
2750
|
+
var providerListHtml = '';
|
|
2751
|
+
if (availableProviders.length === 0) {
|
|
2752
|
+
providerListHtml = '<div class="text-sm text-muted-foreground py-4 text-center">' + t('codexlens.noRotationProviders') + '</div>';
|
|
2753
|
+
} else {
|
|
2754
|
+
availableProviders.forEach(function(provider, index) {
|
|
2755
|
+
// Find if this provider is already configured
|
|
2756
|
+
var configured = configuredProviders.find(function(p) { return p.providerId === provider.providerId; });
|
|
2757
|
+
var isProviderEnabled = configured ? configured.enabled : false;
|
|
2758
|
+
var weight = configured ? configured.weight : 1;
|
|
2759
|
+
var maxConcurrent = configured ? configured.maxConcurrentPerKey : 4;
|
|
2760
|
+
var useAllKeys = configured ? configured.useAllKeys : true;
|
|
2761
|
+
|
|
2762
|
+
// Get model options
|
|
2763
|
+
var modelOptions = provider.embeddingModels.map(function(m) {
|
|
2764
|
+
var selected = configured && configured.modelId === m.modelId ? 'selected' : '';
|
|
2765
|
+
return '<option value="' + m.modelId + '" ' + selected + '>' + m.modelName + ' (' + m.dimensions + 'd)</option>';
|
|
2766
|
+
}).join('');
|
|
2767
|
+
|
|
2768
|
+
// Get key count
|
|
2769
|
+
var keyCount = provider.apiKeys.filter(function(k) { return k.enabled; }).length;
|
|
2770
|
+
|
|
2771
|
+
providerListHtml +=
|
|
2772
|
+
'<div class="border border-border rounded-lg p-3 ' + (isProviderEnabled ? 'bg-success/5 border-success/30' : 'bg-muted/30') + '" data-provider-id="' + provider.providerId + '">' +
|
|
2773
|
+
'<div class="flex items-center justify-between mb-2">' +
|
|
2774
|
+
'<div class="flex items-center gap-2">' +
|
|
2775
|
+
'<input type="checkbox" id="rotationProvider_' + index + '" ' + (isProviderEnabled ? 'checked' : '') +
|
|
2776
|
+
' class="rotation-provider-toggle" data-provider-id="' + provider.providerId + '" />' +
|
|
2777
|
+
'<label for="rotationProvider_' + index + '" class="font-medium text-sm">' + provider.providerName + '</label>' +
|
|
2778
|
+
'<span class="text-xs px-1.5 py-0.5 bg-muted rounded text-muted-foreground">' + keyCount + ' keys</span>' +
|
|
2779
|
+
'</div>' +
|
|
2780
|
+
'</div>' +
|
|
2781
|
+
'<div class="grid grid-cols-2 gap-2 text-xs">' +
|
|
2782
|
+
'<div>' +
|
|
2783
|
+
'<label class="text-muted-foreground">Model</label>' +
|
|
2784
|
+
'<select class="w-full px-2 py-1 border border-border rounded bg-background text-sm rotation-model-select" data-provider-id="' + provider.providerId + '">' +
|
|
2785
|
+
modelOptions +
|
|
2786
|
+
'</select>' +
|
|
2787
|
+
'</div>' +
|
|
2788
|
+
'<div>' +
|
|
2789
|
+
'<label class="text-muted-foreground">' + t('codexlens.providerWeight') + '</label>' +
|
|
2790
|
+
'<input type="number" min="0.1" max="10" step="0.1" value="' + weight + '" ' +
|
|
2791
|
+
'class="w-full px-2 py-1 border border-border rounded bg-background text-sm rotation-weight-input" data-provider-id="' + provider.providerId + '" />' +
|
|
2792
|
+
'</div>' +
|
|
2793
|
+
'<div>' +
|
|
2794
|
+
'<label class="text-muted-foreground">' + t('codexlens.maxConcurrentPerKey') + '</label>' +
|
|
2795
|
+
'<input type="number" min="1" max="16" value="' + maxConcurrent + '" ' +
|
|
2796
|
+
'class="w-full px-2 py-1 border border-border rounded bg-background text-sm rotation-concurrent-input" data-provider-id="' + provider.providerId + '" />' +
|
|
2797
|
+
'</div>' +
|
|
2798
|
+
'<div class="flex items-center gap-1">' +
|
|
2799
|
+
'<input type="checkbox" id="useAllKeys_' + index + '" ' + (useAllKeys ? 'checked' : '') +
|
|
2800
|
+
' class="rotation-use-all-keys" data-provider-id="' + provider.providerId + '" />' +
|
|
2801
|
+
'<label for="useAllKeys_' + index + '" class="text-muted-foreground">' + t('codexlens.useAllKeys') + '</label>' +
|
|
2802
|
+
'</div>' +
|
|
2803
|
+
'</div>' +
|
|
2804
|
+
'</div>';
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2808
|
+
return '<div class="modal-backdrop" id="rotationConfigModal">' +
|
|
2809
|
+
'<div class="modal-container max-w-2xl">' +
|
|
2810
|
+
'<div class="modal-header">' +
|
|
2811
|
+
'<div class="flex items-center gap-3">' +
|
|
2812
|
+
'<div class="modal-icon">' +
|
|
2813
|
+
'<i data-lucide="rotate-cw" class="w-5 h-5"></i>' +
|
|
2814
|
+
'</div>' +
|
|
2815
|
+
'<div>' +
|
|
2816
|
+
'<h2 class="text-lg font-bold">' + t('codexlens.rotation') + '</h2>' +
|
|
2817
|
+
'<p class="text-xs text-muted-foreground">' + t('codexlens.rotationDesc') + '</p>' +
|
|
2818
|
+
'</div>' +
|
|
2819
|
+
'</div>' +
|
|
2820
|
+
'<button onclick="closeRotationModal()" class="text-muted-foreground hover:text-foreground">' +
|
|
2821
|
+
'<i data-lucide="x" class="w-5 h-5"></i>' +
|
|
2822
|
+
'</button>' +
|
|
2823
|
+
'</div>' +
|
|
2824
|
+
'<div class="modal-body space-y-4">' +
|
|
2825
|
+
// Enable toggle
|
|
2826
|
+
'<div class="flex items-center justify-between p-3 bg-muted/30 rounded-lg">' +
|
|
2827
|
+
'<div class="flex items-center gap-2">' +
|
|
2828
|
+
'<i data-lucide="power" class="w-4 h-4 text-primary"></i>' +
|
|
2829
|
+
'<span class="font-medium">' + t('codexlens.rotationEnabled') + '</span>' +
|
|
2830
|
+
'</div>' +
|
|
2831
|
+
'<label class="relative inline-flex items-center cursor-pointer">' +
|
|
2832
|
+
'<input type="checkbox" id="rotationEnabledToggle" ' + (isEnabled ? 'checked' : '') + ' class="sr-only peer" />' +
|
|
2833
|
+
'<div class="w-11 h-6 bg-muted peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[\'\'] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary"></div>' +
|
|
2834
|
+
'</label>' +
|
|
2835
|
+
'</div>' +
|
|
2836
|
+
// Strategy and settings
|
|
2837
|
+
'<div class="grid grid-cols-2 gap-4">' +
|
|
2838
|
+
'<div>' +
|
|
2839
|
+
'<label class="block text-sm font-medium mb-1.5">' + t('codexlens.rotationStrategy') + '</label>' +
|
|
2840
|
+
'<select id="rotationStrategy" class="w-full px-3 py-2 border border-border rounded-lg bg-background text-sm">' +
|
|
2841
|
+
'<option value="round_robin" ' + (strategy === 'round_robin' ? 'selected' : '') + '>' + t('codexlens.strategyRoundRobin') + '</option>' +
|
|
2842
|
+
'<option value="latency_aware" ' + (strategy === 'latency_aware' ? 'selected' : '') + '>' + t('codexlens.strategyLatencyAware') + '</option>' +
|
|
2843
|
+
'<option value="weighted_random" ' + (strategy === 'weighted_random' ? 'selected' : '') + '>' + t('codexlens.strategyWeightedRandom') + '</option>' +
|
|
2844
|
+
'</select>' +
|
|
2845
|
+
'</div>' +
|
|
2846
|
+
'<div>' +
|
|
2847
|
+
'<label class="block text-sm font-medium mb-1.5">' + t('codexlens.cooldownSeconds') + '</label>' +
|
|
2848
|
+
'<input type="number" id="rotationCooldown" min="1" max="300" value="' + cooldown + '" ' +
|
|
2849
|
+
'class="w-full px-3 py-2 border border-border rounded-lg bg-background text-sm" />' +
|
|
2850
|
+
'<p class="text-xs text-muted-foreground mt-1">' + t('codexlens.cooldownHint') + '</p>' +
|
|
2851
|
+
'</div>' +
|
|
2852
|
+
'</div>' +
|
|
2853
|
+
// Target model
|
|
2854
|
+
'<div>' +
|
|
2855
|
+
'<label class="block text-sm font-medium mb-1.5">' + t('codexlens.targetModel') + '</label>' +
|
|
2856
|
+
'<input type="text" id="rotationTargetModel" value="' + targetModel + '" ' +
|
|
2857
|
+
'class="w-full px-3 py-2 border border-border rounded-lg bg-background text-sm" placeholder="qwen3-embedding" />' +
|
|
2858
|
+
'<p class="text-xs text-muted-foreground mt-1">' + t('codexlens.targetModelHint') + '</p>' +
|
|
2859
|
+
'</div>' +
|
|
2860
|
+
// Provider list
|
|
2861
|
+
'<div>' +
|
|
2862
|
+
'<label class="block text-sm font-medium mb-1.5">' + t('codexlens.rotationProviders') + '</label>' +
|
|
2863
|
+
'<div class="space-y-2 max-h-64 overflow-y-auto" id="rotationProviderList">' +
|
|
2864
|
+
providerListHtml +
|
|
2865
|
+
'</div>' +
|
|
2866
|
+
'</div>' +
|
|
2867
|
+
'</div>' +
|
|
2868
|
+
'<div class="modal-footer">' +
|
|
2869
|
+
'<button onclick="closeRotationModal()" class="btn btn-outline">' + t('common.cancel') + '</button>' +
|
|
2870
|
+
'<button onclick="saveRotationConfig()" class="btn btn-primary">' +
|
|
2871
|
+
'<i data-lucide="save" class="w-4 h-4"></i> ' + t('common.save') +
|
|
2872
|
+
'</button>' +
|
|
2873
|
+
'</div>' +
|
|
2874
|
+
'</div>' +
|
|
2875
|
+
'</div>';
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2878
|
+
/**
|
|
2879
|
+
* Initialize rotation config modal events
|
|
2880
|
+
*/
|
|
2881
|
+
function initRotationConfigEvents(rotationConfig, availableProviders) {
|
|
2882
|
+
// Store in window for save function
|
|
2883
|
+
window._rotationAvailableProviders = availableProviders;
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
/**
|
|
2887
|
+
* Close the rotation config modal
|
|
2888
|
+
*/
|
|
2889
|
+
function closeRotationModal() {
|
|
2890
|
+
var modal = document.getElementById('rotationConfigModal');
|
|
2891
|
+
if (modal) modal.remove();
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
/**
|
|
2895
|
+
* Save the rotation configuration
|
|
2896
|
+
*/
|
|
2897
|
+
async function saveRotationConfig() {
|
|
2898
|
+
try {
|
|
2899
|
+
var enabledToggle = document.getElementById('rotationEnabledToggle');
|
|
2900
|
+
var strategySelect = document.getElementById('rotationStrategy');
|
|
2901
|
+
var cooldownInput = document.getElementById('rotationCooldown');
|
|
2902
|
+
var targetModelInput = document.getElementById('rotationTargetModel');
|
|
2903
|
+
|
|
2904
|
+
var enabled = enabledToggle ? enabledToggle.checked : false;
|
|
2905
|
+
var strategy = strategySelect ? strategySelect.value : 'round_robin';
|
|
2906
|
+
var cooldown = cooldownInput ? parseInt(cooldownInput.value, 10) : 60;
|
|
2907
|
+
var targetModel = targetModelInput ? targetModelInput.value.trim() : 'qwen3-embedding';
|
|
2908
|
+
|
|
2909
|
+
// Collect provider configurations
|
|
2910
|
+
var providers = [];
|
|
2911
|
+
var providerToggles = document.querySelectorAll('.rotation-provider-toggle');
|
|
2912
|
+
providerToggles.forEach(function(toggle) {
|
|
2913
|
+
var providerId = toggle.getAttribute('data-provider-id');
|
|
2914
|
+
var isEnabled = toggle.checked;
|
|
2915
|
+
|
|
2916
|
+
var modelSelect = document.querySelector('.rotation-model-select[data-provider-id="' + providerId + '"]');
|
|
2917
|
+
var weightInput = document.querySelector('.rotation-weight-input[data-provider-id="' + providerId + '"]');
|
|
2918
|
+
var concurrentInput = document.querySelector('.rotation-concurrent-input[data-provider-id="' + providerId + '"]');
|
|
2919
|
+
var useAllKeysToggle = document.querySelector('.rotation-use-all-keys[data-provider-id="' + providerId + '"]');
|
|
2920
|
+
|
|
2921
|
+
providers.push({
|
|
2922
|
+
providerId: providerId,
|
|
2923
|
+
modelId: modelSelect ? modelSelect.value : '',
|
|
2924
|
+
weight: weightInput ? parseFloat(weightInput.value) || 1 : 1,
|
|
2925
|
+
maxConcurrentPerKey: concurrentInput ? parseInt(concurrentInput.value, 10) || 4 : 4,
|
|
2926
|
+
useAllKeys: useAllKeysToggle ? useAllKeysToggle.checked : true,
|
|
2927
|
+
enabled: isEnabled
|
|
2928
|
+
});
|
|
2929
|
+
});
|
|
2930
|
+
|
|
2931
|
+
var rotationConfig = {
|
|
2932
|
+
enabled: enabled,
|
|
2933
|
+
strategy: strategy,
|
|
2934
|
+
defaultCooldown: cooldown,
|
|
2935
|
+
targetModel: targetModel,
|
|
2936
|
+
providers: providers
|
|
2937
|
+
};
|
|
2938
|
+
|
|
2939
|
+
var response = await fetch('/api/litellm-api/codexlens/rotation', {
|
|
2940
|
+
method: 'PUT',
|
|
2941
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2942
|
+
body: JSON.stringify(rotationConfig)
|
|
2943
|
+
});
|
|
2944
|
+
|
|
2945
|
+
var result = await response.json();
|
|
2946
|
+
|
|
2947
|
+
if (result.success) {
|
|
2948
|
+
// Show sync result in toast
|
|
2949
|
+
var syncMsg = '';
|
|
2950
|
+
if (result.syncResult) {
|
|
2951
|
+
if (result.syncResult.success) {
|
|
2952
|
+
syncMsg = ' (' + result.syncResult.endpointCount + ' ' + t('codexlens.endpointsSynced') + ')';
|
|
2953
|
+
} else {
|
|
2954
|
+
syncMsg = ' (' + t('codexlens.syncFailed') + ': ' + result.syncResult.message + ')';
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
showRefreshToast(t('codexlens.rotationSaved') + syncMsg, 'success');
|
|
2958
|
+
window.rotationConfig = rotationConfig;
|
|
2959
|
+
updateRotationStatusDisplay(rotationConfig);
|
|
2960
|
+
closeRotationModal();
|
|
2961
|
+
} else {
|
|
2962
|
+
showRefreshToast(t('common.saveFailed') + ': ' + result.error, 'error');
|
|
2963
|
+
}
|
|
2964
|
+
} catch (err) {
|
|
2965
|
+
showRefreshToast(t('common.error') + ': ' + err.message, 'error');
|
|
2966
|
+
}
|
|
2967
|
+
}
|