mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
Put the description back
This commit is contained in:
@@ -602,12 +602,13 @@ derive_utoipa!(Icon as IconSchema);
|
||||
super::routes::dictation::DictationProviderStatus,
|
||||
super::routes::dictation::WhisperModelResponse,
|
||||
super::routes::local_inference::LocalModelResponse,
|
||||
super::routes::local_inference::RepoVariantsResponse,
|
||||
super::routes::local_inference::ModelDownloadStatus,
|
||||
super::routes::local_inference::DownloadProgressResponse,
|
||||
super::routes::local_inference::HfSearchResult,
|
||||
super::routes::local_inference::HfQuantVariant,
|
||||
super::routes::local_inference::DownloadModelRequest,
|
||||
goose::providers::local_inference::hf_models::HfModelInfo,
|
||||
goose::providers::local_inference::hf_models::HfQuantVariant,
|
||||
goose::providers::local_inference::hf_models::HfQuantVariant,
|
||||
goose::providers::local_inference::hf_models::HfGgufFile,
|
||||
goose::providers::local_inference::local_model_registry::ModelSettings,
|
||||
goose::providers::local_inference::local_model_registry::SamplingConfig,
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use crate::routes::errors::ErrorResponse;
|
||||
use crate::state::AppState;
|
||||
use axum::{
|
||||
extract::Path,
|
||||
extract::{Path, Query},
|
||||
http::StatusCode,
|
||||
routing::{delete, get, post},
|
||||
Json, Router,
|
||||
};
|
||||
use goose::config::paths::Paths;
|
||||
use goose::providers::local_inference::{
|
||||
hf_models::{get_repo_gguf_files, resolve_model_spec, search_gguf_models, HfGgufFile},
|
||||
available_inference_memory_bytes, hf_models,
|
||||
hf_models::{resolve_model_spec, HfGgufFile, HfModelInfo, HfQuantVariant},
|
||||
local_model_registry::{
|
||||
display_name_from_repo, get_registry, is_featured_model, model_id_from_repo,
|
||||
parse_model_spec, LocalModelEntry, ModelDownloadStatus as RegistryDownloadStatus,
|
||||
@@ -197,93 +198,69 @@ pub async fn list_local_models() -> Result<Json<Vec<LocalModelResponse>>, ErrorR
|
||||
Ok(Json(models))
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct HfSearchResult {
|
||||
pub id: String,
|
||||
pub author: String,
|
||||
pub name: String,
|
||||
pub downloads: u64,
|
||||
pub likes: u64,
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SearchQuery {
|
||||
pub q: String,
|
||||
pub limit: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct RepoVariantsResponse {
|
||||
pub variants: Vec<HfQuantVariant>,
|
||||
pub recommended_index: Option<usize>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/local-inference/search",
|
||||
params(
|
||||
("q" = String, Query, description = "Search query")
|
||||
("q" = String, Query, description = "Search query"),
|
||||
("limit" = Option<usize>, Query, description = "Max results")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Search results", body = Vec<HfSearchResult>)
|
||||
(status = 200, description = "Search results", body = Vec<HfModelInfo>),
|
||||
(status = 500, description = "Search failed")
|
||||
)
|
||||
)]
|
||||
pub async fn search_hf_models(
|
||||
axum::extract::Query(params): axum::extract::Query<std::collections::HashMap<String, String>>,
|
||||
) -> Result<Json<Vec<HfSearchResult>>, ErrorResponse> {
|
||||
let query = params.get("q").cloned().unwrap_or_default();
|
||||
|
||||
let results = search_gguf_models(&query, 20)
|
||||
Query(params): Query<SearchQuery>,
|
||||
) -> Result<Json<Vec<HfModelInfo>>, ErrorResponse> {
|
||||
let limit = params.limit.unwrap_or(20).min(50);
|
||||
let results = hf_models::search_gguf_models(¶ms.q, limit)
|
||||
.await
|
||||
.map_err(|e| ErrorResponse::internal(format!("HF search failed: {}", e)))?;
|
||||
|
||||
let search_results: Vec<HfSearchResult> = results
|
||||
.into_iter()
|
||||
.map(|m| HfSearchResult {
|
||||
id: m.repo_id.clone(),
|
||||
author: m.author,
|
||||
name: m.model_name,
|
||||
downloads: m.downloads,
|
||||
likes: 0, // HfModelInfo doesn't have likes
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(search_results))
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct HfQuantVariant {
|
||||
pub filename: String,
|
||||
pub size_bytes: u64,
|
||||
pub quantization: String,
|
||||
pub download_url: String,
|
||||
pub quality_rank: u32,
|
||||
.map_err(|e| ErrorResponse::internal(format!("Search failed: {}", e)))?;
|
||||
Ok(Json(results))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/local-inference/repo/{author}/{repo}/files",
|
||||
responses(
|
||||
(status = 200, description = "GGUF files in the repo", body = Vec<HfQuantVariant>)
|
||||
(status = 200, description = "GGUF files in the repo", body = RepoVariantsResponse)
|
||||
)
|
||||
)]
|
||||
pub async fn get_repo_files(
|
||||
Path((author, repo)): Path<(String, String)>,
|
||||
) -> Result<Json<Vec<HfQuantVariant>>, ErrorResponse> {
|
||||
) -> Result<Json<RepoVariantsResponse>, ErrorResponse> {
|
||||
let repo_id = format!("{}/{}", author, repo);
|
||||
let files = get_repo_gguf_files(&repo_id)
|
||||
let variants = hf_models::get_repo_gguf_variants(&repo_id)
|
||||
.await
|
||||
.map_err(|e| ErrorResponse::internal(format!("Failed to fetch files: {}", e)))?;
|
||||
.map_err(|e| ErrorResponse::internal(format!("Failed to fetch repo files: {}", e)))?;
|
||||
|
||||
let variants: Vec<HfQuantVariant> = files
|
||||
.into_iter()
|
||||
.map(|f| HfQuantVariant {
|
||||
filename: f.filename,
|
||||
size_bytes: f.size_bytes,
|
||||
quantization: f.quantization,
|
||||
download_url: f.download_url,
|
||||
quality_rank: 0,
|
||||
})
|
||||
.collect();
|
||||
let runtime = InferenceRuntime::get_or_init();
|
||||
let available_memory = available_inference_memory_bytes(&runtime);
|
||||
let recommended_index = hf_models::recommend_variant(&variants, available_memory);
|
||||
|
||||
Ok(Json(variants))
|
||||
Ok(Json(RepoVariantsResponse {
|
||||
variants,
|
||||
recommended_index,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct DownloadModelRequest {
|
||||
/// Model spec like "bartowski/Llama-3.2-3B-Instruct-GGUF:Q4_K_M"
|
||||
pub spec: Option<String>,
|
||||
/// Alternative: provide repo_id and filename separately
|
||||
pub repo_id: Option<String>,
|
||||
pub filename: Option<String>,
|
||||
pub spec: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -298,34 +275,13 @@ pub struct DownloadModelRequest {
|
||||
pub async fn download_hf_model(
|
||||
Json(req): Json<DownloadModelRequest>,
|
||||
) -> Result<(StatusCode, Json<String>), ErrorResponse> {
|
||||
let (repo_id, quantization, hf_file) = if let Some(spec) = &req.spec {
|
||||
// Parse spec like "bartowski/Llama-3.2-3B-Instruct-GGUF:Q4_K_M"
|
||||
let (_repo, file) = resolve_model_spec(spec)
|
||||
.await
|
||||
.map_err(|e| ErrorResponse::bad_request(format!("Invalid spec: {}", e)))?;
|
||||
// Parse spec like "bartowski/Llama-3.2-3B-Instruct-GGUF:Q4_K_M"
|
||||
let (repo_id, quantization) = parse_model_spec(&req.spec)
|
||||
.ok_or_else(|| ErrorResponse::bad_request("Invalid spec format"))?;
|
||||
|
||||
let (repo, quant) = parse_model_spec(spec)
|
||||
.ok_or_else(|| ErrorResponse::bad_request("Invalid spec format"))?;
|
||||
|
||||
(repo.to_string(), quant.to_string(), file)
|
||||
} else if let (Some(repo_id), Some(filename)) = (&req.repo_id, &req.filename) {
|
||||
// Get file info from repo
|
||||
let files = get_repo_gguf_files(repo_id)
|
||||
.await
|
||||
.map_err(|e| ErrorResponse::internal(format!("Failed to fetch files: {}", e)))?;
|
||||
|
||||
let file = files
|
||||
.into_iter()
|
||||
.find(|f| f.filename == *filename)
|
||||
.ok_or_else(|| ErrorResponse::not_found("File not found in repo"))?;
|
||||
|
||||
let quantization = file.quantization.clone();
|
||||
(repo_id.clone(), quantization, file)
|
||||
} else {
|
||||
return Err(ErrorResponse::bad_request(
|
||||
"Must provide either 'spec' or both 'repo_id' and 'filename'",
|
||||
));
|
||||
};
|
||||
let (_repo, hf_file) = resolve_model_spec(&req.spec)
|
||||
.await
|
||||
.map_err(|e| ErrorResponse::bad_request(format!("Invalid spec: {}", e)))?;
|
||||
|
||||
let model_id = model_id_from_repo(&repo_id, &quantization);
|
||||
let local_path = Paths::in_data_dir("models").join(&hf_file.filename);
|
||||
|
||||
+46
-49
@@ -2073,10 +2073,7 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/HfQuantVariant"
|
||||
}
|
||||
"$ref": "#/components/schemas/RepoVariantsResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2099,6 +2096,17 @@
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"description": "Max results",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"nullable": true,
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -2109,11 +2117,14 @@
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/HfSearchResult"
|
||||
"$ref": "#/components/schemas/HfModelInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Search failed"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4247,20 +4258,13 @@
|
||||
},
|
||||
"DownloadModelRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"spec"
|
||||
],
|
||||
"properties": {
|
||||
"filename": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"repo_id": {
|
||||
"type": "string",
|
||||
"description": "Alternative: provide repo_id and filename separately",
|
||||
"nullable": true
|
||||
},
|
||||
"spec": {
|
||||
"type": "string",
|
||||
"description": "Model spec like \"bartowski/Llama-3.2-3B-Instruct-GGUF:Q4_K_M\"",
|
||||
"nullable": true
|
||||
"description": "Model spec like \"bartowski/Llama-3.2-3B-Instruct-GGUF:Q4_K_M\""
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5002,14 +5006,19 @@
|
||||
},
|
||||
"HfQuantVariant": {
|
||||
"type": "object",
|
||||
"description": "A quantization variant — groups sharded files into one logical entry.",
|
||||
"required": [
|
||||
"filename",
|
||||
"size_bytes",
|
||||
"quantization",
|
||||
"size_bytes",
|
||||
"filename",
|
||||
"download_url",
|
||||
"description",
|
||||
"quality_rank"
|
||||
],
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"download_url": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5031,37 +5040,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"HfSearchResult": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"author",
|
||||
"name",
|
||||
"downloads",
|
||||
"likes"
|
||||
],
|
||||
"properties": {
|
||||
"author": {
|
||||
"type": "string"
|
||||
},
|
||||
"downloads": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"minimum": 0
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"likes": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"minimum": 0
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Icon": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -6667,6 +6645,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"RepoVariantsResponse": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"variants"
|
||||
],
|
||||
"properties": {
|
||||
"recommended_index": {
|
||||
"type": "integer",
|
||||
"nullable": true,
|
||||
"minimum": 0
|
||||
},
|
||||
"variants": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/HfQuantVariant"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ResourceContents": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -233,15 +233,10 @@ export type DictationProviderStatus = {
|
||||
};
|
||||
|
||||
export type DownloadModelRequest = {
|
||||
filename?: string | null;
|
||||
/**
|
||||
* Alternative: provide repo_id and filename separately
|
||||
*/
|
||||
repo_id?: string | null;
|
||||
/**
|
||||
* Model spec like "bartowski/Llama-3.2-3B-Instruct-GGUF:Q4_K_M"
|
||||
*/
|
||||
spec?: string | null;
|
||||
spec: string;
|
||||
};
|
||||
|
||||
export type DownloadProgress = {
|
||||
@@ -486,7 +481,11 @@ export type HfModelInfo = {
|
||||
repo_id: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A quantization variant — groups sharded files into one logical entry.
|
||||
*/
|
||||
export type HfQuantVariant = {
|
||||
description: string;
|
||||
download_url: string;
|
||||
filename: string;
|
||||
quality_rank: number;
|
||||
@@ -494,14 +493,6 @@ export type HfQuantVariant = {
|
||||
size_bytes: number;
|
||||
};
|
||||
|
||||
export type HfSearchResult = {
|
||||
author: string;
|
||||
downloads: number;
|
||||
id: string;
|
||||
likes: number;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type Icon = {
|
||||
mimeType?: string;
|
||||
sizes?: Array<string>;
|
||||
@@ -1007,6 +998,11 @@ export type RemoveExtensionRequest = {
|
||||
session_id: string;
|
||||
};
|
||||
|
||||
export type RepoVariantsResponse = {
|
||||
recommended_index?: number | null;
|
||||
variants: Array<HfQuantVariant>;
|
||||
};
|
||||
|
||||
export type ResourceContents = {
|
||||
_meta?: {
|
||||
[key: string]: unknown;
|
||||
@@ -3128,7 +3124,7 @@ export type GetRepoFilesResponses = {
|
||||
/**
|
||||
* GGUF files in the repo
|
||||
*/
|
||||
200: Array<HfQuantVariant>;
|
||||
200: RepoVariantsResponse;
|
||||
};
|
||||
|
||||
export type GetRepoFilesResponse = GetRepoFilesResponses[keyof GetRepoFilesResponses];
|
||||
@@ -3141,15 +3137,26 @@ export type SearchHfModelsData = {
|
||||
* Search query
|
||||
*/
|
||||
q: string;
|
||||
/**
|
||||
* Max results
|
||||
*/
|
||||
limit?: number | null;
|
||||
};
|
||||
url: '/local-inference/search';
|
||||
};
|
||||
|
||||
export type SearchHfModelsErrors = {
|
||||
/**
|
||||
* Search failed
|
||||
*/
|
||||
500: unknown;
|
||||
};
|
||||
|
||||
export type SearchHfModelsResponses = {
|
||||
/**
|
||||
* Search results
|
||||
*/
|
||||
200: Array<HfSearchResult>;
|
||||
200: Array<HfModelInfo>;
|
||||
};
|
||||
|
||||
export type SearchHfModelsResponse = SearchHfModelsResponses[keyof SearchHfModelsResponses];
|
||||
|
||||
@@ -106,10 +106,7 @@ export function LocalModelSetup({ onSuccess, onCancel }: LocalModelSetupProps) {
|
||||
|
||||
try {
|
||||
await downloadHfModel({
|
||||
body: {
|
||||
repo_id: model.repo_id,
|
||||
filename: model.filename,
|
||||
},
|
||||
body: { spec: model.id },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to start download:', error);
|
||||
|
||||
@@ -5,8 +5,9 @@ import {
|
||||
searchHfModels,
|
||||
getRepoFiles,
|
||||
downloadHfModel,
|
||||
type HfSearchResult,
|
||||
type HfModelInfo,
|
||||
type HfQuantVariant,
|
||||
type RepoVariantsResponse,
|
||||
} from '../../../api';
|
||||
|
||||
const formatBytes = (bytes: number): string => {
|
||||
@@ -34,7 +35,7 @@ interface Props {
|
||||
|
||||
export const HuggingFaceModelSearch = ({ onDownloadStarted }: Props) => {
|
||||
const [query, setQuery] = useState('');
|
||||
const [results, setResults] = useState<HfSearchResult[]>([]);
|
||||
const [results, setResults] = useState<HfModelInfo[]>([]);
|
||||
const [expandedRepo, setExpandedRepo] = useState<string | null>(null);
|
||||
const [repoData, setRepoData] = useState<Record<string, RepoData>>({});
|
||||
const [searching, setSearching] = useState(false);
|
||||
@@ -45,8 +46,8 @@ export const HuggingFaceModelSearch = ({ onDownloadStarted }: Props) => {
|
||||
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
// Build repo_id from search result
|
||||
const getRepoId = (result: HfSearchResult): string => {
|
||||
return `${result.author}/${result.name}`;
|
||||
const getRepoId = (result: HfModelInfo): string => {
|
||||
return result.repo_id;
|
||||
};
|
||||
|
||||
const doSearch = useCallback(async (q: string) => {
|
||||
@@ -69,8 +70,8 @@ export const HuggingFaceModelSearch = ({ onDownloadStarted }: Props) => {
|
||||
const repoId = getRepoId(model);
|
||||
const [author, repo] = repoId.split('/');
|
||||
const filesResponse = await getRepoFiles({ path: { author, repo } });
|
||||
if (filesResponse.data && filesResponse.data.length > 0) {
|
||||
return { model, variants: filesResponse.data };
|
||||
if (filesResponse.data && filesResponse.data.variants.length > 0) {
|
||||
return { model, variantsResponse: filesResponse.data };
|
||||
}
|
||||
} catch {
|
||||
// Skip repos we can't fetch
|
||||
@@ -80,8 +81,8 @@ export const HuggingFaceModelSearch = ({ onDownloadStarted }: Props) => {
|
||||
);
|
||||
|
||||
const validResults = modelsWithVariants.filter(Boolean) as {
|
||||
model: HfSearchResult;
|
||||
variants: HfQuantVariant[];
|
||||
model: HfModelInfo;
|
||||
variantsResponse: RepoVariantsResponse;
|
||||
}[];
|
||||
|
||||
setResults(validResults.map((r) => r.model));
|
||||
@@ -89,21 +90,9 @@ export const HuggingFaceModelSearch = ({ onDownloadStarted }: Props) => {
|
||||
const next = { ...prev };
|
||||
for (const r of validResults) {
|
||||
const repoId = getRepoId(r.model);
|
||||
// Find the best variant (highest quality_rank that's not too big)
|
||||
let recommendedIdx: number | null = null;
|
||||
const sorted = [...r.variants].sort((a, b) => b.quality_rank - a.quality_rank);
|
||||
// Recommend the highest quality variant under 8GB, or the smallest if all are big
|
||||
const under8GB = sorted.filter((v) => v.size_bytes < 8 * 1024 * 1024 * 1024);
|
||||
if (under8GB.length > 0) {
|
||||
recommendedIdx = r.variants.findIndex((v) => v.filename === under8GB[0].filename);
|
||||
} else {
|
||||
// Pick smallest
|
||||
const smallest = [...r.variants].sort((a, b) => a.size_bytes - b.size_bytes)[0];
|
||||
recommendedIdx = r.variants.findIndex((v) => v.filename === smallest.filename);
|
||||
}
|
||||
next[repoId] = {
|
||||
variants: r.variants,
|
||||
recommendedIndex: recommendedIdx,
|
||||
variants: r.variantsResponse.variants,
|
||||
recommendedIndex: r.variantsResponse.recommended_index ?? null,
|
||||
};
|
||||
}
|
||||
return next;
|
||||
@@ -150,20 +139,12 @@ export const HuggingFaceModelSearch = ({ onDownloadStarted }: Props) => {
|
||||
const [author, repo] = repoId.split('/');
|
||||
const response = await getRepoFiles({ path: { author, repo } });
|
||||
if (response.data) {
|
||||
const variants = response.data;
|
||||
// Find recommended
|
||||
let recommendedIdx: number | null = null;
|
||||
const sorted = [...variants].sort((a, b) => b.quality_rank - a.quality_rank);
|
||||
const under8GB = sorted.filter((v) => v.size_bytes < 8 * 1024 * 1024 * 1024);
|
||||
if (under8GB.length > 0) {
|
||||
recommendedIdx = variants.findIndex((v) => v.filename === under8GB[0].filename);
|
||||
} else {
|
||||
const smallest = [...variants].sort((a, b) => a.size_bytes - b.size_bytes)[0];
|
||||
recommendedIdx = variants.findIndex((v) => v.filename === smallest.filename);
|
||||
}
|
||||
setRepoData((prev) => ({
|
||||
...prev,
|
||||
[repoId]: { variants, recommendedIndex: recommendedIdx },
|
||||
[repoId]: {
|
||||
variants: response.data.variants,
|
||||
recommendedIndex: response.data.recommended_index ?? null,
|
||||
},
|
||||
}));
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -178,12 +159,13 @@ export const HuggingFaceModelSearch = ({ onDownloadStarted }: Props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const startDownload = async (repoId: string, filename: string) => {
|
||||
const dlKey = `${repoId}/${filename}`;
|
||||
const startDownload = async (repoId: string, quantization: string) => {
|
||||
const spec = `${repoId}:${quantization}`;
|
||||
const dlKey = spec;
|
||||
setDownloading((prev) => new Set(prev).add(dlKey));
|
||||
try {
|
||||
const response = await downloadHfModel({
|
||||
body: { repo_id: repoId, filename },
|
||||
body: { spec },
|
||||
});
|
||||
if (response.data) {
|
||||
onDownloadStarted(response.data);
|
||||
@@ -256,10 +238,7 @@ export const HuggingFaceModelSearch = ({ onDownloadStarted }: Props) => {
|
||||
const isLoading = loadingFiles.has(repoId);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={model.id}
|
||||
className="border border-border-subtle rounded-lg overflow-hidden"
|
||||
>
|
||||
<div key={repoId} className="border border-border-subtle rounded-lg overflow-hidden">
|
||||
<button
|
||||
className="w-full flex items-center justify-between p-3 text-left hover:bg-background-subtle transition-colors"
|
||||
onClick={() => expandRepo(repoId)}
|
||||
@@ -268,7 +247,6 @@ export const HuggingFaceModelSearch = ({ onDownloadStarted }: Props) => {
|
||||
<span className="text-sm font-medium text-text-default truncate">{repoId}</span>
|
||||
<div className="flex items-center gap-3 text-xs text-text-muted">
|
||||
<span>{formatDownloads(model.downloads)} downloads</span>
|
||||
{model.likes > 0 && <span>❤️ {formatDownloads(model.likes)}</span>}
|
||||
</div>
|
||||
</div>
|
||||
{isExpanded ? (
|
||||
@@ -287,7 +265,7 @@ export const HuggingFaceModelSearch = ({ onDownloadStarted }: Props) => {
|
||||
</div>
|
||||
)}
|
||||
{variants.map((variant, idx) => {
|
||||
const dlKey = `${repoId}/${variant.filename}`;
|
||||
const dlKey = `${repoId}:${variant.quantization}`;
|
||||
const isStarting = downloading.has(dlKey);
|
||||
const isRecommended = idx === recommendedIndex;
|
||||
|
||||
@@ -308,6 +286,11 @@ export const HuggingFaceModelSearch = ({ onDownloadStarted }: Props) => {
|
||||
<span className="text-xs text-text-muted">
|
||||
{formatBytes(variant.size_bytes)}
|
||||
</span>
|
||||
{variant.description && (
|
||||
<span className="text-xs text-text-muted italic">
|
||||
{variant.description}
|
||||
</span>
|
||||
)}
|
||||
{isRecommended && (
|
||||
<span className="inline-flex items-center gap-1 text-xs bg-blue-500 text-white px-1.5 py-0.5 rounded">
|
||||
<Star className="w-3 h-3" />
|
||||
@@ -320,7 +303,7 @@ export const HuggingFaceModelSearch = ({ onDownloadStarted }: Props) => {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={isStarting}
|
||||
onClick={() => startDownload(repoId, variant.filename)}
|
||||
onClick={() => startDownload(repoId, variant.quantization)}
|
||||
>
|
||||
{isStarting ? (
|
||||
<Loader2 className="w-3 h-3 animate-spin" />
|
||||
|
||||
@@ -109,10 +109,7 @@ export const LocalInferenceSettings = () => {
|
||||
|
||||
try {
|
||||
await downloadHfModel({
|
||||
body: {
|
||||
repo_id: model.repo_id,
|
||||
filename: model.filename,
|
||||
},
|
||||
body: { spec: model.id },
|
||||
});
|
||||
pollDownloadProgress(modelId);
|
||||
scrollToDownloads();
|
||||
|
||||
Reference in New Issue
Block a user