mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-08-06 14:10:13 -03:00
Compare commits
4 Commits
91b0bf8933
...
f92f958682
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f92f958682 | ||
|
|
f63fab0676 | ||
|
|
cfc4903c0c | ||
|
|
a527a847fe |
@@ -446,7 +446,16 @@ class SaveImageLM:
|
||||
lora_resource["versionName"] = lora_civitai["name"]
|
||||
civitai_resources.append(lora_resource)
|
||||
|
||||
sampler_display = self._get_civitai_sampler_name(sampler, scheduler)
|
||||
sampler_name = CIVITAI_SAMPLER_MAP.get(sampler, sampler) if sampler else None
|
||||
|
||||
scheduler_mapping = {
|
||||
"normal": "Normal",
|
||||
"karras": "Karras",
|
||||
"exponential": "Exponential",
|
||||
"sgm_uniform": "SGM Uniform",
|
||||
"sgm_quadratic": "SGM Quadratic",
|
||||
}
|
||||
scheduler_name = scheduler_mapping.get(scheduler, scheduler) if scheduler else None
|
||||
|
||||
# Build output lines
|
||||
lines = [prompt] if prompt else [""]
|
||||
@@ -456,8 +465,11 @@ class SaveImageLM:
|
||||
params: list[str] = []
|
||||
if steps is not None:
|
||||
params.append(f"Steps: {steps}")
|
||||
if sampler_display:
|
||||
params.append(f"Sampler: {sampler_display}")
|
||||
if sampler_name:
|
||||
if scheduler_name:
|
||||
params.append(f"Sampler: {sampler_name} {scheduler_name}")
|
||||
else:
|
||||
params.append(f"Sampler: {sampler_name}")
|
||||
if cfg is not None:
|
||||
params.append(f"CFG scale: {cfg}")
|
||||
if seed is not None:
|
||||
|
||||
@@ -517,7 +517,10 @@ class UpdateRoutes:
|
||||
custom_headers={'Accept': 'application/vnd.github+json'}
|
||||
)
|
||||
if c_ok:
|
||||
behind_by = c_data.get('behind_by', 0)
|
||||
if c_data.get('status') in ('ahead', 'diverged'):
|
||||
behind_by = c_data.get('ahead_by', 0)
|
||||
else:
|
||||
behind_by = c_data.get('behind_by', 0)
|
||||
|
||||
return version, changelog, behind_by, commit_date
|
||||
|
||||
|
||||
@@ -927,6 +927,25 @@ class ModelScanner:
|
||||
# Update cache data
|
||||
self._cache.raw_data = [item for item in self._cache.raw_data if item['file_path'] not in missing_files]
|
||||
|
||||
dedup_removed = 0
|
||||
seen_paths: set = set()
|
||||
deduped: list = []
|
||||
for item in reversed(self._cache.raw_data):
|
||||
path = item.get('file_path', '')
|
||||
if path not in seen_paths:
|
||||
seen_paths.add(path)
|
||||
deduped.append(item)
|
||||
else:
|
||||
for tag in item.get('tags', []):
|
||||
if tag in self._tags_count:
|
||||
self._tags_count[tag] = max(0, self._tags_count[tag] - 1)
|
||||
if self._tags_count[tag] == 0:
|
||||
del self._tags_count[tag]
|
||||
dedup_removed += 1
|
||||
if dedup_removed > 0:
|
||||
self._cache.raw_data = list(reversed(deduped))
|
||||
total_removed += dedup_removed
|
||||
|
||||
# Resort cache if changes were made
|
||||
if total_added > 0 or total_removed > 0:
|
||||
# Update folders list
|
||||
@@ -1352,18 +1371,25 @@ class ModelScanner:
|
||||
# Update folder in metadata
|
||||
metadata_dict['folder'] = folder
|
||||
|
||||
# Add to cache
|
||||
self._cache.raw_data.append(metadata_dict)
|
||||
self._cache.add_to_version_index(metadata_dict)
|
||||
file_path = metadata_dict.get('file_path', '')
|
||||
if file_path:
|
||||
old_entries = [item for item in self._cache.raw_data if item.get('file_path') == file_path]
|
||||
for old_entry in old_entries:
|
||||
for tag in old_entry.get('tags', []):
|
||||
if tag in self._tags_count:
|
||||
self._tags_count[tag] = max(0, self._tags_count[tag] - 1)
|
||||
if self._tags_count[tag] == 0:
|
||||
del self._tags_count[tag]
|
||||
self._hash_index.remove_by_path(file_path)
|
||||
self._cache.raw_data = [item for item in self._cache.raw_data if item.get('file_path') != file_path]
|
||||
|
||||
for tag in metadata_dict.get('tags', []):
|
||||
self._tags_count[tag] = self._tags_count.get(tag, 0) + 1
|
||||
|
||||
self._cache.raw_data.append(metadata_dict)
|
||||
|
||||
# Resort cache data
|
||||
await self._cache.resort()
|
||||
|
||||
# Update folders list
|
||||
all_folders = set(self._cache.folders)
|
||||
all_folders.add(folder)
|
||||
self._cache.folders = sorted(list(all_folders), key=lambda x: x.lower())
|
||||
|
||||
# Update the hash index
|
||||
self._hash_index.add_entry(metadata_dict['sha256'], metadata_dict['file_path'])
|
||||
await self._persist_current_cache()
|
||||
|
||||
@@ -158,6 +158,7 @@ export class DownloadManager {
|
||||
this.modelVersionId = null;
|
||||
this.source = null;
|
||||
this.selectedFile = null;
|
||||
this._isDiffusionModel = false;
|
||||
|
||||
this.selectedFolder = '';
|
||||
this.batchModels = [];
|
||||
@@ -787,24 +788,40 @@ export class DownloadManager {
|
||||
async proceedToLocationContent() {
|
||||
|
||||
try {
|
||||
// Fetch model roots
|
||||
const rootsData = await this.apiClient.fetchModelRoots();
|
||||
const _isDiffusionModel = this.selectedFile
|
||||
? (this.selectedFile.type === 'UNet' || this.selectedFile.type === 'Diffusion Model')
|
||||
: (this.currentVersion?.files || []).some(
|
||||
f => f.type === 'UNet' || f.type === 'Diffusion Model'
|
||||
);
|
||||
this._isDiffusionModel = _isDiffusionModel;
|
||||
|
||||
let rootsData;
|
||||
if (this._isDiffusionModel && this.apiClient.modelType === 'checkpoints') {
|
||||
rootsData = await this.apiClient.fetchModelRoots('diffusion_model');
|
||||
} else {
|
||||
rootsData = await this.apiClient.fetchModelRoots();
|
||||
}
|
||||
const modelRoot = document.getElementById('modelRoot');
|
||||
modelRoot.innerHTML = rootsData.roots.map(root =>
|
||||
`<option value="${root}">${root}</option>`
|
||||
).join('');
|
||||
|
||||
// Set default root if available
|
||||
const singularType = this.apiClient.modelType.replace(/s$/, '');
|
||||
const singularType = this._isDiffusionModel
|
||||
? 'unet'
|
||||
: this.apiClient.modelType.replace(/s$/, '');
|
||||
const defaultRootKey = `default_${singularType}_root`;
|
||||
const defaultRoot = state.global.settings[defaultRootKey];
|
||||
console.log(`Default root for ${this.apiClient.modelType}:`, defaultRoot);
|
||||
console.log(`Default root for ${singularType}:`, defaultRoot);
|
||||
console.log('Available roots:', rootsData.roots);
|
||||
if (defaultRoot && rootsData.roots.includes(defaultRoot)) {
|
||||
console.log(`Setting default root: ${defaultRoot}`);
|
||||
modelRoot.value = defaultRoot;
|
||||
}
|
||||
|
||||
const subtypeDisplay = this._isDiffusionModel ? 'Diffusion Model' : this.apiClient.apiConfig.config.displayName;
|
||||
document.getElementById('modelRootLabel').textContent =
|
||||
translate('modals.download.selectTypeRoot', { type: subtypeDisplay });
|
||||
|
||||
// Set autocomplete="off" on folderPath input
|
||||
const folderPathInput = document.getElementById('folderPath');
|
||||
if (folderPathInput) {
|
||||
@@ -1776,13 +1793,15 @@ export class DownloadManager {
|
||||
const modelRoot = document.getElementById('modelRoot').value;
|
||||
const config = this.apiClient.apiConfig.config;
|
||||
|
||||
let fullPath = modelRoot || translate('modals.download.selectTypeRoot', { type: config.displayName });
|
||||
const subtypeDisplay = this._isDiffusionModel ? 'Diffusion Model' : config.displayName;
|
||||
let fullPath = modelRoot || translate('modals.download.selectTypeRoot', { type: subtypeDisplay });
|
||||
|
||||
if (modelRoot) {
|
||||
if (this.useDefaultPath) {
|
||||
// Show actual template path
|
||||
try {
|
||||
const singularType = this.apiClient.modelType.replace(/s$/, '');
|
||||
const singularType = this._isDiffusionModel
|
||||
? 'unet'
|
||||
: this.apiClient.modelType.replace(/s$/, '');
|
||||
const templates = state.global.settings.download_path_templates;
|
||||
const template = templates[singularType];
|
||||
fullPath += `/${template}`;
|
||||
|
||||
@@ -615,13 +615,17 @@ export class UpdateService {
|
||||
if (newVersionEl) {
|
||||
if (this.updateInfo?.nightly) {
|
||||
const behind = this.updateInfo.behind_by || 0;
|
||||
const hash = this.latestVersion.replace('main-', '');
|
||||
const remoteHash = this.latestVersion.replace('main-', '');
|
||||
const localHash = this.gitInfo.short_hash || '';
|
||||
const date = this.updateInfo.commit_date || '';
|
||||
const datePart = date ? ` · ${date}` : '';
|
||||
|
||||
if (behind > 0) {
|
||||
newVersionEl.textContent = `${behind} commit${behind !== 1 ? 's' : ''} behind main (${hash}${datePart})`;
|
||||
newVersionEl.textContent = `${behind} commit${behind !== 1 ? 's' : ''} behind main (${remoteHash}${datePart})`;
|
||||
} else if (localHash !== remoteHash) {
|
||||
newVersionEl.textContent = `Behind main (${remoteHash}${datePart})`;
|
||||
} else {
|
||||
newVersionEl.textContent = `Up to date (${hash}${datePart})`;
|
||||
newVersionEl.textContent = `Up to date (${remoteHash}${datePart})`;
|
||||
}
|
||||
} else {
|
||||
newVersionEl.textContent = this.latestVersion;
|
||||
|
||||
@@ -333,6 +333,7 @@ export const PATH_TEMPLATE_PLACEHOLDERS = [
|
||||
export const DEFAULT_PATH_TEMPLATES = {
|
||||
lora: '{base_model}/{first_tag}',
|
||||
checkpoint: '{base_model}',
|
||||
unet: '{base_model}',
|
||||
embedding: '{first_tag}'
|
||||
};
|
||||
|
||||
|
||||
@@ -428,6 +428,71 @@ async def test_get_nightly_version_parses_behind_by(monkeypatch):
|
||||
assert changelog[0] == "test: add nightly feature"
|
||||
|
||||
|
||||
class _AheadCompareDownloader:
|
||||
"""Fake compare API response with status='ahead' (main is ahead of local)."""
|
||||
|
||||
commit_sha = "9999999"
|
||||
commit_msg = "latest commit"
|
||||
commit_date = "2026-07-28T00:00:00Z"
|
||||
ahead_by = 3
|
||||
|
||||
async def make_request(self, method, url, **kwargs):
|
||||
if "/compare/" in url:
|
||||
return True, {"status": "ahead", "ahead_by": self.ahead_by, "behind_by": 0}
|
||||
return True, {
|
||||
"sha": self.commit_sha,
|
||||
"commit": {
|
||||
"message": self.commit_msg,
|
||||
"committer": {"date": self.commit_date},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_nightly_version_reads_ahead_by_when_ahead(monkeypatch):
|
||||
"""compare/{local}...main returns status='ahead' → read ahead_by."""
|
||||
monkeypatch.setattr(update_routes, "get_downloader", lambda: _stub_downloader(_AheadCompareDownloader()))
|
||||
|
||||
version, changelog, behind_by, commit_date = await update_routes.UpdateRoutes._get_nightly_version(
|
||||
local_hash="oldhash"
|
||||
)
|
||||
|
||||
assert version == "main-9999999"
|
||||
assert behind_by == 3
|
||||
assert commit_date == "2026-07-28"
|
||||
|
||||
|
||||
class _DivergedCompareDownloader:
|
||||
"""Fake compare API response with status='diverged' (both have unique commits)."""
|
||||
|
||||
commit_sha = "aaaaaaa"
|
||||
commit_msg = "diverged test"
|
||||
commit_date = "2026-07-29T00:00:00Z"
|
||||
|
||||
async def make_request(self, method, url, **kwargs):
|
||||
if "/compare/" in url:
|
||||
return True, {"status": "diverged", "ahead_by": 5, "behind_by": 2}
|
||||
return True, {
|
||||
"sha": self.commit_sha,
|
||||
"commit": {
|
||||
"message": self.commit_msg,
|
||||
"committer": {"date": self.commit_date},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_nightly_version_reads_ahead_by_when_diverged(monkeypatch):
|
||||
"""compare/{local}...main returns status='diverged' → read ahead_by (remote ahead)."""
|
||||
monkeypatch.setattr(update_routes, "get_downloader", lambda: _stub_downloader(_DivergedCompareDownloader()))
|
||||
|
||||
version, changelog, behind_by, commit_date = await update_routes.UpdateRoutes._get_nightly_version(
|
||||
local_hash="divhash"
|
||||
)
|
||||
|
||||
assert behind_by == 5
|
||||
|
||||
|
||||
class _CheckUpdatesDownloader:
|
||||
"""Fake downloader returning both a release list and a nightly commit + compare."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user