Skip to content

Commit b77672d

Browse files
author
catlog22
committed
feat: 增强模型下载功能,支持 HuggingFace Hub 直接下载 ONNX 格式模型
1 parent 1e91fa9 commit b77672d

3 files changed

Lines changed: 75 additions & 45 deletions

File tree

ccw/src/templates/dashboard-css/31-api-settings.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -916,7 +916,7 @@ select.cli-input {
916916
.cli-textarea {
917917
resize: vertical;
918918
min-height: 4rem;
919-
max-height: 12rem;
919+
max-height: 20rem;
920920
font-family: 'SF Mono', 'Consolas', 'Liberation Mono', monospace;
921921
font-size: 0.8125rem;
922922
}
@@ -2681,7 +2681,7 @@ select.cli-input {
26812681
display: flex;
26822682
position: relative;
26832683
min-height: 200px;
2684-
max-height: 350px;
2684+
max-height: min(450px, 50vh);
26852685
}
26862686

26872687
.json-line-numbers {

ccw/src/templates/dashboard-js/views/codexlens-manager.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2672,14 +2672,15 @@ async function loadModelList() {
26722672
'<i data-lucide="plus-circle" class="w-3 h-3"></i> Download Custom Model' +
26732673
'</div>' +
26742674
'<div class="flex gap-2">' +
2675-
'<input type="text" id="customModelInput" placeholder="e.g., BAAI/bge-small-en-v1.5" ' +
2675+
'<input type="text" id="customModelInput" placeholder="e.g., Xenova/bge-small-en-v1.5" ' +
26762676
'class="flex-1 text-xs px-2 py-1.5 border border-border rounded bg-background focus:border-primary focus:ring-1 focus:ring-primary outline-none" />' +
26772677
'<button onclick="downloadCustomModel()" class="text-xs px-3 py-1.5 bg-primary text-primary-foreground rounded hover:bg-primary/90">' +
26782678
'Download' +
26792679
'</button>' +
26802680
'</div>' +
2681-
'<div class="text-[10px] text-muted-foreground mt-2">' +
2682-
'Enter any HuggingFace model name compatible with FastEmbed' +
2681+
'<div class="text-[10px] text-muted-foreground mt-2 space-y-1">' +
2682+
'<div><span class="text-amber-500">⚠</span> Only <strong>ONNX-format</strong> models work with FastEmbed (e.g., Xenova/* models)</div>' +
2683+
'<div>PyTorch models (intfloat/*, sentence-transformers/*) will download but won\'t work with local embedding</div>' +
26832684
'</div>' +
26842685
'</div>';
26852686
} else {

codex-lens/src/codexlens/cli/model_manager.py

Lines changed: 69 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
from pathlib import Path
77
from typing import Dict, List, Optional
88

9+
try:
10+
from huggingface_hub import snapshot_download, list_repo_files
11+
HUGGINGFACE_HUB_AVAILABLE = True
12+
except ImportError:
13+
HUGGINGFACE_HUB_AVAILABLE = False
14+
915
try:
1016
from fastembed import TextEmbedding
1117
FASTEMBED_AVAILABLE = True
@@ -557,64 +563,85 @@ def download_model(profile: str, progress_callback: Optional[callable] = None) -
557563

558564
def download_custom_model(model_name: str, model_type: str = "embedding", progress_callback: Optional[callable] = None) -> Dict[str, any]:
559565
"""Download a custom model by HuggingFace model name.
560-
561-
This allows users to download any HuggingFace model that is compatible
562-
with fastembed (TextEmbedding or TextCrossEncoder).
563-
566+
567+
This allows users to download any HuggingFace model directly from
568+
HuggingFace Hub. The model will be placed in the standard cache
569+
directory where it can be discovered by scan_discovered_models().
570+
571+
Note: Downloaded models may not be directly usable by FastEmbed unless
572+
they are in ONNX format. This function is primarily for downloading
573+
models that users want to use with other frameworks or custom code.
574+
564575
Args:
565-
model_name: Full HuggingFace model name (e.g., "BAAI/bge-small-en-v1.5")
566-
model_type: Type of model ("embedding" or "reranker")
576+
model_name: Full HuggingFace model name (e.g., "intfloat/e5-small-v2")
577+
model_type: Type of model ("embedding" or "reranker") - for metadata only
567578
progress_callback: Optional callback function to report progress
568-
579+
569580
Returns:
570581
Result dictionary with success status
571582
"""
572-
if model_type == "embedding":
573-
if not FASTEMBED_AVAILABLE:
574-
return {
575-
"success": False,
576-
"error": "fastembed not installed. Install with: pip install codexlens[semantic]",
577-
}
578-
else:
579-
if not RERANKER_AVAILABLE:
580-
return {
581-
"success": False,
582-
"error": "fastembed reranker not available. Install with: pip install fastembed>=0.4.0",
583-
}
584-
583+
if not HUGGINGFACE_HUB_AVAILABLE:
584+
return {
585+
"success": False,
586+
"error": "huggingface_hub not installed. Install with: pip install huggingface_hub",
587+
}
588+
585589
# Validate model name format (org/model-name)
586590
if not model_name or "/" not in model_name:
587591
return {
588592
"success": False,
589-
"error": "Invalid model name format. Expected: 'org/model-name' (e.g., 'BAAI/bge-small-en-v1.5')",
593+
"error": "Invalid model name format. Expected: 'org/model-name' (e.g., 'intfloat/e5-small-v2')",
590594
}
591-
595+
592596
try:
593597
cache_dir = get_cache_dir()
594-
598+
595599
if progress_callback:
596-
progress_callback(f"Downloading custom model {model_name}...")
597-
598-
if model_type == "reranker":
599-
# Download reranker model
600-
reranker = TextCrossEncoder(model_name=model_name, cache_dir=str(cache_dir))
600+
progress_callback(f"Checking model format for {model_name}...")
601+
602+
# Check if model contains ONNX files before downloading
603+
try:
604+
files = list_repo_files(repo_id=model_name)
605+
has_onnx = any(
606+
f.endswith('.onnx') or
607+
f.startswith('onnx/') or
608+
'/onnx/' in f or
609+
f == 'model.onnx'
610+
for f in files
611+
)
612+
613+
if not has_onnx:
614+
return {
615+
"success": False,
616+
"error": f"Model '{model_name}' does not contain ONNX files. "
617+
f"FastEmbed requires ONNX-format models. "
618+
f"Try Xenova/* versions or check the recommended models list.",
619+
"files_found": len(files),
620+
"suggestion": "Use models from the 'Recommended Models' list, or search for ONNX versions (e.g., Xenova/*).",
621+
}
622+
601623
if progress_callback:
602-
progress_callback(f"Initializing reranker {model_name}...")
603-
list(reranker.rerank("test query", ["test document"]))
604-
else:
605-
# Download embedding model
606-
embedder = TextEmbedding(model_name=model_name, cache_dir=str(cache_dir))
624+
progress_callback(f"ONNX format detected. Downloading {model_name}...")
625+
626+
except Exception as check_err:
627+
# If we can't check, warn but allow download
607628
if progress_callback:
608-
progress_callback(f"Initializing {model_name}...")
609-
list(embedder.embed(["test"]))
610-
629+
progress_callback(f"Could not verify format, proceeding with download...")
630+
631+
# Use huggingface_hub to download the model
632+
# This downloads to the standard HuggingFace cache directory
633+
local_path = snapshot_download(
634+
repo_id=model_name,
635+
cache_dir=str(cache_dir),
636+
)
637+
611638
if progress_callback:
612-
progress_callback(f"Custom model {model_name} downloaded successfully")
613-
639+
progress_callback(f"Model {model_name} downloaded successfully")
640+
614641
# Get cache info
615642
sanitized_name = f"models--{model_name.replace('/', '--')}"
616643
model_cache_path = cache_dir / sanitized_name
617-
644+
618645
cache_size = 0
619646
if model_cache_path.exists():
620647
total_size = sum(
@@ -623,14 +650,16 @@ def download_custom_model(model_name: str, model_type: str = "embedding", progre
623650
if f.is_file()
624651
)
625652
cache_size = round(total_size / (1024 * 1024), 1)
626-
653+
627654
return {
628655
"success": True,
629656
"result": {
630657
"model_name": model_name,
631658
"model_type": model_type,
632659
"cache_size_mb": cache_size,
633660
"cache_path": str(model_cache_path),
661+
"local_path": local_path,
662+
"note": "Model downloaded. Note: Only ONNX-format models are compatible with FastEmbed.",
634663
},
635664
}
636665

0 commit comments

Comments
 (0)