feat: improve model scan cache override, combo UX, and filename handling
- Add force_rescan end-to-end so callers can bypass the short-lived local model scan cache: backend entry in __init__.py and core/resolver.py, lifecycle_graph_methods forwards it through the workflow analysis body, and download_target_methods passes ?force=1 from the refresh flow. - Rewrite combo dropdown lifecycle in missing_browser_methods with show/hide helpers, pointer-capture tracking, and a capture-phase outside-click listener so the list stays open during drag/hover and closes reliably otherwise. - Animate refresh buttons (CSS keyframes + JS Web Animations) with reduced-motion fallback. - Encode selected model path into the selected-bar data-model attribute and wire contextmenu so users can open the containing folder from the selection. - Fix XSS risk in selected-bar label by escaping label HTML; change queue/selected Remove buttons to danger style. - Increase max missing-browser model column width clamp from 430 to 820 and stop truncating missing-filename display; show full filename with tooltip instead. - Update huggingface-author-index.json metadata timestamp/counts (1 file changed, 1 line).
This commit is contained in:
+13
-1
@@ -207,6 +207,12 @@ class ModelResolverExtension:
|
||||
data = await request.json()
|
||||
workflow_json = data.get("workflow")
|
||||
analysis_id = str(data.get("analysis_id") or "").strip()
|
||||
force_rescan = data.get("force_rescan", False)
|
||||
force_rescan = (
|
||||
force_rescan
|
||||
if isinstance(force_rescan, bool)
|
||||
else str(force_rescan).lower() in {"1", "true", "yes"}
|
||||
)
|
||||
|
||||
if not workflow_json:
|
||||
return web.json_response(
|
||||
@@ -240,6 +246,7 @@ class ModelResolverExtension:
|
||||
0.0,
|
||||
10,
|
||||
update_analysis_progress if analysis_id else None,
|
||||
force_rescan=force_rescan,
|
||||
)
|
||||
|
||||
# Filter out LoraManager lorAs that already exist locally (exists=True)
|
||||
@@ -529,7 +536,12 @@ class ModelResolverExtension:
|
||||
async def get_models(request):
|
||||
"""Get list of all available models."""
|
||||
try:
|
||||
models = get_model_files()
|
||||
force_rescan = str(
|
||||
request.query.get("force")
|
||||
or request.query.get("force_rescan")
|
||||
or ""
|
||||
).lower() in {"1", "true", "yes"}
|
||||
models = get_model_files(force_rescan=force_rescan)
|
||||
return web.json_response(models)
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
|
||||
+3
-1
@@ -326,6 +326,7 @@ def analyze_and_find_matches(
|
||||
similarity_threshold: float = 0.0,
|
||||
max_matches_per_model: int = 10,
|
||||
progress_callback: Optional[Callable[[Dict[str, Any]], None]] = None,
|
||||
force_rescan: bool = False,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Main entry point: analyze workflow and find matches for missing models.
|
||||
@@ -334,6 +335,7 @@ def analyze_and_find_matches(
|
||||
workflow_json: Complete workflow JSON dictionary
|
||||
similarity_threshold: Minimum similarity score (0.0 to 1.0) for matches
|
||||
max_matches_per_model: Maximum number of matches to return per missing model
|
||||
force_rescan: If True, bypass the short-lived local model scan cache
|
||||
|
||||
Returns:
|
||||
Dictionary with analysis results:
|
||||
@@ -389,7 +391,7 @@ def analyze_and_find_matches(
|
||||
|
||||
# Analyze workflow to find all model references
|
||||
# Get available models
|
||||
available_models = get_model_files()
|
||||
available_models = get_model_files(force_rescan=force_rescan)
|
||||
available_models_by_category = {}
|
||||
for model in available_models:
|
||||
model_category = model.get("category", "")
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1011,6 +1011,7 @@
|
||||
font-weight: 700;
|
||||
color: var(--mr-text);
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
word-break: break-word;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
@@ -1024,7 +1025,10 @@
|
||||
}
|
||||
.mr-card-title-primary {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
color: var(--mr-text-soft);
|
||||
overflow-wrap: anywhere;
|
||||
word-break: normal;
|
||||
}
|
||||
.mr-card-title-secondary {
|
||||
display: block;
|
||||
@@ -1577,6 +1581,33 @@
|
||||
border-color: var(--mr-missing-control-focus-border, rgba(114,211,138,0.30));
|
||||
box-shadow: 0 0 0 2px var(--mr-missing-control-focus-ring, rgba(114,211,138,0.08));
|
||||
}
|
||||
@keyframes mr-refresh-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.mr-refresh-spin-target,
|
||||
#model-resolver-modal .mr-refresh-spin-target {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 0;
|
||||
}
|
||||
.mr-btn.mr-is-refreshing > svg,
|
||||
#model-resolver-modal .mr-btn.mr-is-refreshing > svg,
|
||||
.mr-btn.mr-is-refreshing .mr-refresh-spin-target,
|
||||
#model-resolver-modal .mr-btn.mr-is-refreshing .mr-refresh-spin-target {
|
||||
animation: mr-refresh-spin 0.62s linear infinite;
|
||||
transform-origin: center;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.mr-btn.mr-is-refreshing > svg,
|
||||
#model-resolver-modal .mr-btn.mr-is-refreshing > svg,
|
||||
.mr-btn.mr-is-refreshing .mr-refresh-spin-target,
|
||||
#model-resolver-modal .mr-btn.mr-is-refreshing .mr-refresh-spin-target {
|
||||
animation: mr-refresh-spin 0.9s linear 1;
|
||||
}
|
||||
}
|
||||
.mr-btn-primary,
|
||||
#model-resolver-modal .mr-btn-primary {
|
||||
background: var(--mr-accent);
|
||||
@@ -4591,6 +4622,10 @@
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
#model-resolver-modal .mr-options-shell .mr-btn.mr-btn-primary,
|
||||
#model-resolver-modal .mr-options-shell .mr-btn.mr-btn-primary:hover:not(:disabled) {
|
||||
color: #101010;
|
||||
}
|
||||
.mr-options-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -5690,6 +5725,16 @@
|
||||
border-color: rgba(47, 128, 237, 0.48);
|
||||
color: #ffffff;
|
||||
}
|
||||
#model-resolver-modal .mr-missing-detail-pane .model-resolver-selected .mr-btn-danger {
|
||||
background: rgba(239, 68, 68, 0.16);
|
||||
border-color: rgba(239, 68, 68, 0.34);
|
||||
color: #fee2e2;
|
||||
}
|
||||
#model-resolver-modal .mr-missing-detail-pane .model-resolver-selected .mr-btn-danger:hover:not(:disabled) {
|
||||
background: rgba(239, 68, 68, 0.24);
|
||||
border-color: rgba(239, 68, 68, 0.48);
|
||||
color: #ffffff;
|
||||
}
|
||||
#model-resolver-modal .mr-missing-detail-pane .mr-search-progress-list-compact {
|
||||
gap: 7px;
|
||||
margin: 10px 0 8px;
|
||||
|
||||
@@ -176,7 +176,7 @@ export const queueMethods = {
|
||||
html += `<div class="mr-queue-item-title">${this.escapeHtml(nodeLabel)} #${this.escapeHtml(String(r.node_id))}</div>`;
|
||||
html += `<div class="mr-queue-item-meta"><span>Original</span><code>${this.escapeHtml(orig)}</code></div>`;
|
||||
html += `<div class="mr-queue-item-selection"><span>Selected</span><code>${this.escapeHtml(label)}</code></div>`;
|
||||
html += `<div class="mr-queue-item-actions"><button id="${rmId}" class="mr-btn mr-btn-secondary mr-btn-sm">Remove</button></div>`;
|
||||
html += `<div class="mr-queue-item-actions"><button id="${rmId}" class="mr-btn mr-btn-danger mr-btn-sm">Remove</button></div>`;
|
||||
html += `</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
@@ -338,20 +338,49 @@ export const queueMethods = {
|
||||
if (!selection) {
|
||||
selectedBar.style.display = 'none';
|
||||
selectedBar.innerHTML = '';
|
||||
selectedBar.removeAttribute('data-model');
|
||||
selectedBar.removeAttribute('data-tooltip');
|
||||
selectedBar.oncontextmenu = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Build selected bar content
|
||||
const label = selection.resolved_model?.relative_path || selection.resolved_model?.filename || selection.resolved_path || '';
|
||||
const selectedPath = selection.resolved_model?.path || selection.resolved_path || '';
|
||||
const selectedContext = selectedPath
|
||||
? {
|
||||
context_scope: 'download_folder',
|
||||
open_folder_label: 'Open Containing Folder',
|
||||
name: selection.resolved_model?.filename || label,
|
||||
path: selectedPath,
|
||||
resolved_path: selectedPath,
|
||||
category: selection.category,
|
||||
original_path: selection.original_path
|
||||
}
|
||||
: null;
|
||||
const selectedContextAttrs = selectedContext
|
||||
? ` data-model="${this.escapeHtml(encodeURIComponent(JSON.stringify(selectedContext)))}" oncontextmenu="window.MLOpenContextMenu(event, this)" data-tooltip="Right-click to open containing folder"`
|
||||
: '';
|
||||
if (selectedContext) {
|
||||
selectedBar.dataset.model = encodeURIComponent(JSON.stringify(selectedContext));
|
||||
selectedBar.dataset.tooltip = 'Right-click to open containing folder';
|
||||
selectedBar.oncontextmenu = (event) => {
|
||||
window.MLOpenContextMenu?.(event, selectedBar);
|
||||
};
|
||||
} else {
|
||||
selectedBar.removeAttribute('data-model');
|
||||
selectedBar.removeAttribute('data-tooltip');
|
||||
selectedBar.oncontextmenu = null;
|
||||
}
|
||||
const applyBtnId = `selected-apply-${nodeId}-${widgetIndex}`;
|
||||
const removeBtnId = `selected-remove-${nodeId}-${widgetIndex}`;
|
||||
|
||||
selectedBar.innerHTML = `<div class="mr-selected-bar-inner">`;
|
||||
selectedBar.innerHTML = `<div class="mr-selected-bar-inner"${selectedContextAttrs}>`;
|
||||
selectedBar.innerHTML += `<span class="mr-selected-label">✓ Selected:</span>`;
|
||||
selectedBar.innerHTML += `<code class="mr-selected-code">${label}</code>`;
|
||||
selectedBar.innerHTML += `<code class="mr-selected-code">${this.escapeHtml(label)}</code>`;
|
||||
selectedBar.innerHTML += `<span class="mr-selected-actions">`;
|
||||
selectedBar.innerHTML += `<button id="${applyBtnId}" class="mr-btn mr-btn-primary mr-btn-sm" data-tooltip="Apply this selected local match to the workflow.">Apply</button>`;
|
||||
selectedBar.innerHTML += `<button id="${removeBtnId}" class="mr-btn mr-btn-secondary mr-btn-sm">Remove</button>`;
|
||||
selectedBar.innerHTML += `<button id="${removeBtnId}" class="mr-btn mr-btn-danger mr-btn-sm">Remove</button>`;
|
||||
selectedBar.innerHTML += `</span>`;
|
||||
selectedBar.innerHTML += `</div>`;
|
||||
selectedBar.style.display = 'block';
|
||||
|
||||
@@ -8,10 +8,11 @@ export const downloadTargetMethods = {
|
||||
/**
|
||||
* Ensure all models are loaded for the dropdown.
|
||||
*/
|
||||
async ensureAllModelsLoaded() {
|
||||
if (this.allModels && this.allModels.length) return;
|
||||
async ensureAllModelsLoaded(options = {}) {
|
||||
const force = options === true || Boolean(options?.force);
|
||||
if (!force && this.allModels && this.allModels.length) return;
|
||||
try {
|
||||
const resp = await api.fetchApi('/model_resolver/models');
|
||||
const resp = await api.fetchApi(`/model_resolver/models${force ? '?force=1' : ''}`);
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||
const models = await resp.json();
|
||||
const list = Array.isArray(models) ? models : [];
|
||||
|
||||
@@ -113,7 +113,7 @@ export const lifecycleGraphMethods = {
|
||||
/**
|
||||
* Load workflow data and display missing models
|
||||
*/
|
||||
async loadWorkflowData(workflow = null, { force = false } = {}) {
|
||||
async loadWorkflowData(workflow = null, { force = false, forceRescan = force } = {}) {
|
||||
if (!this.contentElement) return;
|
||||
|
||||
this._workflowDataLoadToken = null;
|
||||
@@ -180,7 +180,7 @@ export const lifecycleGraphMethods = {
|
||||
const response = await api.fetchApi('/model_resolver/analyze', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ workflow, analysis_id: analysisId })
|
||||
body: JSON.stringify({ workflow, analysis_id: analysisId, force_rescan: Boolean(forceRescan) })
|
||||
});
|
||||
if (this._analysisProgressToken === analysisId) {
|
||||
this._analysisProgressToken = null;
|
||||
|
||||
@@ -218,7 +218,7 @@ export const missingBrowserMethods = {
|
||||
}
|
||||
|
||||
return {
|
||||
modelPx: clamp(modelPx, 180, 430),
|
||||
modelPx: clamp(modelPx, 180, 820),
|
||||
typePx: clamp(typePx, 66, 120)
|
||||
};
|
||||
},
|
||||
@@ -255,7 +255,7 @@ export const missingBrowserMethods = {
|
||||
<span class="mr-missing-stat mr-missing-stat-none">${stats.none} no match</span>
|
||||
</div>
|
||||
<button id="mr-refresh-missing-analysis" type="button" class="mr-btn mr-btn-secondary mr-btn-sm mr-missing-refresh-btn" data-tooltip="Re-analyze workflow and refresh local matches">
|
||||
${getSvgIcon('refreshCw')} Refresh
|
||||
<span class="mr-refresh-spin-target">${getSvgIcon('refreshCw')}</span> Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -278,7 +278,6 @@ export const missingBrowserMethods = {
|
||||
const isSelected = key === selectedKey;
|
||||
const isBatchSelected = this.batchSelectedMissingKeys?.has(key);
|
||||
const filename = this.getMissingFilename(missing);
|
||||
const formattedFilename = this.formatFilename(filename, 46);
|
||||
const bestMatch = this.getBestLocalMatch(missing, 70);
|
||||
const confidence = bestMatch ? Number(bestMatch.confidence || 0) : 0;
|
||||
const matchName = bestMatch?.model?.relative_path || bestMatch?.filename || bestMatch?.path || '';
|
||||
@@ -302,7 +301,7 @@ export const missingBrowserMethods = {
|
||||
</span>
|
||||
<span class="mr-missing-row-index">${index + 1}</span>
|
||||
<span class="mr-missing-row-model">
|
||||
<span class="mr-missing-row-name" data-tooltip="${this.escapeHtml(filename)}">${this.escapeHtml(formattedFilename.display)}</span>
|
||||
<span class="mr-missing-row-name" data-tooltip="${this.escapeHtml(filename)}">${this.escapeHtml(filename)}</span>
|
||||
${rowNodeHtml}
|
||||
</span>
|
||||
<span class="mr-missing-row-type ${typeColorClass}">${this.escapeHtml(typeLabel)}</span>
|
||||
@@ -598,17 +597,62 @@ export const missingBrowserMethods = {
|
||||
const comboInput = container.querySelector(`#combo-input-${comboId}`);
|
||||
const comboList = container.querySelector(`#combo-list-${comboId}`);
|
||||
const comboRefresh = container.querySelector(`#combo-refresh-${comboId}`);
|
||||
const comboSection = comboInput?.closest('.mr-combo-section')
|
||||
|| comboList?.closest('.mr-combo-section')
|
||||
|| comboRefresh?.closest('.mr-combo-section');
|
||||
|
||||
if (comboList) {
|
||||
this.enableWheelScrollChaining(comboList);
|
||||
}
|
||||
|
||||
const allModels = Array.isArray(this.allModels) ? this.allModels : [];
|
||||
const getAllModels = () => Array.isArray(this.allModels) ? this.allModels : [];
|
||||
const buildLabel = (m) => `${m.category ? m.category + ': ' : ''}${m.relative_path || m.filename || ''}`;
|
||||
const getFolder = (m) => m.path || m.base_directory || '';
|
||||
let comboListPointerActive = false;
|
||||
let outsideComboPointerHandler = null;
|
||||
const isInsideCombo = (target) => (
|
||||
Boolean(target) && (
|
||||
Boolean(comboSection?.contains(target))
|
||||
|| Boolean(comboInput?.contains(target))
|
||||
|| Boolean(comboList?.contains(target))
|
||||
|| Boolean(comboRefresh?.contains(target))
|
||||
)
|
||||
);
|
||||
const stopOutsideComboPointerListener = () => {
|
||||
if (!outsideComboPointerHandler) return;
|
||||
window.removeEventListener('pointerdown', outsideComboPointerHandler, true);
|
||||
outsideComboPointerHandler = null;
|
||||
};
|
||||
const startOutsideComboPointerListener = () => {
|
||||
if (outsideComboPointerHandler) return;
|
||||
outsideComboPointerHandler = (event) => {
|
||||
if (!comboList?.isConnected) {
|
||||
stopOutsideComboPointerListener();
|
||||
return;
|
||||
}
|
||||
if (isInsideCombo(event.target)) {
|
||||
return;
|
||||
}
|
||||
hideComboList();
|
||||
};
|
||||
window.addEventListener('pointerdown', outsideComboPointerHandler, true);
|
||||
};
|
||||
const showComboList = () => {
|
||||
if (!comboList) return;
|
||||
comboList.classList.remove('mr-is-hidden');
|
||||
comboList.classList.add('mr-is-visible');
|
||||
startOutsideComboPointerListener();
|
||||
};
|
||||
const hideComboList = () => {
|
||||
if (!comboList) return;
|
||||
comboList.classList.remove('mr-is-visible');
|
||||
comboList.classList.add('mr-is-hidden');
|
||||
stopOutsideComboPointerListener();
|
||||
};
|
||||
|
||||
const populateComboOptions = (filterText, highlightIdx = -1) => {
|
||||
if (!comboList) return;
|
||||
const allModels = getAllModels();
|
||||
const f = (filterText || '').toLowerCase();
|
||||
const filtered = f
|
||||
? allModels.filter(m => buildLabel(m).toLowerCase().includes(f))
|
||||
@@ -639,6 +683,7 @@ export const missingBrowserMethods = {
|
||||
const chosenModel = allModels[idx];
|
||||
if (chosenModel) {
|
||||
this.queueResolution(missing, chosenModel);
|
||||
hideComboList();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -646,6 +691,16 @@ export const missingBrowserMethods = {
|
||||
};
|
||||
|
||||
if (comboList) {
|
||||
comboList.addEventListener('pointerdown', () => {
|
||||
comboListPointerActive = true;
|
||||
const releaseComboPointer = () => {
|
||||
setTimeout(() => {
|
||||
comboListPointerActive = false;
|
||||
}, 0);
|
||||
};
|
||||
window.addEventListener('pointerup', releaseComboPointer, { once: true, capture: true });
|
||||
window.addEventListener('pointercancel', releaseComboPointer, { once: true, capture: true });
|
||||
}, { capture: true });
|
||||
populateComboOptions('');
|
||||
}
|
||||
|
||||
@@ -655,27 +710,40 @@ export const missingBrowserMethods = {
|
||||
}, 200);
|
||||
comboInput.addEventListener('input', debouncedFilter);
|
||||
comboInput.addEventListener('focus', () => {
|
||||
if (comboList) {
|
||||
comboList.classList.remove('mr-is-hidden');
|
||||
comboList.classList.add('mr-is-visible');
|
||||
}
|
||||
showComboList();
|
||||
populateComboOptions(comboInput.value);
|
||||
});
|
||||
comboInput.addEventListener('blur', () => {
|
||||
setTimeout(() => {
|
||||
if (comboList) {
|
||||
comboList.classList.remove('mr-is-visible');
|
||||
comboList.classList.add('mr-is-hidden');
|
||||
if (!comboList) return;
|
||||
if (comboListPointerActive || comboList.matches(':hover')) {
|
||||
return;
|
||||
}
|
||||
hideComboList();
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
|
||||
if (comboRefresh) {
|
||||
comboRefresh.addEventListener('click', async () => {
|
||||
this.allModels = null;
|
||||
await this.ensureAllModelsLoaded();
|
||||
populateComboOptions(comboInput?.value || '');
|
||||
const minRefreshFeedback = new Promise(resolve => setTimeout(resolve, 420));
|
||||
const refreshAnimation = this.startRefreshButtonAnimation(comboRefresh);
|
||||
try {
|
||||
comboRefresh.disabled = true;
|
||||
comboRefresh.classList.add('mr-btn-is-disabled', 'mr-is-refreshing');
|
||||
this.allModels = null;
|
||||
await this.ensureAllModelsLoaded({ force: true });
|
||||
await minRefreshFeedback;
|
||||
populateComboOptions(comboInput?.value || '');
|
||||
} catch (error) {
|
||||
await minRefreshFeedback;
|
||||
console.warn('Model Resolver: could not refresh local model list', error);
|
||||
this.showNotification('Failed to refresh local model list', 'error');
|
||||
} finally {
|
||||
refreshAnimation?.cancel();
|
||||
comboRefresh.disabled = false;
|
||||
comboRefresh.classList.remove('mr-btn-is-disabled', 'mr-is-refreshing');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -690,6 +758,29 @@ export const missingBrowserMethods = {
|
||||
this.restoreDownloadProgressForMissing?.(missing);
|
||||
},
|
||||
|
||||
startRefreshButtonAnimation(button) {
|
||||
if (!button) return null;
|
||||
|
||||
const target = button.querySelector('.mr-refresh-spin-target') || button.querySelector('svg');
|
||||
if (!target || typeof target.animate !== 'function') return null;
|
||||
|
||||
const existing = target.getAnimations?.() || [];
|
||||
existing.forEach(animation => animation.cancel());
|
||||
target.style.transformOrigin = 'center';
|
||||
|
||||
return target.animate(
|
||||
[
|
||||
{ transform: 'rotate(0deg)' },
|
||||
{ transform: 'rotate(360deg)' }
|
||||
],
|
||||
{
|
||||
duration: 620,
|
||||
easing: 'linear',
|
||||
iterations: Infinity
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Display missing models in the dialog
|
||||
*/
|
||||
@@ -803,23 +894,27 @@ export const missingBrowserMethods = {
|
||||
async refreshMissingAnalysis(button = null) {
|
||||
if (button?.disabled) return;
|
||||
|
||||
const minRefreshFeedback = new Promise(resolve => setTimeout(resolve, 420));
|
||||
const refreshAnimation = this.startRefreshButtonAnimation(button);
|
||||
try {
|
||||
if (button) {
|
||||
button.disabled = true;
|
||||
button.classList.add('mr-btn-is-disabled');
|
||||
button.classList.add('mr-btn-is-disabled', 'mr-is-refreshing');
|
||||
}
|
||||
|
||||
this.showNotification('Refreshing missing models and local matches...', 'info');
|
||||
this.allModels = null;
|
||||
this.invalidateLoadedModelsCacheForActiveWorkflow?.();
|
||||
await minRefreshFeedback;
|
||||
await this.loadWorkflowData(null, { force: true });
|
||||
} catch (error) {
|
||||
console.error('Model Resolver: missing analysis refresh failed:', error);
|
||||
this.showNotification('Refresh failed: ' + error.message, 'error');
|
||||
} finally {
|
||||
if (button) {
|
||||
refreshAnimation?.cancel();
|
||||
button.disabled = false;
|
||||
button.classList.remove('mr-btn-is-disabled');
|
||||
button.classList.remove('mr-btn-is-disabled', 'mr-is-refreshing');
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -835,8 +930,7 @@ export const missingBrowserMethods = {
|
||||
const perfectMatches = filteredMatches.filter(m => m.confidence === 100);
|
||||
const otherMatches = filteredMatches.filter(m => m.confidence < 100 && m.confidence >= 70);
|
||||
|
||||
// Format the missing filename for display
|
||||
const missingFilename = this.formatFilename(missing.original_path, 60);
|
||||
const missingFilename = this.getMissingFilename(missing);
|
||||
|
||||
// Determine node info for the chip
|
||||
const nodeDisplay = this.getMissingNodeDisplay(missing);
|
||||
@@ -850,7 +944,7 @@ export const missingBrowserMethods = {
|
||||
html += `<div class="mr-card-title-wrap">`;
|
||||
|
||||
const titleMetaParts = [];
|
||||
let titlePrimaryHtml = `<span class="mr-card-title-primary" data-tooltip="${this.escapeHtml(missingFilename.full)}">${missingFilename.display}</span>`;
|
||||
let titlePrimaryHtml = `<span class="mr-card-title-primary" data-tooltip="${this.escapeHtml(missingFilename)}">${this.escapeHtml(missingFilename)}</span>`;
|
||||
let titleSecondaryHtml = '';
|
||||
|
||||
const modelId = missing.urn_model_id || missing.urn?.model_id;
|
||||
@@ -859,7 +953,7 @@ export const missingBrowserMethods = {
|
||||
const urnLoadingId = `urn-loading-${missing.node_id}-${missing.widget_index}`;
|
||||
|
||||
if (missing.is_urn) {
|
||||
titleMetaParts.push(`<span class="mr-card-title-eyebrow" data-tooltip="${this.escapeHtml(missingFilename.full)}">${missingFilename.display}</span>`);
|
||||
titleMetaParts.push(`<span class="mr-card-title-eyebrow" data-tooltip="${this.escapeHtml(missingFilename)}">${this.escapeHtml(missingFilename)}</span>`);
|
||||
}
|
||||
|
||||
if (missing.is_urn && !missing.civitai_info) {
|
||||
@@ -923,7 +1017,7 @@ export const missingBrowserMethods = {
|
||||
html += `<div class="mr-combo-row">`;
|
||||
html += `<label class="mr-combo-label">Model</label>`;
|
||||
html += `<input id="combo-input-${comboId}" class="mr-combo-input" type="text" placeholder="Type to filter local models...">`;
|
||||
html += `<button id="combo-refresh-${comboId}" type="button" aria-label="Reload local model list" data-tooltip="Reload local model list" class="mr-btn mr-btn-secondary mr-btn-sm mr-btn-icon-only mr-combo-refresh-btn">${getSvgIcon('refreshCw', 'currentColor', 'mr-combo-refresh-icon')}</button>`;
|
||||
html += `<button id="combo-refresh-${comboId}" type="button" aria-label="Reload local model list" data-tooltip="Reload local model list" class="mr-btn mr-btn-secondary mr-btn-sm mr-btn-icon-only mr-combo-refresh-btn"><span class="mr-refresh-spin-target">${getSvgIcon('refreshCw', 'currentColor', 'mr-combo-refresh-icon')}</span></button>`;
|
||||
html += `</div>`;
|
||||
html += `<div id="combo-list-${comboId}" class="mr-combo-list"></div>`;
|
||||
html += `</div>`;
|
||||
|
||||
Reference in New Issue
Block a user