feat: enhance CivArchive resilience and mirror display
backend: - add 429 retry handling with backoff in CivArchive API requests - add page-scraping fallback for CivArchive model payloads - normalize CivArchive mirrors with dead/gated/paid/sha256 metadata - include file metadata and mirror data in normalized archive files - propagate additional dead/missing status field aliases - include metadata in normalized CivitAI files frontend: - render mirror lists with source grouping, dead-link indicators, and skull icons - add file badges (primary, type, format, size, fp, mirror count, id) - improve secondary info in resolve/download methods to include base model - enhance search panel secondary info with base model and filename - on selecting a version with empty files, fetch fresh version details - propagate base model context to CivitAI search state and display labels
This commit is contained in:
@@ -3975,6 +3975,9 @@
|
||||
border-radius: 7px;
|
||||
background: rgba(255,255,255,0.04);
|
||||
}
|
||||
.mr-model-details-file > .mr-model-details-mirrors {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.mr-model-details-file.is-primary {
|
||||
border-color: rgba(114,211,138,0.28);
|
||||
background: rgba(114,211,138,0.08);
|
||||
@@ -3997,12 +4000,150 @@
|
||||
color: rgba(255,255,255,0.58);
|
||||
font-size: 11px;
|
||||
}
|
||||
.mr-model-details-file-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
margin-top: 7px;
|
||||
}
|
||||
.mr-model-details-file-badges span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 20px;
|
||||
max-width: 100%;
|
||||
padding: 2px 6px;
|
||||
border-radius: 5px;
|
||||
background: rgba(255,255,255,0.075);
|
||||
color: rgba(255,255,255,0.78);
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
}
|
||||
.mr-model-details-mirrors {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin-top: 2px;
|
||||
padding-top: 9px;
|
||||
border-top: 1px solid rgba(255,255,255,0.08);
|
||||
}
|
||||
.mr-model-details-mirrors-title {
|
||||
color: rgba(255,255,255,0.62);
|
||||
font-size: 10px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.mr-model-details-mirror-group {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
}
|
||||
.mr-model-details-mirror-group-title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
min-width: 0;
|
||||
color: rgba(255,255,255,0.56);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.mr-model-details-mirror-platform-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
flex: 0 0 18px;
|
||||
}
|
||||
.mr-model-details-mirror-platform-icon svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
.mr-model-details-mirror-list {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
padding-left: 25px;
|
||||
}
|
||||
.mr-model-details-mirror {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
min-height: 48px;
|
||||
padding: 9px 10px;
|
||||
border: 1px solid rgba(255,255,255,0.07);
|
||||
border-radius: 6px;
|
||||
background: rgba(0,0,0,0.15);
|
||||
color: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
transition: background 120ms ease, border-color 120ms ease;
|
||||
}
|
||||
.mr-model-details-mirror:hover {
|
||||
border-color: rgba(255,255,255,0.13);
|
||||
background: rgba(255,255,255,0.07);
|
||||
}
|
||||
.mr-model-details-mirror.is-dead {
|
||||
border-color: rgba(185,28,28,0.58);
|
||||
background: rgba(127,29,29,0.36);
|
||||
}
|
||||
.mr-model-details-mirror.is-dead:hover {
|
||||
border-color: rgba(248,113,113,0.74);
|
||||
background: rgba(153,27,27,0.54);
|
||||
}
|
||||
.mr-model-details-mirror-info {
|
||||
min-width: 0;
|
||||
}
|
||||
.mr-model-details-mirror-info strong,
|
||||
.mr-model-details-mirror-info small {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.mr-model-details-mirror-info strong {
|
||||
color: #f0f0f2;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.mr-model-details-mirror-info small {
|
||||
margin-top: 3px;
|
||||
color: rgba(255,255,255,0.42);
|
||||
font-size: 10px;
|
||||
}
|
||||
.mr-model-details-mirror.is-dead .mr-model-details-mirror-info small {
|
||||
color: rgba(254,202,202,0.72);
|
||||
}
|
||||
.mr-model-details-mirror-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
color: rgba(255,255,255,0.56);
|
||||
}
|
||||
.mr-model-details-mirror-action svg {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
}
|
||||
.mr-model-details-mirror.is-dead .mr-model-details-mirror-action {
|
||||
color: #f87171;
|
||||
}
|
||||
.mr-model-details-use {
|
||||
min-height: 31px;
|
||||
padding: 0 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 11px;
|
||||
}
|
||||
.mr-model-details-mirror.mr-model-details-use {
|
||||
min-height: 48px;
|
||||
padding: 9px 10px;
|
||||
font-size: inherit;
|
||||
}
|
||||
.mr-model-details-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -4051,6 +4192,12 @@
|
||||
.mr-model-details-file {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.mr-model-details-mirror {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.mr-model-details-mirror-list {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mr-options-wrap {
|
||||
|
||||
@@ -1488,13 +1488,18 @@ export const resolveDownloadMethods = {
|
||||
if (civarchiveResult && civarchiveResult.download_url) {
|
||||
const archiveFilename = civarchiveResult.filename || missing.original_path?.split('/').pop()?.split('\\').pop() || '';
|
||||
const archiveName = civarchiveResult.name || archiveFilename || 'Model';
|
||||
const archiveSecondary = [
|
||||
archiveName && archiveName !== archiveFilename ? archiveFilename : '',
|
||||
civarchiveResult.platform || '',
|
||||
civarchiveResult.base_model || ''
|
||||
].filter(Boolean).join(' / ');
|
||||
addRow({
|
||||
sourceKey: 'civarchive',
|
||||
sourceLabel: 'CivArchive',
|
||||
model: archiveName,
|
||||
version: civarchiveResult.version_name || '',
|
||||
filename: archiveFilename,
|
||||
secondary: archiveName && archiveName !== archiveFilename ? archiveFilename : (civarchiveResult.platform || civarchiveResult.base_model || ''),
|
||||
secondary: archiveSecondary,
|
||||
match: this.getSearchResultMatchDisplay(civarchiveResult),
|
||||
size: this.formatSearchResultSize(civarchiveResult),
|
||||
downloadUrl: civarchiveResult.download_url,
|
||||
@@ -1535,13 +1540,17 @@ export const resolveDownloadMethods = {
|
||||
const modelUrl = civitaiResult.url || (civitaiResult.model_id ? `https://civitai.com/models/${civitaiResult.model_id}${civitaiResult.version_id ? `?modelVersionId=${civitaiResult.version_id}` : ''}` : '');
|
||||
const downloadFilename = missing.civitai_info?.expected_filename || civitaiResult.filename || civitaiResult.name;
|
||||
const modelName = missing.civitai_info?.model_name || civitaiResult.name || downloadFilename || 'Model';
|
||||
const civitaiSecondary = [
|
||||
civitaiResult.type || '',
|
||||
civitaiResult.base_model || missing.civitai_info?.base_model || ''
|
||||
].filter(Boolean).join(' / ');
|
||||
addRow({
|
||||
sourceKey: 'civitai',
|
||||
sourceLabel: 'CivitAI',
|
||||
model: modelName,
|
||||
version: missing.civitai_info?.version_name || civitaiResult.version_name || '',
|
||||
filename: downloadFilename,
|
||||
secondary: civitaiResult.type || civitaiResult.base_model || '',
|
||||
secondary: civitaiSecondary,
|
||||
match: this.getSearchResultMatchDisplay(civitaiResult),
|
||||
size: this.formatSearchResultSize(civitaiResult),
|
||||
downloadUrl: civitaiResult.download_url,
|
||||
|
||||
@@ -1114,6 +1114,12 @@ export const searchPanelMethods = {
|
||||
);
|
||||
const modelName = modelParts.name || downloadFilename;
|
||||
const fullModelName = this.getVersionedModelName(modelName, modelParts.version);
|
||||
const baseModel = downloadSource.base_model || missing.civitai_info?.base_model || '';
|
||||
const secondaryParts = [
|
||||
sourceSecondary,
|
||||
fullModelName && fullModelName !== downloadFilename ? downloadFilename : '',
|
||||
baseModel
|
||||
].filter(Boolean);
|
||||
|
||||
return {
|
||||
sourceKey,
|
||||
@@ -1121,7 +1127,7 @@ export const searchPanelMethods = {
|
||||
model: modelName,
|
||||
version: modelParts.version,
|
||||
filename: downloadFilename,
|
||||
secondary: sourceSecondary || (fullModelName && fullModelName !== downloadFilename ? downloadFilename : ''),
|
||||
secondary: secondaryParts.join(' / '),
|
||||
match: isFromWorkflow
|
||||
? { label: 'Provided', className: 'strong' }
|
||||
: this.getSearchResultMatchDisplay(downloadSource, 'Known', 'strong'),
|
||||
|
||||
@@ -1186,6 +1186,47 @@ export const modelInfoMethods = {
|
||||
`;
|
||||
},
|
||||
|
||||
async fetchSourceModelDetailsVersion(details, versionId) {
|
||||
if (!details?._detailsData || !versionId) return;
|
||||
|
||||
try {
|
||||
const data = details._detailsData;
|
||||
const tokens = this.getStoredTokens?.() || {};
|
||||
const response = await api.fetchApi('/model_resolver/model-details', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
source: data.source || details._sourceModel?.details_source || details._sourceModel?.source,
|
||||
model_id: data.model_id || details._sourceModel?.model_id || details._sourceModel?.modelId,
|
||||
version_id: versionId,
|
||||
civitai_key: tokens.civitai_key || ''
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Version details request failed: ${response.status}`);
|
||||
}
|
||||
|
||||
const fresh = await response.json();
|
||||
const freshVersion = fresh.selected_version;
|
||||
if (!freshVersion?.id) return;
|
||||
|
||||
data.versions = (data.versions || []).map(version =>
|
||||
String(version.id) === String(freshVersion.id)
|
||||
? { ...version, ...freshVersion }
|
||||
: version
|
||||
);
|
||||
data.selected_version = data.versions.find(version => String(version.id) === String(freshVersion.id)) || freshVersion;
|
||||
if (String(details._selectedVersionId || '') !== String(freshVersion.id)) return;
|
||||
|
||||
details.innerHTML = this.renderSourceModelDetails(data, details._sourceModel || {});
|
||||
this.bindTooltips(details);
|
||||
} catch (error) {
|
||||
console.error('Model Resolver: failed to fetch source model version details:', error);
|
||||
this.showNotification?.('Failed to load this model version.', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
renderSourceModelDetails(data = {}, contextModel = {}) {
|
||||
const versions = Array.isArray(data.versions) ? data.versions : [];
|
||||
const selectedVersionId = String(data.selected_version?.id || data.version_id || versions[0]?.id || '');
|
||||
@@ -1283,6 +1324,8 @@ export const modelInfoMethods = {
|
||||
}
|
||||
|
||||
return files.map((file, index) => {
|
||||
const fileMeta = this.getSourceModelFileMeta(file);
|
||||
const mirrors = this.getSourceModelMirrors(file);
|
||||
const payload = {
|
||||
source: data.source || contextModel.details_source || contextModel.source,
|
||||
model_id: data.model_id || contextModel.model_id,
|
||||
@@ -1299,20 +1342,183 @@ export const modelInfoMethods = {
|
||||
match_type: 'selected',
|
||||
confidence: 100
|
||||
};
|
||||
const mirrorList = mirrors.length > 1
|
||||
? this.renderSourceModelMirrors(mirrors, payload)
|
||||
: '';
|
||||
return `
|
||||
<div class="mr-model-details-file ${file.primary ? 'is-primary' : ''}">
|
||||
<div>
|
||||
<strong>${this.escapeHtml(file.name || `File ${index + 1}`)}</strong>
|
||||
<span>${this.escapeHtml([file.type, this.formatSearchResultSize(file), file.sha256 ? `SHA256 ${String(file.sha256).slice(0, 10)}` : ''].filter(Boolean).join(' / '))}</span>
|
||||
<span>${this.escapeHtml(fileMeta.summary)}</span>
|
||||
${fileMeta.badges.length ? `<div class="mr-model-details-file-badges">${fileMeta.badges.map(badge => `<span>${this.escapeHtml(badge)}</span>`).join('')}</div>` : ''}
|
||||
</div>
|
||||
<button type="button" class="mr-btn mr-btn-primary mr-model-details-use" data-selection="${this.escapeHtml(encodeURIComponent(JSON.stringify(payload)))}">
|
||||
Use this version
|
||||
</button>
|
||||
${mirrorList}
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
},
|
||||
|
||||
getSourceModelMirrors(file = {}) {
|
||||
const mirrors = Array.isArray(file.mirrors)
|
||||
? file.mirrors.filter(mirror => mirror?.url)
|
||||
: [];
|
||||
const downloadUrls = Array.isArray(file.download_urls) ? file.download_urls.filter(Boolean) : [];
|
||||
const seen = new Set();
|
||||
const normalized = [];
|
||||
|
||||
for (const mirror of mirrors) {
|
||||
const url = String(mirror.url || '');
|
||||
if (!url || seen.has(url)) continue;
|
||||
seen.add(url);
|
||||
normalized.push({
|
||||
...mirror,
|
||||
url,
|
||||
filename: mirror.filename || file.name,
|
||||
is_dead: Boolean(mirror.is_dead || mirror.isDead || mirror.deleted_at || mirror.deletedAt)
|
||||
});
|
||||
}
|
||||
|
||||
for (const url of downloadUrls) {
|
||||
const value = String(url || '');
|
||||
if (!value || seen.has(value)) continue;
|
||||
seen.add(value);
|
||||
normalized.push({
|
||||
url: value,
|
||||
filename: file.name,
|
||||
source: this.getSourceModelMirrorHost(value),
|
||||
is_dead: false
|
||||
});
|
||||
}
|
||||
|
||||
return normalized;
|
||||
},
|
||||
|
||||
renderSourceModelMirrors(mirrors = [], basePayload = {}) {
|
||||
if (!mirrors.length) return '';
|
||||
const groups = this.groupSourceModelMirrors(mirrors);
|
||||
return `
|
||||
<div class="mr-model-details-mirrors">
|
||||
<div class="mr-model-details-mirrors-title">Mirrors</div>
|
||||
${groups.map(group => `
|
||||
<div class="mr-model-details-mirror-group">
|
||||
<div class="mr-model-details-mirror-group-title">
|
||||
<span class="mr-model-details-mirror-platform-icon">${this.getSourceModelMirrorIcon(group.label)}</span>
|
||||
<span>${this.escapeHtml(group.label)} (${group.items.length} ${group.items.length === 1 ? 'mirror' : 'mirrors'})</span>
|
||||
</div>
|
||||
<div class="mr-model-details-mirror-list">
|
||||
${group.items.map((mirror) => {
|
||||
const payload = {
|
||||
...basePayload,
|
||||
download_url: mirror.url,
|
||||
filename: mirror.filename || basePayload.filename,
|
||||
mirror_source: mirror.source || this.getSourceModelMirrorHost(mirror.url),
|
||||
mirror_index: mirror._index
|
||||
};
|
||||
const meta = this.getSourceModelMirrorMeta(mirror, mirror._index === 0);
|
||||
const isDead = this.isSourceModelMirrorDead(mirror);
|
||||
const title = isDead ? 'Likely dead link' : 'Download file';
|
||||
const actionIcon = getSvgIcon(isDead ? 'skull' : 'download');
|
||||
const rowClass = isDead ? ' is-dead' : '';
|
||||
const payloadData = this.escapeHtml(encodeURIComponent(JSON.stringify(payload)));
|
||||
return `
|
||||
<button type="button"
|
||||
class="mr-model-details-mirror${rowClass} mr-model-details-use"
|
||||
title="${this.escapeHtml(title)}"
|
||||
data-tooltip="${this.escapeHtml(title)}"
|
||||
data-selection="${payloadData}">
|
||||
<div class="mr-model-details-mirror-info">
|
||||
<strong>${this.escapeHtml(mirror.filename || basePayload.filename || 'Download mirror')}</strong>
|
||||
${meta ? `<small>${this.escapeHtml(meta)}</small>` : ''}
|
||||
</div>
|
||||
<span class="mr-model-details-mirror-action" aria-hidden="true">${actionIcon}</span>
|
||||
</button>
|
||||
`;
|
||||
}).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
groupSourceModelMirrors(mirrors = []) {
|
||||
const groups = new Map();
|
||||
mirrors.forEach((mirror, index) => {
|
||||
const label = this.getSourceModelMirrorSourceLabel(mirror, index);
|
||||
if (!groups.has(label)) {
|
||||
groups.set(label, { label, items: [] });
|
||||
}
|
||||
groups.get(label).items.push({ ...mirror, _index: index });
|
||||
});
|
||||
return Array.from(groups.values());
|
||||
},
|
||||
|
||||
getSourceModelMirrorIcon(label = '') {
|
||||
const normalized = String(label || '').toLowerCase();
|
||||
if (normalized.includes('huggingface')) return getSvgIcon('huggingface');
|
||||
if (normalized.includes('civitai')) return getSvgIcon('civitai');
|
||||
return getSvgIcon('globe');
|
||||
},
|
||||
|
||||
isSourceModelMirrorDead(mirror = {}) {
|
||||
return Boolean(mirror.is_dead || mirror.isDead || mirror.deleted_at || mirror.deletedAt);
|
||||
},
|
||||
|
||||
getSourceModelMirrorHost(url = '') {
|
||||
try {
|
||||
return new URL(url).hostname.replace(/^www\./, '');
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
getSourceModelMirrorSourceLabel(mirror = {}, index = 0) {
|
||||
const source = String(mirror.source || '').trim();
|
||||
if (source) return source;
|
||||
const host = this.getSourceModelMirrorHost(mirror.url || '');
|
||||
if (host.includes('huggingface.co')) return 'HuggingFace';
|
||||
if (host.includes('civitai.com')) return 'CivitAI';
|
||||
return host || `Mirror ${index + 1}`;
|
||||
},
|
||||
|
||||
getSourceModelMirrorMeta(mirror = {}, isDefault = false) {
|
||||
const host = this.getSourceModelMirrorHost(mirror.url || '');
|
||||
const meta = [
|
||||
isDefault ? 'Default' : '',
|
||||
this.isSourceModelMirrorDead(mirror) ? 'Likely dead' : '',
|
||||
host,
|
||||
mirror.is_gated ? 'Gated' : '',
|
||||
mirror.is_paid ? 'Paid' : '',
|
||||
mirror.sha256 ? `SHA256 ${String(mirror.sha256).slice(0, 10)}` : ''
|
||||
].filter(Boolean);
|
||||
return meta.join(' / ');
|
||||
},
|
||||
|
||||
getSourceModelFileMeta(file = {}) {
|
||||
const metadata = file.metadata && typeof file.metadata === 'object' ? file.metadata : {};
|
||||
const mirrorCount = Array.isArray(file.mirrors) && file.mirrors.length
|
||||
? file.mirrors.length
|
||||
: file.mirror_count;
|
||||
const badges = [
|
||||
file.primary ? 'Primary' : '',
|
||||
file.type || '',
|
||||
metadata.format || '',
|
||||
metadata.size || '',
|
||||
metadata.fp || '',
|
||||
mirrorCount ? `${mirrorCount} mirrors` : '',
|
||||
file.id ? `ID ${file.id}` : ''
|
||||
].filter(Boolean);
|
||||
const summary = [
|
||||
this.formatSearchResultSize(file),
|
||||
file.sha256 ? `SHA256 ${String(file.sha256).slice(0, 10)}` : ''
|
||||
].filter(Boolean).join(' / ') || 'Downloadable file';
|
||||
|
||||
return { badges, summary };
|
||||
},
|
||||
|
||||
renderSourceModelImageMeta(image = {}) {
|
||||
const stats = image.stats || {};
|
||||
const metaItems = [
|
||||
@@ -1374,9 +1580,14 @@ export const modelInfoMethods = {
|
||||
details._selectedVersionId = String(versionTab.dataset.versionId || '');
|
||||
const data = details._detailsData;
|
||||
if (data) {
|
||||
data.selected_version = (data.versions || []).find(version => String(version.id) === details._selectedVersionId) || data.selected_version;
|
||||
const selected = (data.versions || []).find(version => String(version.id) === details._selectedVersionId) || data.selected_version;
|
||||
data.selected_version = selected;
|
||||
details.innerHTML = this.renderSourceModelDetails(data, details._sourceModel || {});
|
||||
this.bindTooltips(details);
|
||||
|
||||
if (selected?.id && (!Array.isArray(selected.files) || !selected.files.length)) {
|
||||
this.fetchSourceModelDetailsVersion(details, selected.id);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1462,6 +1673,7 @@ export const modelInfoMethods = {
|
||||
}
|
||||
|
||||
const sourceKey = String(selection.source || contextModel.details_source || contextModel.source || '').toLowerCase();
|
||||
const selectedBaseModel = selection.base_model || contextModel.base_model || '';
|
||||
const sourceResult = {
|
||||
source: sourceKey,
|
||||
model_id: selection.model_id,
|
||||
@@ -1473,7 +1685,7 @@ export const modelInfoMethods = {
|
||||
url: selection.url,
|
||||
download_url: selection.download_url,
|
||||
size: selection.size,
|
||||
base_model: selection.base_model,
|
||||
base_model: selectedBaseModel,
|
||||
tags: selection.tags || [],
|
||||
match_type: selection.match_type || 'selected',
|
||||
confidence: selection.confidence || 100,
|
||||
@@ -1491,8 +1703,24 @@ export const modelInfoMethods = {
|
||||
if (state?.results && sourceKey) {
|
||||
state.results[sourceKey] = sourceResult;
|
||||
}
|
||||
missing.civitai_info = {
|
||||
...(missing.civitai_info || {}),
|
||||
model_name: selection.name || missing.civitai_info?.model_name,
|
||||
version_name: selection.version_name || missing.civitai_info?.version_name,
|
||||
expected_filename: selection.filename || missing.civitai_info?.expected_filename,
|
||||
base_model: selectedBaseModel || missing.civitai_info?.base_model,
|
||||
tags: selection.tags || missing.civitai_info?.tags || []
|
||||
};
|
||||
if (selectedBaseModel) {
|
||||
const canonicalBaseModel = this.resolveBaseModelAlias?.(selectedBaseModel) || selectedBaseModel;
|
||||
if (state) {
|
||||
state.selectedBaseModel = canonicalBaseModel;
|
||||
state.lastAttemptBaseModelContext = canonicalBaseModel;
|
||||
}
|
||||
}
|
||||
|
||||
this.refreshSearchUiForMissing?.(missing, state || null);
|
||||
this.refreshSearchBaseModelLabels?.();
|
||||
this.updateBatchFooterButtons?.();
|
||||
this.persistSearchStateForActiveWorkflow?.();
|
||||
this.showNotification?.(`Selected ${selection.filename || selection.name || 'model version'}.`, 'success');
|
||||
|
||||
@@ -6,6 +6,7 @@ const SVG_ICONS = {
|
||||
refreshCw: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a9 9 0 1 1-2.64-6.36"></path><path d="M21 3v6h-6"></path></svg>`,
|
||||
locate: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12h3"></path><path d="M19 12h3"></path><path d="M12 2v3"></path><path d="M12 19v3"></path><circle cx="12" cy="12" r="7"></circle><circle cx="12" cy="12" r="2.5"></circle></svg>`,
|
||||
download: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v12"></path><path d="m7 10 5 5 5-5"></path><path d="M5 21h14"></path></svg>`,
|
||||
skull: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12.5 17-.5-1-.5 1h1z"></path><path d="M15 22a1 1 0 0 0 1-1v-1a2 2 0 0 0 1.56-3.25 8 8 0 1 0-11.12 0A2 2 0 0 0 8 20v1a1 1 0 0 0 1 1z"></path><circle cx="15" cy="12" r="1"></circle><circle cx="9" cy="12" r="1"></circle></svg>`,
|
||||
copy: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`,
|
||||
externalLink: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 12h10.5"></path><path d="m15.25 15.75 3.75-3.75-3.75-3.75"></path><path d="M19 7.75V7a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-.75"></path></svg>`,
|
||||
moreCircle: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="{color}" stroke-width="1.5" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"></path></svg>`,
|
||||
|
||||
Reference in New Issue
Block a user