claude-code-workflow 6.2.6 → 6.2.7
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/ccw/src/templates/dashboard-js/components/cli-status.js +35 -2
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +45 -6
- 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/commands.py +23 -14
- package/codex-lens/src/codexlens/cli/embedding_manager.py +15 -0
- package/codex-lens/src/codexlens/cli/model_manager.py +337 -311
- 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/chain_search.py +14 -1
- package/codex-lens/src/codexlens/search/hybrid_search.py +25 -12
- package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/vector_store.py +97 -0
- package/package.json +1 -1
|
@@ -98,15 +98,17 @@ async function loadCodexLensStatus() {
|
|
|
98
98
|
}
|
|
99
99
|
window.cliToolsStatus.codexlens = {
|
|
100
100
|
installed: data.ready || false,
|
|
101
|
-
version: data.version || null
|
|
101
|
+
version: data.version || null,
|
|
102
|
+
installedModels: [] // Will be populated by loadSemanticStatus
|
|
102
103
|
};
|
|
103
104
|
|
|
104
105
|
// Update CodexLens badge
|
|
105
106
|
updateCodexLensBadge();
|
|
106
107
|
|
|
107
|
-
// If CodexLens is ready, also check semantic status
|
|
108
|
+
// If CodexLens is ready, also check semantic status and models
|
|
108
109
|
if (data.ready) {
|
|
109
110
|
await loadSemanticStatus();
|
|
111
|
+
await loadInstalledModels();
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
return data;
|
|
@@ -132,6 +134,37 @@ async function loadSemanticStatus() {
|
|
|
132
134
|
}
|
|
133
135
|
}
|
|
134
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Load installed embedding models
|
|
139
|
+
*/
|
|
140
|
+
async function loadInstalledModels() {
|
|
141
|
+
try {
|
|
142
|
+
const response = await fetch('/api/codexlens/models');
|
|
143
|
+
if (!response.ok) throw new Error('Failed to load models');
|
|
144
|
+
const data = await response.json();
|
|
145
|
+
|
|
146
|
+
if (data.success && data.result && data.result.models) {
|
|
147
|
+
// Filter to only installed models
|
|
148
|
+
const installedModels = data.result.models
|
|
149
|
+
.filter(m => m.installed)
|
|
150
|
+
.map(m => m.profile);
|
|
151
|
+
|
|
152
|
+
// Update window.cliToolsStatus
|
|
153
|
+
if (window.cliToolsStatus && window.cliToolsStatus.codexlens) {
|
|
154
|
+
window.cliToolsStatus.codexlens.installedModels = installedModels;
|
|
155
|
+
window.cliToolsStatus.codexlens.allModels = data.result.models;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log('[CLI Status] Installed models:', installedModels);
|
|
159
|
+
return installedModels;
|
|
160
|
+
}
|
|
161
|
+
return [];
|
|
162
|
+
} catch (err) {
|
|
163
|
+
console.error('Failed to load installed models:', err);
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
135
168
|
// ========== Badge Update ==========
|
|
136
169
|
function updateCliBadge() {
|
|
137
170
|
const badge = document.getElementById('badgeCliTools');
|
|
@@ -349,6 +349,50 @@ function getSelectedModel() {
|
|
|
349
349
|
return select ? select.value : 'code';
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
+
/**
|
|
353
|
+
* Build model select options HTML, showing only installed models
|
|
354
|
+
* @returns {string} HTML string for select options
|
|
355
|
+
*/
|
|
356
|
+
function buildModelSelectOptions() {
|
|
357
|
+
var installedModels = window.cliToolsStatus?.codexlens?.installedModels || [];
|
|
358
|
+
var allModels = window.cliToolsStatus?.codexlens?.allModels || [];
|
|
359
|
+
|
|
360
|
+
// Model display configuration
|
|
361
|
+
var modelConfig = {
|
|
362
|
+
'code': { label: t('index.modelCode') || 'Code (768d)', star: true },
|
|
363
|
+
'base': { label: t('index.modelBase') || 'Base (768d)', star: false },
|
|
364
|
+
'fast': { label: t('index.modelFast') || 'Fast (384d)', star: false },
|
|
365
|
+
'minilm': { label: t('index.modelMinilm') || 'MiniLM (384d)', star: false },
|
|
366
|
+
'multilingual': { label: t('index.modelMultilingual') || 'Multilingual (1024d)', warn: true },
|
|
367
|
+
'balanced': { label: t('index.modelBalanced') || 'Balanced (1024d)', warn: true }
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// If no models installed, show placeholder
|
|
371
|
+
if (installedModels.length === 0) {
|
|
372
|
+
return '<option value="" disabled selected>' + (t('index.noModelsInstalled') || 'No models installed') + '</option>';
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Build options for installed models only
|
|
376
|
+
var options = '';
|
|
377
|
+
var firstInstalled = null;
|
|
378
|
+
|
|
379
|
+
// Preferred order: code, fast, minilm, base, multilingual, balanced
|
|
380
|
+
var preferredOrder = ['code', 'fast', 'minilm', 'base', 'multilingual', 'balanced'];
|
|
381
|
+
|
|
382
|
+
preferredOrder.forEach(function(profile) {
|
|
383
|
+
if (installedModels.includes(profile) && modelConfig[profile]) {
|
|
384
|
+
var config = modelConfig[profile];
|
|
385
|
+
var style = config.warn ? ' style="color: var(--muted-foreground)"' : '';
|
|
386
|
+
var suffix = config.star ? ' ⭐' : (config.warn ? ' ⚠️' : '');
|
|
387
|
+
var selected = !firstInstalled ? ' selected' : '';
|
|
388
|
+
if (!firstInstalled) firstInstalled = profile;
|
|
389
|
+
options += '<option value="' + profile + '"' + style + selected + '>' + config.label + suffix + '</option>';
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
return options;
|
|
394
|
+
}
|
|
395
|
+
|
|
352
396
|
// ========== Tools Section (Left Column) ==========
|
|
353
397
|
function renderToolsSection() {
|
|
354
398
|
var container = document.getElementById('tools-section');
|
|
@@ -404,12 +448,7 @@ function renderToolsSection() {
|
|
|
404
448
|
(codexLensStatus.ready
|
|
405
449
|
? '<span class="tool-status-text success"><i data-lucide="check-circle" class="w-3.5 h-3.5"></i> v' + (codexLensStatus.version || 'installed') + '</span>' +
|
|
406
450
|
'<select id="codexlensModelSelect" class="btn-sm bg-muted border border-border rounded text-xs" onclick="event.stopPropagation()" title="' + (t('index.selectModel') || 'Select embedding model') + '">' +
|
|
407
|
-
|
|
408
|
-
'<option value="base">' + (t('index.modelBase') || 'Base (768d)') + '</option>' +
|
|
409
|
-
'<option value="fast">' + (t('index.modelFast') || 'Fast (384d)') + '</option>' +
|
|
410
|
-
'<option value="minilm">' + (t('index.modelMinilm') || 'MiniLM (384d)') + '</option>' +
|
|
411
|
-
'<option value="multilingual" style="color: var(--muted-foreground)">' + (t('index.modelMultilingual') || 'Multilingual (1024d)') + ' ⚠️</option>' +
|
|
412
|
-
'<option value="balanced" style="color: var(--muted-foreground)">' + (t('index.modelBalanced') || 'Balanced (1024d)') + ' ⚠️</option>' +
|
|
451
|
+
buildModelSelectOptions() +
|
|
413
452
|
'</select>' +
|
|
414
453
|
'<button class="btn-sm btn-primary" onclick="event.stopPropagation(); initCodexLensIndex(\'full\', getSelectedModel())" title="' + (t('index.fullDesc') || 'FTS + Semantic search (recommended)') + '"><i data-lucide="layers" class="w-3 h-3"></i> ' + (t('index.fullIndex') || '全部索引') + '</button>' +
|
|
415
454
|
'<button class="btn-sm btn-outline" onclick="event.stopPropagation(); initCodexLensIndex(\'vector\', getSelectedModel())" title="' + (t('index.vectorDesc') || 'Semantic search with embeddings') + '"><i data-lucide="sparkles" class="w-3 h-3"></i> ' + (t('index.vectorIndex') || '向量索引') + '</button>' +
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -35,8 +35,17 @@ from .output import (
|
|
|
35
35
|
app = typer.Typer(help="CodexLens CLI — local code indexing and search.")
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
def _configure_logging(verbose: bool) -> None:
|
|
39
|
-
|
|
38
|
+
def _configure_logging(verbose: bool, json_mode: bool = False) -> None:
|
|
39
|
+
"""Configure logging level.
|
|
40
|
+
|
|
41
|
+
In JSON mode, suppress INFO logs to keep stderr clean for error parsing.
|
|
42
|
+
Only WARNING and above are shown to avoid mixing logs with JSON output.
|
|
43
|
+
"""
|
|
44
|
+
if json_mode and not verbose:
|
|
45
|
+
# In JSON mode, suppress INFO logs to keep stderr clean
|
|
46
|
+
level = logging.WARNING
|
|
47
|
+
else:
|
|
48
|
+
level = logging.DEBUG if verbose else logging.INFO
|
|
40
49
|
logging.basicConfig(level=level, format="%(levelname)s %(message)s")
|
|
41
50
|
|
|
42
51
|
|
|
@@ -95,7 +104,7 @@ def init(
|
|
|
95
104
|
If semantic search dependencies are installed, automatically generates embeddings
|
|
96
105
|
after indexing completes. Use --no-embeddings to skip this step.
|
|
97
106
|
"""
|
|
98
|
-
_configure_logging(verbose)
|
|
107
|
+
_configure_logging(verbose, json_mode)
|
|
99
108
|
config = Config()
|
|
100
109
|
languages = _parse_languages(language)
|
|
101
110
|
base_path = path.expanduser().resolve()
|
|
@@ -314,7 +323,7 @@ def search(
|
|
|
314
323
|
# Force hybrid mode
|
|
315
324
|
codexlens search "authentication" --mode hybrid
|
|
316
325
|
"""
|
|
317
|
-
_configure_logging(verbose)
|
|
326
|
+
_configure_logging(verbose, json_mode)
|
|
318
327
|
search_path = path.expanduser().resolve()
|
|
319
328
|
|
|
320
329
|
# Validate mode
|
|
@@ -487,7 +496,7 @@ def symbol(
|
|
|
487
496
|
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable debug logging."),
|
|
488
497
|
) -> None:
|
|
489
498
|
"""Look up symbols by name and optional kind."""
|
|
490
|
-
_configure_logging(verbose)
|
|
499
|
+
_configure_logging(verbose, json_mode)
|
|
491
500
|
search_path = path.expanduser().resolve()
|
|
492
501
|
|
|
493
502
|
registry: RegistryStore | None = None
|
|
@@ -538,7 +547,7 @@ def inspect(
|
|
|
538
547
|
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable debug logging."),
|
|
539
548
|
) -> None:
|
|
540
549
|
"""Analyze a single file and display symbols."""
|
|
541
|
-
_configure_logging(verbose)
|
|
550
|
+
_configure_logging(verbose, json_mode)
|
|
542
551
|
config = Config()
|
|
543
552
|
factory = ParserFactory(config)
|
|
544
553
|
|
|
@@ -588,7 +597,7 @@ def status(
|
|
|
588
597
|
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable debug logging."),
|
|
589
598
|
) -> None:
|
|
590
599
|
"""Show index status and configuration."""
|
|
591
|
-
_configure_logging(verbose)
|
|
600
|
+
_configure_logging(verbose, json_mode)
|
|
592
601
|
|
|
593
602
|
registry: RegistryStore | None = None
|
|
594
603
|
try:
|
|
@@ -648,7 +657,7 @@ def status(
|
|
|
648
657
|
# Embedding manager not available
|
|
649
658
|
pass
|
|
650
659
|
except Exception as e:
|
|
651
|
-
|
|
660
|
+
logging.debug(f"Failed to get embeddings status: {e}")
|
|
652
661
|
|
|
653
662
|
stats = {
|
|
654
663
|
"index_root": str(index_root),
|
|
@@ -737,7 +746,7 @@ def projects(
|
|
|
737
746
|
- show <path>: Show details for a specific project
|
|
738
747
|
- remove <path>: Remove a project from the registry
|
|
739
748
|
"""
|
|
740
|
-
_configure_logging(verbose)
|
|
749
|
+
_configure_logging(verbose, json_mode)
|
|
741
750
|
|
|
742
751
|
registry: RegistryStore | None = None
|
|
743
752
|
try:
|
|
@@ -892,7 +901,7 @@ def config(
|
|
|
892
901
|
Config keys:
|
|
893
902
|
- index_dir: Directory to store indexes (default: ~/.codexlens/indexes)
|
|
894
903
|
"""
|
|
895
|
-
_configure_logging(verbose)
|
|
904
|
+
_configure_logging(verbose, json_mode)
|
|
896
905
|
|
|
897
906
|
config_file = Path.home() / ".codexlens" / "config.json"
|
|
898
907
|
|
|
@@ -1057,7 +1066,7 @@ def migrate(
|
|
|
1057
1066
|
This is a safe operation that preserves all existing data.
|
|
1058
1067
|
Progress is shown during migration.
|
|
1059
1068
|
"""
|
|
1060
|
-
_configure_logging(verbose)
|
|
1069
|
+
_configure_logging(verbose, json_mode)
|
|
1061
1070
|
base_path = path.expanduser().resolve()
|
|
1062
1071
|
|
|
1063
1072
|
registry: RegistryStore | None = None
|
|
@@ -1183,7 +1192,7 @@ def clean(
|
|
|
1183
1192
|
With path, removes that project's indexes.
|
|
1184
1193
|
With --all, removes all indexes (use with caution).
|
|
1185
1194
|
"""
|
|
1186
|
-
_configure_logging(verbose)
|
|
1195
|
+
_configure_logging(verbose, json_mode)
|
|
1187
1196
|
|
|
1188
1197
|
try:
|
|
1189
1198
|
mapper = PathMapper()
|
|
@@ -1329,7 +1338,7 @@ def semantic_list(
|
|
|
1329
1338
|
Shows files that have LLM-generated summaries and keywords.
|
|
1330
1339
|
Results are aggregated from all index databases in the project.
|
|
1331
1340
|
"""
|
|
1332
|
-
_configure_logging(verbose)
|
|
1341
|
+
_configure_logging(verbose, json_mode)
|
|
1333
1342
|
base_path = path.expanduser().resolve()
|
|
1334
1343
|
|
|
1335
1344
|
registry: Optional[RegistryStore] = None
|
|
@@ -1798,7 +1807,7 @@ def embeddings_generate(
|
|
|
1798
1807
|
codexlens embeddings-generate ~/.codexlens/indexes/project/_index.db # Specific index
|
|
1799
1808
|
codexlens embeddings-generate ~/projects/my-app --model fast --force # Regenerate with fast model
|
|
1800
1809
|
"""
|
|
1801
|
-
_configure_logging(verbose)
|
|
1810
|
+
_configure_logging(verbose, json_mode)
|
|
1802
1811
|
|
|
1803
1812
|
from codexlens.cli.embedding_manager import generate_embeddings, generate_embeddings_recursive
|
|
1804
1813
|
|
|
@@ -279,6 +279,21 @@ def generate_embeddings(
|
|
|
279
279
|
|
|
280
280
|
try:
|
|
281
281
|
with VectorStore(index_path) as vector_store:
|
|
282
|
+
# Check model compatibility with existing embeddings
|
|
283
|
+
if not force:
|
|
284
|
+
is_compatible, warning = vector_store.check_model_compatibility(
|
|
285
|
+
model_profile, embedder.model_name, embedder.embedding_dim
|
|
286
|
+
)
|
|
287
|
+
if not is_compatible:
|
|
288
|
+
return {
|
|
289
|
+
"success": False,
|
|
290
|
+
"error": warning,
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
# Set/update model configuration for this index
|
|
294
|
+
vector_store.set_model_config(
|
|
295
|
+
model_profile, embedder.model_name, embedder.embedding_dim
|
|
296
|
+
)
|
|
282
297
|
# Use bulk insert mode for efficient batch ANN index building
|
|
283
298
|
# This defers ANN updates until end_bulk_insert() is called
|
|
284
299
|
with vector_store.bulk_insert():
|
|
@@ -1,311 +1,337 @@
|
|
|
1
|
-
"""Model Manager - Manage fastembed models for semantic search."""
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import os
|
|
5
|
-
import shutil
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import Dict, List, Optional
|
|
8
|
-
|
|
9
|
-
try:
|
|
10
|
-
from fastembed import TextEmbedding
|
|
11
|
-
FASTEMBED_AVAILABLE = True
|
|
12
|
-
except ImportError:
|
|
13
|
-
FASTEMBED_AVAILABLE = False
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# Model profiles with metadata
|
|
17
|
-
# Note: 768d is max recommended dimension for optimal performance/quality balance
|
|
18
|
-
# 1024d models are available but not recommended due to higher resource usage
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
"
|
|
166
|
-
"
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
"
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
"
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
"
|
|
304
|
-
"
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
1
|
+
"""Model Manager - Manage fastembed models for semantic search."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from fastembed import TextEmbedding
|
|
11
|
+
FASTEMBED_AVAILABLE = True
|
|
12
|
+
except ImportError:
|
|
13
|
+
FASTEMBED_AVAILABLE = False
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Model profiles with metadata
|
|
17
|
+
# Note: 768d is max recommended dimension for optimal performance/quality balance
|
|
18
|
+
# 1024d models are available but not recommended due to higher resource usage
|
|
19
|
+
# cache_name: The actual Hugging Face repo name used by fastembed for ONNX caching
|
|
20
|
+
MODEL_PROFILES = {
|
|
21
|
+
"fast": {
|
|
22
|
+
"model_name": "BAAI/bge-small-en-v1.5",
|
|
23
|
+
"cache_name": "qdrant/bge-small-en-v1.5-onnx-q", # fastembed uses ONNX version
|
|
24
|
+
"dimensions": 384,
|
|
25
|
+
"size_mb": 80,
|
|
26
|
+
"description": "Fast, lightweight, English-optimized",
|
|
27
|
+
"use_case": "Quick prototyping, resource-constrained environments",
|
|
28
|
+
"recommended": True,
|
|
29
|
+
},
|
|
30
|
+
"base": {
|
|
31
|
+
"model_name": "BAAI/bge-base-en-v1.5",
|
|
32
|
+
"cache_name": "qdrant/bge-base-en-v1.5-onnx-q", # fastembed uses ONNX version
|
|
33
|
+
"dimensions": 768,
|
|
34
|
+
"size_mb": 220,
|
|
35
|
+
"description": "General purpose, good balance of speed and quality",
|
|
36
|
+
"use_case": "General text search, documentation",
|
|
37
|
+
"recommended": True,
|
|
38
|
+
},
|
|
39
|
+
"code": {
|
|
40
|
+
"model_name": "jinaai/jina-embeddings-v2-base-code",
|
|
41
|
+
"cache_name": "jinaai/jina-embeddings-v2-base-code", # Uses original name
|
|
42
|
+
"dimensions": 768,
|
|
43
|
+
"size_mb": 150,
|
|
44
|
+
"description": "Code-optimized, best for programming languages",
|
|
45
|
+
"use_case": "Open source projects, code semantic search",
|
|
46
|
+
"recommended": True,
|
|
47
|
+
},
|
|
48
|
+
"minilm": {
|
|
49
|
+
"model_name": "sentence-transformers/all-MiniLM-L6-v2",
|
|
50
|
+
"cache_name": "qdrant/all-MiniLM-L6-v2-onnx", # fastembed uses ONNX version
|
|
51
|
+
"dimensions": 384,
|
|
52
|
+
"size_mb": 90,
|
|
53
|
+
"description": "Popular lightweight model, good quality",
|
|
54
|
+
"use_case": "General purpose, low resource environments",
|
|
55
|
+
"recommended": True,
|
|
56
|
+
},
|
|
57
|
+
"multilingual": {
|
|
58
|
+
"model_name": "intfloat/multilingual-e5-large",
|
|
59
|
+
"cache_name": "qdrant/multilingual-e5-large-onnx", # fastembed uses ONNX version
|
|
60
|
+
"dimensions": 1024,
|
|
61
|
+
"size_mb": 1000,
|
|
62
|
+
"description": "Multilingual + code support (high resource usage)",
|
|
63
|
+
"use_case": "Enterprise multilingual projects",
|
|
64
|
+
"recommended": False, # 1024d not recommended
|
|
65
|
+
},
|
|
66
|
+
"balanced": {
|
|
67
|
+
"model_name": "mixedbread-ai/mxbai-embed-large-v1",
|
|
68
|
+
"cache_name": "mixedbread-ai/mxbai-embed-large-v1", # Uses original name
|
|
69
|
+
"dimensions": 1024,
|
|
70
|
+
"size_mb": 600,
|
|
71
|
+
"description": "High accuracy, general purpose (high resource usage)",
|
|
72
|
+
"use_case": "High-quality semantic search, balanced performance",
|
|
73
|
+
"recommended": False, # 1024d not recommended
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_cache_dir() -> Path:
|
|
79
|
+
"""Get fastembed cache directory.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Path to cache directory (usually ~/.cache/fastembed or %LOCALAPPDATA%\\Temp\\fastembed_cache)
|
|
83
|
+
"""
|
|
84
|
+
# Check HF_HOME environment variable first
|
|
85
|
+
if "HF_HOME" in os.environ:
|
|
86
|
+
return Path(os.environ["HF_HOME"])
|
|
87
|
+
|
|
88
|
+
# Default cache locations
|
|
89
|
+
if os.name == "nt": # Windows
|
|
90
|
+
cache_dir = Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData" / "Local")) / "Temp" / "fastembed_cache"
|
|
91
|
+
else: # Unix-like
|
|
92
|
+
cache_dir = Path.home() / ".cache" / "fastembed"
|
|
93
|
+
|
|
94
|
+
return cache_dir
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _get_model_cache_path(cache_dir: Path, info: Dict) -> Path:
|
|
98
|
+
"""Get the actual cache path for a model.
|
|
99
|
+
|
|
100
|
+
fastembed uses ONNX versions of models with different names than the original.
|
|
101
|
+
This function returns the correct path based on the cache_name field.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
cache_dir: The fastembed cache directory
|
|
105
|
+
info: Model profile info dictionary
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Path to the model cache directory
|
|
109
|
+
"""
|
|
110
|
+
cache_name = info.get("cache_name", info["model_name"])
|
|
111
|
+
return cache_dir / f"models--{cache_name.replace('/', '--')}"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def list_models() -> Dict[str, any]:
|
|
115
|
+
"""List available model profiles and their installation status.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Dictionary with model profiles, installed status, and cache info
|
|
119
|
+
"""
|
|
120
|
+
if not FASTEMBED_AVAILABLE:
|
|
121
|
+
return {
|
|
122
|
+
"success": False,
|
|
123
|
+
"error": "fastembed not installed. Install with: pip install codexlens[semantic]",
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
cache_dir = get_cache_dir()
|
|
127
|
+
cache_exists = cache_dir.exists()
|
|
128
|
+
|
|
129
|
+
models = []
|
|
130
|
+
for profile, info in MODEL_PROFILES.items():
|
|
131
|
+
model_name = info["model_name"]
|
|
132
|
+
|
|
133
|
+
# Check if model is cached using the actual cache name
|
|
134
|
+
installed = False
|
|
135
|
+
cache_size_mb = 0
|
|
136
|
+
|
|
137
|
+
if cache_exists:
|
|
138
|
+
# Check for model directory in cache using correct cache_name
|
|
139
|
+
model_cache_path = _get_model_cache_path(cache_dir, info)
|
|
140
|
+
if model_cache_path.exists():
|
|
141
|
+
installed = True
|
|
142
|
+
# Calculate cache size
|
|
143
|
+
total_size = sum(
|
|
144
|
+
f.stat().st_size
|
|
145
|
+
for f in model_cache_path.rglob("*")
|
|
146
|
+
if f.is_file()
|
|
147
|
+
)
|
|
148
|
+
cache_size_mb = round(total_size / (1024 * 1024), 1)
|
|
149
|
+
|
|
150
|
+
models.append({
|
|
151
|
+
"profile": profile,
|
|
152
|
+
"model_name": model_name,
|
|
153
|
+
"dimensions": info["dimensions"],
|
|
154
|
+
"estimated_size_mb": info["size_mb"],
|
|
155
|
+
"actual_size_mb": cache_size_mb if installed else None,
|
|
156
|
+
"description": info["description"],
|
|
157
|
+
"use_case": info["use_case"],
|
|
158
|
+
"installed": installed,
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
"success": True,
|
|
163
|
+
"result": {
|
|
164
|
+
"models": models,
|
|
165
|
+
"cache_dir": str(cache_dir),
|
|
166
|
+
"cache_exists": cache_exists,
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def download_model(profile: str, progress_callback: Optional[callable] = None) -> Dict[str, any]:
|
|
172
|
+
"""Download a model by profile name.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
profile: Model profile name (fast, code, multilingual, balanced)
|
|
176
|
+
progress_callback: Optional callback function to report progress
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Result dictionary with success status
|
|
180
|
+
"""
|
|
181
|
+
if not FASTEMBED_AVAILABLE:
|
|
182
|
+
return {
|
|
183
|
+
"success": False,
|
|
184
|
+
"error": "fastembed not installed. Install with: pip install codexlens[semantic]",
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if profile not in MODEL_PROFILES:
|
|
188
|
+
return {
|
|
189
|
+
"success": False,
|
|
190
|
+
"error": f"Unknown profile: {profile}. Available: {', '.join(MODEL_PROFILES.keys())}",
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
info = MODEL_PROFILES[profile]
|
|
194
|
+
model_name = info["model_name"]
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
# Download model by instantiating TextEmbedding
|
|
198
|
+
# This will automatically download to cache if not present
|
|
199
|
+
if progress_callback:
|
|
200
|
+
progress_callback(f"Downloading {model_name}...")
|
|
201
|
+
|
|
202
|
+
embedder = TextEmbedding(model_name=model_name)
|
|
203
|
+
|
|
204
|
+
if progress_callback:
|
|
205
|
+
progress_callback(f"Model {model_name} downloaded successfully")
|
|
206
|
+
|
|
207
|
+
# Get cache info using correct cache_name
|
|
208
|
+
cache_dir = get_cache_dir()
|
|
209
|
+
model_cache_path = _get_model_cache_path(cache_dir, info)
|
|
210
|
+
|
|
211
|
+
cache_size = 0
|
|
212
|
+
if model_cache_path.exists():
|
|
213
|
+
total_size = sum(
|
|
214
|
+
f.stat().st_size
|
|
215
|
+
for f in model_cache_path.rglob("*")
|
|
216
|
+
if f.is_file()
|
|
217
|
+
)
|
|
218
|
+
cache_size = round(total_size / (1024 * 1024), 1)
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
"success": True,
|
|
222
|
+
"result": {
|
|
223
|
+
"profile": profile,
|
|
224
|
+
"model_name": model_name,
|
|
225
|
+
"cache_size_mb": cache_size,
|
|
226
|
+
"cache_path": str(model_cache_path),
|
|
227
|
+
},
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
except Exception as e:
|
|
231
|
+
return {
|
|
232
|
+
"success": False,
|
|
233
|
+
"error": f"Failed to download model: {str(e)}",
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def delete_model(profile: str) -> Dict[str, any]:
|
|
238
|
+
"""Delete a downloaded model from cache.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
profile: Model profile name to delete
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
Result dictionary with success status
|
|
245
|
+
"""
|
|
246
|
+
if profile not in MODEL_PROFILES:
|
|
247
|
+
return {
|
|
248
|
+
"success": False,
|
|
249
|
+
"error": f"Unknown profile: {profile}. Available: {', '.join(MODEL_PROFILES.keys())}",
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
info = MODEL_PROFILES[profile]
|
|
253
|
+
model_name = info["model_name"]
|
|
254
|
+
cache_dir = get_cache_dir()
|
|
255
|
+
model_cache_path = _get_model_cache_path(cache_dir, info)
|
|
256
|
+
|
|
257
|
+
if not model_cache_path.exists():
|
|
258
|
+
return {
|
|
259
|
+
"success": False,
|
|
260
|
+
"error": f"Model {profile} ({model_name}) is not installed",
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
# Calculate size before deletion
|
|
265
|
+
total_size = sum(
|
|
266
|
+
f.stat().st_size
|
|
267
|
+
for f in model_cache_path.rglob("*")
|
|
268
|
+
if f.is_file()
|
|
269
|
+
)
|
|
270
|
+
size_mb = round(total_size / (1024 * 1024), 1)
|
|
271
|
+
|
|
272
|
+
# Delete model directory
|
|
273
|
+
shutil.rmtree(model_cache_path)
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
"success": True,
|
|
277
|
+
"result": {
|
|
278
|
+
"profile": profile,
|
|
279
|
+
"model_name": model_name,
|
|
280
|
+
"deleted_size_mb": size_mb,
|
|
281
|
+
"cache_path": str(model_cache_path),
|
|
282
|
+
},
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
except Exception as e:
|
|
286
|
+
return {
|
|
287
|
+
"success": False,
|
|
288
|
+
"error": f"Failed to delete model: {str(e)}",
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def get_model_info(profile: str) -> Dict[str, any]:
|
|
293
|
+
"""Get detailed information about a model profile.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
profile: Model profile name
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Result dictionary with model information
|
|
300
|
+
"""
|
|
301
|
+
if profile not in MODEL_PROFILES:
|
|
302
|
+
return {
|
|
303
|
+
"success": False,
|
|
304
|
+
"error": f"Unknown profile: {profile}. Available: {', '.join(MODEL_PROFILES.keys())}",
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
info = MODEL_PROFILES[profile]
|
|
308
|
+
model_name = info["model_name"]
|
|
309
|
+
|
|
310
|
+
# Check installation status using correct cache_name
|
|
311
|
+
cache_dir = get_cache_dir()
|
|
312
|
+
model_cache_path = _get_model_cache_path(cache_dir, info)
|
|
313
|
+
installed = model_cache_path.exists()
|
|
314
|
+
|
|
315
|
+
cache_size_mb = None
|
|
316
|
+
if installed:
|
|
317
|
+
total_size = sum(
|
|
318
|
+
f.stat().st_size
|
|
319
|
+
for f in model_cache_path.rglob("*")
|
|
320
|
+
if f.is_file()
|
|
321
|
+
)
|
|
322
|
+
cache_size_mb = round(total_size / (1024 * 1024), 1)
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
"success": True,
|
|
326
|
+
"result": {
|
|
327
|
+
"profile": profile,
|
|
328
|
+
"model_name": model_name,
|
|
329
|
+
"dimensions": info["dimensions"],
|
|
330
|
+
"estimated_size_mb": info["size_mb"],
|
|
331
|
+
"actual_size_mb": cache_size_mb,
|
|
332
|
+
"description": info["description"],
|
|
333
|
+
"use_case": info["use_case"],
|
|
334
|
+
"installed": installed,
|
|
335
|
+
"cache_path": str(model_cache_path) if installed else None,
|
|
336
|
+
},
|
|
337
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -396,7 +396,20 @@ class ChainSearchEngine:
|
|
|
396
396
|
all_results = []
|
|
397
397
|
stats = SearchStats()
|
|
398
398
|
|
|
399
|
-
|
|
399
|
+
# Force single-threaded execution for vector/hybrid search to avoid GPU crashes
|
|
400
|
+
# DirectML/ONNX have threading issues when multiple threads access GPU resources
|
|
401
|
+
effective_workers = options.max_workers
|
|
402
|
+
if options.enable_vector or options.hybrid_mode:
|
|
403
|
+
effective_workers = 1
|
|
404
|
+
self.logger.debug("Using single-threaded mode for vector search (GPU safety)")
|
|
405
|
+
# Pre-load embedder to avoid initialization overhead per-search
|
|
406
|
+
try:
|
|
407
|
+
from codexlens.semantic.embedder import get_embedder
|
|
408
|
+
get_embedder(profile="code", use_gpu=True)
|
|
409
|
+
except Exception:
|
|
410
|
+
pass # Ignore pre-load failures
|
|
411
|
+
|
|
412
|
+
executor = self._get_executor(effective_workers)
|
|
400
413
|
# Submit all search tasks
|
|
401
414
|
future_to_path = {
|
|
402
415
|
executor.submit(
|
|
@@ -274,19 +274,32 @@ class HybridSearchEngine:
|
|
|
274
274
|
)
|
|
275
275
|
return []
|
|
276
276
|
|
|
277
|
-
#
|
|
278
|
-
|
|
279
|
-
if
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
profile = "code"
|
|
286
|
-
elif detected_dim == 1024:
|
|
287
|
-
profile = "multilingual" # or balanced, both are 1024
|
|
277
|
+
# Get stored model configuration (preferred) or auto-detect from dimension
|
|
278
|
+
model_config = vector_store.get_model_config()
|
|
279
|
+
if model_config:
|
|
280
|
+
profile = model_config["model_profile"]
|
|
281
|
+
self.logger.debug(
|
|
282
|
+
"Using stored model config: %s (%s, %dd)",
|
|
283
|
+
profile, model_config["model_name"], model_config["embedding_dim"]
|
|
284
|
+
)
|
|
288
285
|
else:
|
|
289
|
-
|
|
286
|
+
# Fallback: auto-detect from embedding dimension
|
|
287
|
+
detected_dim = vector_store.dimension
|
|
288
|
+
if detected_dim is None:
|
|
289
|
+
self.logger.info("Vector store dimension unknown, using default profile")
|
|
290
|
+
profile = "code" # Default fallback
|
|
291
|
+
elif detected_dim == 384:
|
|
292
|
+
profile = "fast"
|
|
293
|
+
elif detected_dim == 768:
|
|
294
|
+
profile = "code"
|
|
295
|
+
elif detected_dim == 1024:
|
|
296
|
+
profile = "multilingual" # or balanced, both are 1024
|
|
297
|
+
else:
|
|
298
|
+
profile = "code" # Default fallback
|
|
299
|
+
self.logger.debug(
|
|
300
|
+
"No stored model config, auto-detected profile '%s' from dimension %s",
|
|
301
|
+
profile, detected_dim
|
|
302
|
+
)
|
|
290
303
|
|
|
291
304
|
# Use cached embedder (singleton) for performance
|
|
292
305
|
embedder = get_embedder(profile=profile)
|
|
Binary file
|
|
@@ -116,6 +116,17 @@ class VectorStore:
|
|
|
116
116
|
CREATE INDEX IF NOT EXISTS idx_chunks_file
|
|
117
117
|
ON semantic_chunks(file_path)
|
|
118
118
|
""")
|
|
119
|
+
# Model configuration table - tracks which model generated the embeddings
|
|
120
|
+
conn.execute("""
|
|
121
|
+
CREATE TABLE IF NOT EXISTS embeddings_config (
|
|
122
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
123
|
+
model_profile TEXT NOT NULL,
|
|
124
|
+
model_name TEXT NOT NULL,
|
|
125
|
+
embedding_dim INTEGER NOT NULL,
|
|
126
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
127
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
128
|
+
)
|
|
129
|
+
""")
|
|
119
130
|
conn.commit()
|
|
120
131
|
|
|
121
132
|
def _init_ann_index(self) -> None:
|
|
@@ -932,6 +943,92 @@ class VectorStore:
|
|
|
932
943
|
return self._ann_index.count()
|
|
933
944
|
return 0
|
|
934
945
|
|
|
946
|
+
def get_model_config(self) -> Optional[Dict[str, Any]]:
|
|
947
|
+
"""Get the model configuration used for embeddings in this store.
|
|
948
|
+
|
|
949
|
+
Returns:
|
|
950
|
+
Dictionary with model_profile, model_name, embedding_dim, or None if not set.
|
|
951
|
+
"""
|
|
952
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
953
|
+
row = conn.execute(
|
|
954
|
+
"SELECT model_profile, model_name, embedding_dim, created_at, updated_at "
|
|
955
|
+
"FROM embeddings_config WHERE id = 1"
|
|
956
|
+
).fetchone()
|
|
957
|
+
if row:
|
|
958
|
+
return {
|
|
959
|
+
"model_profile": row[0],
|
|
960
|
+
"model_name": row[1],
|
|
961
|
+
"embedding_dim": row[2],
|
|
962
|
+
"created_at": row[3],
|
|
963
|
+
"updated_at": row[4],
|
|
964
|
+
}
|
|
965
|
+
return None
|
|
966
|
+
|
|
967
|
+
def set_model_config(
|
|
968
|
+
self, model_profile: str, model_name: str, embedding_dim: int
|
|
969
|
+
) -> None:
|
|
970
|
+
"""Set the model configuration for embeddings in this store.
|
|
971
|
+
|
|
972
|
+
This should be called when generating new embeddings. If a different
|
|
973
|
+
model was previously used, this will update the configuration.
|
|
974
|
+
|
|
975
|
+
Args:
|
|
976
|
+
model_profile: Model profile name (fast, code, minilm, etc.)
|
|
977
|
+
model_name: Full model name (e.g., jinaai/jina-embeddings-v2-base-code)
|
|
978
|
+
embedding_dim: Embedding dimension (e.g., 768)
|
|
979
|
+
"""
|
|
980
|
+
with sqlite3.connect(self.db_path) as conn:
|
|
981
|
+
conn.execute(
|
|
982
|
+
"""
|
|
983
|
+
INSERT INTO embeddings_config (id, model_profile, model_name, embedding_dim)
|
|
984
|
+
VALUES (1, ?, ?, ?)
|
|
985
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
986
|
+
model_profile = excluded.model_profile,
|
|
987
|
+
model_name = excluded.model_name,
|
|
988
|
+
embedding_dim = excluded.embedding_dim,
|
|
989
|
+
updated_at = CURRENT_TIMESTAMP
|
|
990
|
+
""",
|
|
991
|
+
(model_profile, model_name, embedding_dim)
|
|
992
|
+
)
|
|
993
|
+
conn.commit()
|
|
994
|
+
|
|
995
|
+
def check_model_compatibility(
|
|
996
|
+
self, model_profile: str, model_name: str, embedding_dim: int
|
|
997
|
+
) -> Tuple[bool, Optional[str]]:
|
|
998
|
+
"""Check if the given model is compatible with existing embeddings.
|
|
999
|
+
|
|
1000
|
+
Args:
|
|
1001
|
+
model_profile: Model profile to check
|
|
1002
|
+
model_name: Model name to check
|
|
1003
|
+
embedding_dim: Embedding dimension to check
|
|
1004
|
+
|
|
1005
|
+
Returns:
|
|
1006
|
+
Tuple of (is_compatible, warning_message).
|
|
1007
|
+
is_compatible is True if no existing config or configs match.
|
|
1008
|
+
warning_message is a user-friendly message if incompatible.
|
|
1009
|
+
"""
|
|
1010
|
+
existing = self.get_model_config()
|
|
1011
|
+
if existing is None:
|
|
1012
|
+
return True, None
|
|
1013
|
+
|
|
1014
|
+
# Check dimension first (most critical)
|
|
1015
|
+
if existing["embedding_dim"] != embedding_dim:
|
|
1016
|
+
return False, (
|
|
1017
|
+
f"Dimension mismatch: existing embeddings use {existing['embedding_dim']}d "
|
|
1018
|
+
f"({existing['model_profile']}), but requested model uses {embedding_dim}d "
|
|
1019
|
+
f"({model_profile}). Use --force to regenerate all embeddings."
|
|
1020
|
+
)
|
|
1021
|
+
|
|
1022
|
+
# Check model (different models with same dimension may have different semantic spaces)
|
|
1023
|
+
if existing["model_profile"] != model_profile:
|
|
1024
|
+
return False, (
|
|
1025
|
+
f"Model mismatch: existing embeddings use '{existing['model_profile']}' "
|
|
1026
|
+
f"({existing['model_name']}), but requested '{model_profile}' "
|
|
1027
|
+
f"({model_name}). Use --force to regenerate all embeddings."
|
|
1028
|
+
)
|
|
1029
|
+
|
|
1030
|
+
return True, None
|
|
1031
|
+
|
|
935
1032
|
def close(self) -> None:
|
|
936
1033
|
"""Close the vector store and release resources.
|
|
937
1034
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-workflow",
|
|
3
|
-
"version": "6.2.
|
|
3
|
+
"version": "6.2.7",
|
|
4
4
|
"description": "JSON-driven multi-agent development framework with intelligent CLI orchestration (Gemini/Qwen/Codex), context-first architecture, and automated workflow execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "ccw/src/index.js",
|