mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-08-22 21:41:27 -03:00
Compare commits
4 Commits
91b0bf8933
...
f92f958682
| Author | SHA1 | Date | |
|---|---|---|---|
| f92f958682 | |||
| f63fab0676 | |||
| cfc4903c0c | |||
| a527a847fe |
+15
-3
@@ -446,7 +446,16 @@ class SaveImageLM:
|
|||||||
lora_resource["versionName"] = lora_civitai["name"]
|
lora_resource["versionName"] = lora_civitai["name"]
|
||||||
civitai_resources.append(lora_resource)
|
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
|
# Build output lines
|
||||||
lines = [prompt] if prompt else [""]
|
lines = [prompt] if prompt else [""]
|
||||||
@@ -456,8 +465,11 @@ class SaveImageLM:
|
|||||||
params: list[str] = []
|
params: list[str] = []
|
||||||
if steps is not None:
|
if steps is not None:
|
||||||
params.append(f"Steps: {steps}")
|
params.append(f"Steps: {steps}")
|
||||||
if sampler_display:
|
if sampler_name:
|
||||||
params.append(f"Sampler: {sampler_display}")
|
if scheduler_name:
|
||||||
|
params.append(f"Sampler: {sampler_name} {scheduler_name}")
|
||||||
|
else:
|
||||||
|
params.append(f"Sampler: {sampler_name}")
|
||||||
if cfg is not None:
|
if cfg is not None:
|
||||||
params.append(f"CFG scale: {cfg}")
|
params.append(f"CFG scale: {cfg}")
|
||||||
if seed is not None:
|
if seed is not None:
|
||||||
|
|||||||
@@ -517,6 +517,9 @@ class UpdateRoutes:
|
|||||||
custom_headers={'Accept': 'application/vnd.github+json'}
|
custom_headers={'Accept': 'application/vnd.github+json'}
|
||||||
)
|
)
|
||||||
if c_ok:
|
if c_ok:
|
||||||
|
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)
|
behind_by = c_data.get('behind_by', 0)
|
||||||
|
|
||||||
return version, changelog, behind_by, commit_date
|
return version, changelog, behind_by, commit_date
|
||||||
|
|||||||
@@ -927,6 +927,25 @@ class ModelScanner:
|
|||||||
# Update cache data
|
# Update cache data
|
||||||
self._cache.raw_data = [item for item in self._cache.raw_data if item['file_path'] not in missing_files]
|
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
|
# Resort cache if changes were made
|
||||||
if total_added > 0 or total_removed > 0:
|
if total_added > 0 or total_removed > 0:
|
||||||
# Update folders list
|
# Update folders list
|
||||||
@@ -1352,18 +1371,25 @@ class ModelScanner:
|
|||||||
# Update folder in metadata
|
# Update folder in metadata
|
||||||
metadata_dict['folder'] = folder
|
metadata_dict['folder'] = folder
|
||||||
|
|
||||||
# Add to cache
|
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)
|
self._cache.raw_data.append(metadata_dict)
|
||||||
self._cache.add_to_version_index(metadata_dict)
|
|
||||||
|
|
||||||
# Resort cache data
|
|
||||||
await self._cache.resort()
|
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
|
# Update the hash index
|
||||||
self._hash_index.add_entry(metadata_dict['sha256'], metadata_dict['file_path'])
|
self._hash_index.add_entry(metadata_dict['sha256'], metadata_dict['file_path'])
|
||||||
await self._persist_current_cache()
|
await self._persist_current_cache()
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ export class DownloadManager {
|
|||||||
this.modelVersionId = null;
|
this.modelVersionId = null;
|
||||||
this.source = null;
|
this.source = null;
|
||||||
this.selectedFile = null;
|
this.selectedFile = null;
|
||||||
|
this._isDiffusionModel = false;
|
||||||
|
|
||||||
this.selectedFolder = '';
|
this.selectedFolder = '';
|
||||||
this.batchModels = [];
|
this.batchModels = [];
|
||||||
@@ -787,24 +788,40 @@ export class DownloadManager {
|
|||||||
async proceedToLocationContent() {
|
async proceedToLocationContent() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch model roots
|
const _isDiffusionModel = this.selectedFile
|
||||||
const rootsData = await this.apiClient.fetchModelRoots();
|
? (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');
|
const modelRoot = document.getElementById('modelRoot');
|
||||||
modelRoot.innerHTML = rootsData.roots.map(root =>
|
modelRoot.innerHTML = rootsData.roots.map(root =>
|
||||||
`<option value="${root}">${root}</option>`
|
`<option value="${root}">${root}</option>`
|
||||||
).join('');
|
).join('');
|
||||||
|
|
||||||
// Set default root if available
|
const singularType = this._isDiffusionModel
|
||||||
const singularType = this.apiClient.modelType.replace(/s$/, '');
|
? 'unet'
|
||||||
|
: this.apiClient.modelType.replace(/s$/, '');
|
||||||
const defaultRootKey = `default_${singularType}_root`;
|
const defaultRootKey = `default_${singularType}_root`;
|
||||||
const defaultRoot = state.global.settings[defaultRootKey];
|
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);
|
console.log('Available roots:', rootsData.roots);
|
||||||
if (defaultRoot && rootsData.roots.includes(defaultRoot)) {
|
if (defaultRoot && rootsData.roots.includes(defaultRoot)) {
|
||||||
console.log(`Setting default root: ${defaultRoot}`);
|
console.log(`Setting default root: ${defaultRoot}`);
|
||||||
modelRoot.value = 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
|
// Set autocomplete="off" on folderPath input
|
||||||
const folderPathInput = document.getElementById('folderPath');
|
const folderPathInput = document.getElementById('folderPath');
|
||||||
if (folderPathInput) {
|
if (folderPathInput) {
|
||||||
@@ -1776,13 +1793,15 @@ export class DownloadManager {
|
|||||||
const modelRoot = document.getElementById('modelRoot').value;
|
const modelRoot = document.getElementById('modelRoot').value;
|
||||||
const config = this.apiClient.apiConfig.config;
|
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 (modelRoot) {
|
||||||
if (this.useDefaultPath) {
|
if (this.useDefaultPath) {
|
||||||
// Show actual template path
|
|
||||||
try {
|
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 templates = state.global.settings.download_path_templates;
|
||||||
const template = templates[singularType];
|
const template = templates[singularType];
|
||||||
fullPath += `/${template}`;
|
fullPath += `/${template}`;
|
||||||
|
|||||||
@@ -615,13 +615,17 @@ export class UpdateService {
|
|||||||
if (newVersionEl) {
|
if (newVersionEl) {
|
||||||
if (this.updateInfo?.nightly) {
|
if (this.updateInfo?.nightly) {
|
||||||
const behind = this.updateInfo.behind_by || 0;
|
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 date = this.updateInfo.commit_date || '';
|
||||||
const datePart = date ? ` · ${date}` : '';
|
const datePart = date ? ` · ${date}` : '';
|
||||||
|
|
||||||
if (behind > 0) {
|
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 {
|
} else {
|
||||||
newVersionEl.textContent = `Up to date (${hash}${datePart})`;
|
newVersionEl.textContent = `Up to date (${remoteHash}${datePart})`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newVersionEl.textContent = this.latestVersion;
|
newVersionEl.textContent = this.latestVersion;
|
||||||
|
|||||||
@@ -333,6 +333,7 @@ export const PATH_TEMPLATE_PLACEHOLDERS = [
|
|||||||
export const DEFAULT_PATH_TEMPLATES = {
|
export const DEFAULT_PATH_TEMPLATES = {
|
||||||
lora: '{base_model}/{first_tag}',
|
lora: '{base_model}/{first_tag}',
|
||||||
checkpoint: '{base_model}',
|
checkpoint: '{base_model}',
|
||||||
|
unet: '{base_model}',
|
||||||
embedding: '{first_tag}'
|
embedding: '{first_tag}'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -428,6 +428,71 @@ async def test_get_nightly_version_parses_behind_by(monkeypatch):
|
|||||||
assert changelog[0] == "test: add nightly feature"
|
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:
|
class _CheckUpdatesDownloader:
|
||||||
"""Fake downloader returning both a release list and a nightly commit + compare."""
|
"""Fake downloader returning both a release list and a nightly commit + compare."""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user