feat: add SHA-256 search support and refresh HuggingFace author index

- Propagate sha256 through search orchestrator and providers
- Add hash-based lookup for CivitAI, HuggingFace, CivArchive, and LoRA Manager Archive
- Update search UI to accept SHA-256 hashes and refine link/name toggle styling
- Refresh HuggingFace author index metadata
This commit is contained in:
Dariusz L
2026-08-04 05:41:51 +02:00
parent 2bf3d4a071
commit 3f0e753edd
10 changed files with 2121 additions and 1797 deletions
+87 -59
View File
@@ -1,6 +1,7 @@
"""Request orchestration for model source searches."""
from ..routes.context import RouteContext
from ..type_utils import normalize_sha256
from .search_cache import SearchResultCache
from .search_dependencies import SearchDependencies
from .search_providers import (
@@ -135,7 +136,10 @@ class SearchOrchestrator:
"""Execute all requested sources and return a JSON response."""
try:
data = await request.json()
filename = data.get("filename", "")
filename = str(data.get("filename", "") or "").strip()
sha256 = normalize_sha256(
data.get("sha256") or data.get("hash") or data.get("file_hash")
)
category = data.get("category", "")
base_model_context = data.get("base_model_context", "")
progress_id = str(data.get("progress_id") or "").strip()
@@ -186,7 +190,7 @@ class SearchOrchestrator:
model_id = data.get("model_id")
version_id = data.get("version_id")
if not filename and not (is_urn and model_id and version_id):
if not filename and not sha256 and not (is_urn and model_id and version_id):
return self.web.json_response(
{
"error": (
@@ -250,6 +254,7 @@ class SearchOrchestrator:
hf_use_brave_fallback=hf_use_brave_fallback,
force_search=force_search,
normalized_sources=normalized_sources,
sha256=sha256,
)
raise_if_search_cancelled = (
self.provider_runner.raise_if_search_cancelled
@@ -303,6 +308,7 @@ class SearchOrchestrator:
else model_id or version_id
),
base=base_model_context,
sha256=sha256,
force=force_search,
)
)
@@ -319,6 +325,8 @@ class SearchOrchestrator:
"searched_sources": sorted(normalized_sources),
"source_errors": {},
"source_status": {},
"search_mode": "sha256" if sha256 else "name",
"search_sha256": sha256,
}
def iter_result_items(result):
@@ -333,65 +341,83 @@ class SearchOrchestrator:
matches = []
seen_match_paths = set()
seen_hashes = set()
for source_key in (
"huggingface",
"civitai",
"civarchive",
"lora_manager_archive",
"popular",
"model_list",
):
for source_result in iter_result_items(payload.get(source_key)):
raise_if_search_cancelled(search_request, source_key)
sha256 = self.extract_sha256_from_metadata(source_result)
if not sha256 or sha256 in seen_hashes:
continue
seen_hashes.add(sha256)
self.search_tracker.update(
progress_id,
progress_source,
"local_hash",
"Checking local metadata hashes",
94,
source_entries = []
if search_request.sha256:
source_entries.append(
(
"sha256_query",
{
"filename": filename,
"sha256": search_request.sha256,
},
)
try:
hash_matches = self.search_local_matches_by_hash(
sha256,
category=category or None,
max_matches=20,
force_rescan=force_search,
)
except Exception as hash_error:
self.logger.warning(
"Local metadata hash lookup failed "
f"for {source_key}:{sha256}: {hash_error}"
)
continue
)
source_entries.extend(
(
source_key,
source_result,
)
for source_key in (
"huggingface",
"civitai",
"civarchive",
"lora_manager_archive",
"popular",
"model_list",
)
for source_result in iter_result_items(payload.get(source_key))
)
for source_key, source_result in source_entries:
raise_if_search_cancelled(search_request, source_key)
sha256 = self.extract_sha256_from_metadata(source_result)
if not sha256 or sha256 in seen_hashes:
continue
seen_hashes.add(sha256)
for match in hash_matches:
model_path = (
match.get("model", {}).get("path")
or match.get("path")
or ""
)
path_key = model_path.lower()
if path_key and path_key in seen_match_paths:
continue
if path_key:
seen_match_paths.add(path_key)
matches.append(
{
**match,
"hash_lookup_source": source_key,
"hash_lookup_filename": (
source_result.get("filename")
or source_result.get("path")
or filename
),
"hash_lookup_sha256": sha256,
}
)
self.search_tracker.update(
progress_id,
progress_source,
"local_hash",
"Checking local metadata hashes",
94,
)
try:
hash_matches = self.search_local_matches_by_hash(
sha256,
category=category or None,
max_matches=20,
force_rescan=force_search,
)
except Exception as hash_error:
self.logger.warning(
"Local metadata hash lookup failed "
f"for {source_key}:{sha256}: {hash_error}"
)
continue
for match in hash_matches:
model_path = (
match.get("model", {}).get("path")
or match.get("path")
or ""
)
path_key = model_path.lower()
if path_key and path_key in seen_match_paths:
continue
if path_key:
seen_match_paths.add(path_key)
matches.append(
{
**match,
"hash_lookup_source": source_key,
"hash_lookup_filename": (
source_result.get("filename")
or source_result.get("path")
or filename
),
"hash_lookup_sha256": sha256,
}
)
if matches:
self.logger.info(
@@ -433,6 +459,8 @@ class SearchOrchestrator:
raise_if_search_cancelled(search_request, progress_source)
results["local_hash_matches"] = collect_local_hash_matches(results)
if results["local_hash_matches"]:
results["found"] = True
raise_if_search_cancelled(search_request, progress_source)
self.logger.info(
+26 -2
View File
@@ -33,6 +33,7 @@ class SearchRequest:
hf_use_brave_fallback: bool
force_search: bool
normalized_sources: frozenset[str]
sha256: str = ""
@property
def search_local(self):
@@ -226,6 +227,17 @@ class SearchProviderRunner:
def task():
source_results = {"popular": None, "model_list": None}
source_found = False
request_sha256 = getattr(request, "sha256", "")
if request_sha256:
self.owner.search_tracker.update(
request.progress_id,
"local",
"hash",
"Searching local models by SHA-256",
58,
)
return source_results, source_found
popular_info = self.owner.get_popular_model_url(request.filename)
self.owner.log_search_result("popular", popular_info)
@@ -289,6 +301,7 @@ class SearchProviderRunner:
def task():
hf_result = self.owner.search_huggingface_for_file(
request.filename,
sha256=getattr(request, "sha256", ""),
token=request.hf_token or None,
brave_api_key=request.brave_search_api_key or None,
use_api_search=request.hf_use_api_search,
@@ -435,6 +448,7 @@ class SearchProviderRunner:
"civitai",
lambda base_ctx, callback: self.owner.search_civitai_for_file(
request.filename,
sha256=getattr(request, "sha256", ""),
api_key=request.civitai_key or None,
model_type=request.category,
base_model_context=base_ctx,
@@ -508,6 +522,7 @@ class SearchProviderRunner:
"civarchive",
lambda base_ctx, callback: self.owner.search_civarchive_for_file(
request.filename,
sha256=getattr(request, "sha256", ""),
model_type=request.category,
base_model_context=base_ctx,
limit=request.civarchive_candidate_limit,
@@ -589,6 +604,7 @@ class SearchProviderRunner:
"lora_manager_archive",
lambda base_ctx, callback: self.owner.search_lora_manager_archive_for_file(
request.filename,
sha256=getattr(request, "sha256", ""),
model_type=request.category,
base_model_context=base_ctx,
progress_callback=callback,
@@ -626,7 +642,13 @@ class SearchProviderRunner:
)
)
if request.search_civarchive_source and (
request.filename or (request.is_urn and request.data.get("model_id") and request.data.get("version_id"))
request.filename
or getattr(request, "sha256", "")
or (
request.is_urn
and request.data.get("model_id")
and request.data.get("version_id")
)
):
tasks.append(
self.owner.asyncio.to_thread(
@@ -634,7 +656,9 @@ class SearchProviderRunner:
request,
)
)
if request.search_lora_manager_archive_source and request.filename:
if request.search_lora_manager_archive_source and (
request.filename or getattr(request, "sha256", "")
):
tasks.append(
self.owner.asyncio.to_thread(
self.search_lora_manager_archive_source_task,
+8 -6
View File
@@ -2137,19 +2137,21 @@ def search_civarchive_for_file(
exact_only: bool = False,
limit: int = DEFAULT_CIVARCHIVE_CANDIDATE_LIMIT,
progress_callback: Optional[Callable[[Dict[str, Any]], None]] = None,
sha256: Optional[str] = None,
) -> Optional[Dict[str, Any]]:
"""
Search CivArchive for the best downloadable match for a model filename.
"""
normalized_filename = (filename or "").strip()
if not normalized_filename:
requested_sha256 = _extract_sha256(sha256 or "")
if not normalized_filename and not requested_sha256:
return None
limit = max(1, min(int(limit), MAX_CIVARCHIVE_CANDIDATE_LIMIT))
detail_limit = min(limit, SEARCH_RESULT_DETAIL_LIMIT)
base_model_key = _normalize_base_model(base_model_context or "")
cache_key = (
f"file::{normalized_filename.lower()}::{model_type or ''}::{base_model_key}::{exact_only}::{limit}"
f"file::{normalized_filename.lower()}::hash::{requested_sha256}::{model_type or ''}::{base_model_key}::{exact_only}::{limit}"
)
if cache_key in _search_cache:
_report_progress(
@@ -2160,17 +2162,17 @@ def search_civarchive_for_file(
)
return _search_cache[cache_key]
sha256 = _extract_sha256(normalized_filename)
if sha256:
hash_query = requested_sha256 or _extract_sha256(normalized_filename)
if hash_query:
_report_progress(
progress_callback,
"hash",
"Resolving CivArchive hash",
40,
sha256=sha256,
sha256=hash_query,
)
result = resolve_civarchive_by_hash(
sha256,
hash_query,
query=normalized_filename,
exact_only=exact_only,
model_type=model_type,
+85 -1
View File
@@ -49,6 +49,7 @@ from ..type_utils import (
first_non_empty,
normalize_model_file_info,
normalize_model_image,
normalize_sha256,
parse_size_to_bytes,
resolve_model_category,
select_primary_model_file,
@@ -879,6 +880,7 @@ def search_civitai_for_file(
use_api_search: bool = True,
use_html_fallback: bool = True,
progress_callback: Optional[Callable[[Dict[str, Any]], None]] = None,
sha256: Optional[str] = None,
) -> Optional[Dict[str, Any]]:
"""
Search CivitAI for a specific model file.
@@ -896,6 +898,7 @@ def search_civitai_for_file(
global _search_cache
candidate_limit = max(1, min(int(candidate_limit), MAX_CIVITAI_CANDIDATE_LIMIT))
requested_sha256 = normalize_sha256(sha256)
session_key = "session" if session_token else "anon"
model_type_key = str(model_type or "").lower()
base_model_key = _normalize_base_model(base_model_context or "")
@@ -905,7 +908,7 @@ def search_civitai_for_file(
f"_html{int(bool(use_html_fallback))}"
)
cache_key = (
f"civit_{filename}_exact{exact_only}_type{model_type_key}_base{base_model_key}_session{session_key}_limit{candidate_limit}_{methods_key}"
f"civit_{filename}_sha{requested_sha256}_exact{exact_only}_type{model_type_key}_base{base_model_key}_session{session_key}_limit{candidate_limit}_{methods_key}"
)
if cache_key in _search_cache:
log.debug(f"CivitAI search cache hit for {filename} (exact_only={exact_only})")
@@ -918,6 +921,61 @@ def search_civitai_for_file(
return _search_cache[cache_key]
try:
if requested_sha256:
_report_progress(
progress_callback,
"hash",
"Resolving CivitAI SHA-256",
62,
sha256=requested_sha256,
)
hash_result = get_model_info_by_hash(
requested_sha256,
api_key=api_key,
)
if hash_result:
result = build_unified_search_result(
"civitai",
model_id=hash_result.get("model_id"),
version_id=hash_result.get("version_id"),
name=hash_result.get("model_name") or hash_result.get("name") or "",
version_name=hash_result.get("version_name") or "",
type=hash_result.get("model_type") or hash_result.get("type") or "",
filename=hash_result.get("filename") or filename,
url=hash_result.get("version_url") or hash_result.get("url") or "",
download_url=hash_result.get("download_url"),
size=hash_result.get("size"),
base_model=hash_result.get("base_model"),
tags=hash_result.get("tags") or [],
trained_words=hash_result.get("trained_words") or [],
images=hash_result.get("images") or [],
match_type="hash",
confidence=100.0,
sha256=requested_sha256,
hashes=hash_result.get("hashes") or {"SHA256": requested_sha256},
description=hash_result.get("description") or "",
model_description=hash_result.get("model_description") or "",
)
_search_cache[cache_key] = result
_report_progress(
progress_callback,
"found",
"Found exact CivitAI SHA-256 match",
92,
confidence=100.0,
)
return result
_search_cache[cache_key] = None
_report_progress(
progress_callback,
"done",
"CivitAI SHA-256 not found",
92,
found=False,
)
return None
# Prefer the CivitAI.red tRPC search because it is much closer to what
# the browser search UI returns than the broad public /models API.
if use_trpc_search:
@@ -1504,6 +1562,7 @@ def get_model_info_by_hash(
"""
global _hash_cache
file_hash = normalize_sha256(file_hash)
if not file_hash:
return None
@@ -1546,6 +1605,9 @@ def get_model_info_by_hash(
"version_id": version_info.get("id"),
"version_name": version_info.get("name"),
"sha256": file_hash,
"filename": "",
"size": None,
"hashes": {},
"url": f"https://civitai.com/models/{model_id}" if model_id else None,
"version_url": f"https://civitai.com/models/{model_id}?modelVersionId={version_info.get('id')}"
if model_id
@@ -1560,6 +1622,28 @@ def get_model_info_by_hash(
"model_description": model_info.get("description", ""),
}
files = version_info.get("files") or []
matching_file = next(
(
file_info
for file_info in files
if isinstance(file_info, dict)
and normalize_sha256(
file_info.get("sha256")
or (file_info.get("hashes") or {}).get("SHA256")
or (file_info.get("hashes") or {}).get("sha256")
)
== file_hash.lower()
),
None,
)
if matching_file:
result["filename"] = matching_file.get("name") or matching_file.get("filename") or ""
result["size"] = matching_file.get("size")
if result["size"] is None and matching_file.get("sizeKB") is not None:
result["size"] = matching_file.get("sizeKB") * 1024
result["hashes"] = matching_file.get("hashes") or {}
result = _enrich_model_info_with_details(
result,
model_id,
+21 -5
View File
@@ -896,6 +896,7 @@ def search_huggingface_for_file(
use_brave_fallback: bool = True,
force_refresh: bool = False,
progress_callback: Optional[Callable[[Dict[str, Any]], None]] = None,
sha256: Optional[str] = None,
) -> Optional[Dict[str, Any]]:
"""
Search HuggingFace for a specific model file.
@@ -912,6 +913,7 @@ def search_huggingface_for_file(
"""
global _search_cache
requested_sha256 = normalize_sha256(sha256)
brave_key = "brave" if brave_api_key and use_brave_fallback else "nobrave"
token_key = "token" if token else "notoken"
methods_key = (
@@ -920,7 +922,7 @@ def search_huggingface_for_file(
f"brave{int(bool(use_brave_fallback))}_"
f"force{int(bool(force_refresh))}"
)
cache_key = f"hf_{filename}_exact{exact_only}_{token_key}_{brave_key}_{methods_key}"
cache_key = f"hf_{filename}_sha{requested_sha256}_exact{exact_only}_{token_key}_{brave_key}_{methods_key}"
if cache_key in _search_cache:
log.debug(f"HuggingFace cache hit file={filename} exact={exact_only}")
_report_progress(
@@ -932,6 +934,16 @@ def search_huggingface_for_file(
return _search_cache[cache_key]
try:
def result_matches_requested_hash(result: Optional[Dict[str, Any]]) -> bool:
if not requested_sha256:
return True
return normalize_sha256(
result.get("sha256")
or result.get("hash")
or (result.get("hashes") or {}).get("SHA256")
or (result.get("hashes") or {}).get("sha256")
) == requested_sha256
headers = {}
if token:
headers["Authorization"] = f"Bearer {token}"
@@ -1015,7 +1027,7 @@ def search_huggingface_for_file(
exact_only=exact_only,
headers=headers,
)
if result:
if result and result_matches_requested_hash(result):
_search_cache[cache_key] = result
_report_progress(
progress_callback,
@@ -1057,7 +1069,7 @@ def search_huggingface_for_file(
headers={},
)
author_fallback_repo_count += int(index.get("repo_count") or 0)
if result:
if result and result_matches_requested_hash(result):
_search_cache[cache_key] = result
_report_progress(
progress_callback,
@@ -1117,7 +1129,7 @@ def search_huggingface_for_file(
exact_only=exact_only,
headers=headers,
)
if result:
if result and result_matches_requested_hash(result):
_search_cache[cache_key] = result
_report_progress(
progress_callback,
@@ -1178,7 +1190,11 @@ def search_huggingface_for_file(
exact_only=True,
headers=headers,
)
if result and result.get("filename", "").lower() == filename.lower():
if (
result
and result.get("filename", "").lower() == filename.lower()
and result_matches_requested_hash(result)
):
_search_cache[cache_key] = result
_report_progress(
progress_callback,
+69
View File
@@ -32,6 +32,7 @@ from ..type_utils import (
build_search_result,
get_generic_filename_tokens,
normalize_lora_manager_type,
normalize_sha256,
select_primary_model_file,
)
@@ -678,10 +679,78 @@ def search_lora_manager_archive_for_file(
exact_only: bool = False,
limit: int = 10,
progress_callback: Optional[Callable[[Dict[str, Any]], None]] = None,
sha256: Optional[str] = None,
) -> Optional[Dict[str, Any]]:
"""
Search the archive for the best model/version match for a filename.
"""
requested_sha256 = normalize_sha256(sha256)
if requested_sha256:
conn = _connect_readonly()
if not conn:
return None
try:
rows = conn.execute(
"""
SELECT
m.id AS model_id,
m.name AS model_name,
m.type AS model_type,
v.id AS version_id,
v.name AS version_name,
v.base_model AS base_model,
v.position AS position
FROM models m
JOIN model_versions v ON v.model_id = m.id
ORDER BY m.id ASC, COALESCE(v.position, 999999) ASC
"""
).fetchall()
for row in rows:
files = _load_version_files(conn, row["version_id"])
matching_file = next(
(
file_info
for file_info in files
if normalize_sha256(
file_info.get("sha256")
or (file_info.get("hashes") or {}).get("SHA256")
or (file_info.get("hashes") or {}).get("sha256")
)
== requested_sha256
),
None,
)
if not matching_file:
continue
result = _build_result_from_row(conn, row)
result.update(
{
"filename": matching_file.get("name") or result.get("filename", ""),
"download_url": matching_file.get("downloadUrl") or result.get("download_url"),
"size": (
int((matching_file.get("sizeKB") or 0) * 1024)
if matching_file.get("sizeKB") is not None
else result.get("size")
),
"sha256": requested_sha256,
"hashes": matching_file.get("hashes") or {"SHA256": requested_sha256},
"match_type": "hash",
"confidence": 100.0,
}
)
_report_progress(
progress_callback,
"found",
"Found LoRA archive SHA-256 match",
92,
confidence=100.0,
)
return result
finally:
conn.close()
return None
search_query = os.path.splitext(get_filename_from_path(filename))[0]
if not search_query:
return None
File diff suppressed because it is too large Load Diff
+20 -14
View File
@@ -2471,7 +2471,9 @@
justify-content: center;
width: 24px;
height: 24px;
background-color: var(--mr-missing-control-bg-hover, rgba(255, 255, 255, 0.06));
box-sizing: border-box;
background-color: var(--mr-missing-control-bg, rgba(255, 255, 255, 0.035));
border: 1px solid var(--mr-missing-control-border, rgba(255, 255, 255, 0.105));
border-radius: var(--mr-missing-control-radius, 7px);
box-shadow: var(--mr-missing-control-shadow, none);
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
@@ -2479,7 +2481,8 @@
}
.mr-link-name-switch:hover .mr-switch-knob {
background-color: var(--mr-missing-control-border-hover, rgba(255, 255, 255, 0.17));
background-color: var(--mr-missing-control-bg-hover, rgba(255, 255, 255, 0.06));
border-color: var(--mr-missing-control-border-hover, rgba(255, 255, 255, 0.17));
}
.mr-link-name-switch .mr-switch-labels {
@@ -2589,12 +2592,13 @@
.mr-link-name-action {
display: inline-flex;
align-items: center;
justify-content: space-between;
gap: var(--mr-spacing-7);
justify-content: center;
gap: var(--mr-spacing-8);
height: var(--mr-download-control-height);
min-height: var(--mr-download-control-height);
min-width: 108px;
padding: 0 var(--mr-spacing-12);
width: 100%;
min-width: 82px;
padding: 0 var(--mr-spacing-10);
color: var(--mr-text);
background: var(--mr-missing-control-bg, rgba(255, 255, 255, 0.035));
border: 1px solid var(--mr-missing-control-border, rgba(255, 255, 255, 0.105));
@@ -2613,7 +2617,8 @@
grid-template-columns: minmax(0, 1fr);
align-items: start;
gap: var(--mr-spacing-6);
min-width: 108px;
min-width: 82px;
max-width: 82px;
width: 100%;
}
@@ -2627,15 +2632,15 @@
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 16px;
width: 16px;
height: 16px;
flex: 0 0 var(--mr-font-base);
width: var(--mr-font-base);
height: var(--mr-font-base);
}
.mr-link-name-action-icon svg {
display: block;
width: 16px;
height: 16px;
width: var(--mr-font-base);
height: var(--mr-font-base);
}
.mr-link-name-action:hover:not(:disabled) {
@@ -9572,10 +9577,11 @@ a.mr-image-preview-resource-name:hover {
}
#model-resolver-modal .mr-missing-detail-pane .mr-download-section .mr-link-name-action {
min-width: 108px;
min-width: 82px;
width: 82px;
min-height: var(--mr-missing-field-height, 38px);
height: var(--mr-missing-field-height, 38px);
padding: 0 var(--mr-spacing-12);
padding: 0 var(--mr-spacing-10);
color: var(--mr-text);
background: var(--mr-missing-control-bg, rgba(255, 255, 255, 0.035));
border-color: var(--mr-missing-control-border, rgba(255, 255, 255, 0.105));
@@ -2265,13 +2265,15 @@ export const resolveDownloadMethods = {
*/
async searchOnline(missing, { workflowKey = this.getWorkflowScopedQueueKey(), forceSearch = false, source = null, searchQuery = '' } = {}) {
const providedSearchQuery = String(searchQuery || '').trim();
const searchSha256 = normalizeSha256(providedSearchQuery);
const state = this.getSearchStateForWorkflow(workflowKey, missing);
const requestedSearchQuery = providedSearchQuery;
const requestedSearchQuery = searchSha256 ? '' : providedSearchQuery;
let filename = requestedSearchQuery || this.getFilenameFromPath(missing.original_path);
let category = missing.category || this.getNodeTypeDownloadCategory?.(missing.node_type) || '';
if (providedSearchQuery) {
state.inputMode = 'name';
state.manualSearchQuery = providedSearchQuery;
state.lastSearchMode = searchSha256 ? 'sha256' : 'name';
this.persistSearchStateForWorkflow(workflowKey, missing, state);
}
const missingSearchKey = this.getMissingSearchKey(missing);
@@ -2297,7 +2299,7 @@ export const resolveDownloadMethods = {
// For URNs, use the resolved file/model name for searching instead of the URN itself
// and pass the URN type as category (CivitAI expects specific type names)
if (missing.is_urn && !requestedSearchQuery) {
if (missing.is_urn && !requestedSearchQuery && !searchSha256) {
const urnSearchName = missing.civitai_info?.expected_filename
|| missing.download_source?.filename
|| missing.civitai_info?.model_name;
@@ -2324,7 +2326,7 @@ export const resolveDownloadMethods = {
}
}
const isUrn = Boolean(missing.is_urn && !requestedSearchQuery);
const isUrn = Boolean(missing.is_urn && !requestedSearchQuery && !searchSha256);
const resultsId = `search-results-${missing.node_id}-${missing.widget_index}`;
const canUpdateCurrentWorkflow = workflowKey === this.getWorkflowScopedQueueKey();
const resultsDiv = canUpdateCurrentWorkflow
@@ -2404,6 +2406,8 @@ export const resolveDownloadMethods = {
const tokens = this.getStoredTokens();
const baseSearchData = {
filename,
sha256: searchSha256,
search_mode: searchSha256 ? 'sha256' : 'name',
category,
is_urn: isUrn,
base_model_context: baseModelContext,
+3 -3
View File
@@ -412,7 +412,7 @@ export const searchPanelMethods = {
? this.getSearchButtonLabelLines(buttonText).join(' ')
: String(buttonText || 'Add');
const icon = mode === 'name' ? getSvgIcon('search') : getSvgIcon('link');
return `<span class="mr-link-name-action-text">${this.escapeHtml(label)}</span><span class="mr-link-name-action-icon" aria-hidden="true">${icon}</span>`;
return `<span class="mr-btn-icon mr-link-name-action-icon" aria-hidden="true">${icon}</span><span class="mr-link-name-action-text">${this.escapeHtml(label)}</span>`;
},
renderLinkNameSwitchIcon(mode = 'link') {
@@ -454,11 +454,11 @@ export const searchPanelMethods = {
const actionHeading = control.querySelector('.mr-link-name-action-heading');
if (actionHeading) actionHeading.textContent = mode === 'name' ? 'Search' : 'Link';
if (input) {
input.placeholder = mode === 'name' ? 'Search by model name...' : 'https://...';
input.placeholder = mode === 'name' ? 'Model name or SHA-256...' : 'https://...';
if (mode === 'name' && document.activeElement !== input) {
input.value = String(currentState.manualSearchQuery || '');
}
input.setAttribute('aria-label', mode === 'name' ? 'Model name' : 'Model URL');
input.setAttribute('aria-label', mode === 'name' ? 'Model name or SHA-256 hash' : 'Model URL');
}
if (actionButton) {
const buttonText = mode === 'link'