mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 07:05:43 -03:00
Add functionality to open example images folder for models
This commit is contained in:
@@ -5,6 +5,8 @@ import json
|
|||||||
import time
|
import time
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
from server import PromptServer # type: ignore
|
from server import PromptServer # type: ignore
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from ..services.settings_manager import settings
|
from ..services.settings_manager import settings
|
||||||
@@ -56,6 +58,9 @@ class MiscRoutes:
|
|||||||
# Lora code update endpoint
|
# Lora code update endpoint
|
||||||
app.router.add_post('/api/update-lora-code', MiscRoutes.update_lora_code)
|
app.router.add_post('/api/update-lora-code', MiscRoutes.update_lora_code)
|
||||||
|
|
||||||
|
# Add new route for opening example images folder
|
||||||
|
app.router.add_post('/api/open-example-images-folder', MiscRoutes.open_example_images_folder)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def clear_cache(request):
|
async def clear_cache(request):
|
||||||
"""Clear all cache files from the cache folder"""
|
"""Clear all cache files from the cache folder"""
|
||||||
@@ -864,3 +869,63 @@ class MiscRoutes:
|
|||||||
'success': False,
|
'success': False,
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}, status=500)
|
}, status=500)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def open_example_images_folder(request):
|
||||||
|
"""
|
||||||
|
Open the example images folder for a specific model
|
||||||
|
|
||||||
|
Expects a JSON body with:
|
||||||
|
{
|
||||||
|
"model_hash": "sha256_hash" # SHA256 hash of the model
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Parse the request body
|
||||||
|
data = await request.json()
|
||||||
|
model_hash = data.get('model_hash')
|
||||||
|
|
||||||
|
if not model_hash:
|
||||||
|
return web.json_response({
|
||||||
|
'success': False,
|
||||||
|
'error': 'Missing model_hash parameter'
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
# Get the example images path from settings
|
||||||
|
example_images_path = settings.get('example_images_path')
|
||||||
|
if not example_images_path:
|
||||||
|
return web.json_response({
|
||||||
|
'success': False,
|
||||||
|
'error': 'No example images path configured. Please set it in the settings panel first.'
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
# Construct the folder path for this model
|
||||||
|
model_folder = os.path.join(example_images_path, model_hash)
|
||||||
|
|
||||||
|
# Check if the folder exists
|
||||||
|
if not os.path.exists(model_folder):
|
||||||
|
return web.json_response({
|
||||||
|
'success': False,
|
||||||
|
'error': 'No example images found for this model. Download example images first.'
|
||||||
|
}, status=404)
|
||||||
|
|
||||||
|
# Open the folder in the file explorer
|
||||||
|
if os.name == 'nt': # Windows
|
||||||
|
os.startfile(model_folder)
|
||||||
|
elif os.name == 'posix': # macOS and Linux
|
||||||
|
if sys.platform == 'darwin': # macOS
|
||||||
|
subprocess.Popen(['open', model_folder])
|
||||||
|
else: # Linux
|
||||||
|
subprocess.Popen(['xdg-open', model_folder])
|
||||||
|
|
||||||
|
return web.json_response({
|
||||||
|
'success': True,
|
||||||
|
'message': f'Opened example images folder for model {model_hash}'
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to open example images folder: {e}", exc_info=True)
|
||||||
|
return web.json_response({
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}, status=500)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { showToast, copyToClipboard } from '../utils/uiHelpers.js';
|
import { showToast, copyToClipboard, openExampleImagesFolder } from '../utils/uiHelpers.js';
|
||||||
import { state } from '../state/index.js';
|
import { state } from '../state/index.js';
|
||||||
import { showCheckpointModal } from './checkpointModal/index.js';
|
import { showCheckpointModal } from './checkpointModal/index.js';
|
||||||
import { NSFW_LEVELS } from '../utils/constants.js';
|
import { NSFW_LEVELS } from '../utils/constants.js';
|
||||||
@@ -115,8 +115,8 @@ export function createCheckpointCard(checkpoint) {
|
|||||||
<span class="model-name">${checkpoint.model_name}</span>
|
<span class="model-name">${checkpoint.model_name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<i class="fas fa-image"
|
<i class="fas fa-folder-open"
|
||||||
title="Replace Preview Image">
|
title="Open Example Images Folder">
|
||||||
</i>
|
</i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -272,6 +272,12 @@ export function createCheckpointCard(checkpoint) {
|
|||||||
replaceCheckpointPreview(checkpoint.file_path);
|
replaceCheckpointPreview(checkpoint.file_path);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Open example images folder button click event
|
||||||
|
card.querySelector('.fa-folder-open')?.addEventListener('click', e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
openExampleImagesFolder(checkpoint.sha256);
|
||||||
|
});
|
||||||
|
|
||||||
// Add autoplayOnHover handlers for video elements if needed
|
// Add autoplayOnHover handlers for video elements if needed
|
||||||
const videoElement = card.querySelector('video');
|
const videoElement = card.querySelector('video');
|
||||||
if (videoElement && autoplayOnHover) {
|
if (videoElement && autoplayOnHover) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { BaseContextMenu } from './BaseContextMenu.js';
|
import { BaseContextMenu } from './BaseContextMenu.js';
|
||||||
import { refreshSingleCheckpointMetadata, saveModelMetadata } from '../../api/checkpointApi.js';
|
import { refreshSingleCheckpointMetadata, saveModelMetadata, replaceCheckpointPreview } from '../../api/checkpointApi.js';
|
||||||
import { showToast, getNSFWLevelName } from '../../utils/uiHelpers.js';
|
import { showToast, getNSFWLevelName, openExampleImagesFolder } from '../../utils/uiHelpers.js';
|
||||||
import { NSFW_LEVELS } from '../../utils/constants.js';
|
import { NSFW_LEVELS } from '../../utils/constants.js';
|
||||||
import { getStorageItem } from '../../utils/storageHelpers.js';
|
import { getStorageItem } from '../../utils/storageHelpers.js';
|
||||||
import { showExcludeModal } from '../../utils/modalUtils.js';
|
import { showExcludeModal } from '../../utils/modalUtils.js';
|
||||||
@@ -23,10 +23,12 @@ export class CheckpointContextMenu extends BaseContextMenu {
|
|||||||
this.currentCard.click();
|
this.currentCard.click();
|
||||||
break;
|
break;
|
||||||
case 'preview':
|
case 'preview':
|
||||||
// Replace checkpoint preview
|
// Open example images folder instead of replacing preview
|
||||||
if (this.currentCard.querySelector('.fa-image')) {
|
openExampleImagesFolder(this.currentCard.dataset.sha256);
|
||||||
this.currentCard.querySelector('.fa-image').click();
|
break;
|
||||||
}
|
case 'replace-preview':
|
||||||
|
// Add new action for replacing preview images
|
||||||
|
replaceCheckpointPreview(this.currentCard.dataset.filepath);
|
||||||
break;
|
break;
|
||||||
case 'civitai':
|
case 'civitai':
|
||||||
// Open civitai page
|
// Open civitai page
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { BaseContextMenu } from './BaseContextMenu.js';
|
import { BaseContextMenu } from './BaseContextMenu.js';
|
||||||
import { refreshSingleLoraMetadata, saveModelMetadata } from '../../api/loraApi.js';
|
import { refreshSingleLoraMetadata, saveModelMetadata, replacePreview } from '../../api/loraApi.js';
|
||||||
import { showToast, getNSFWLevelName, copyToClipboard, sendLoraToWorkflow } from '../../utils/uiHelpers.js';
|
import { showToast, getNSFWLevelName, copyToClipboard, sendLoraToWorkflow, openExampleImagesFolder } from '../../utils/uiHelpers.js';
|
||||||
import { NSFW_LEVELS } from '../../utils/constants.js';
|
import { NSFW_LEVELS } from '../../utils/constants.js';
|
||||||
import { getStorageItem } from '../../utils/storageHelpers.js';
|
import { getStorageItem } from '../../utils/storageHelpers.js';
|
||||||
import { showExcludeModal, showDeleteModal } from '../../utils/modalUtils.js';
|
import { showExcludeModal, showDeleteModal } from '../../utils/modalUtils.js';
|
||||||
@@ -47,7 +47,12 @@ export class LoraContextMenu extends BaseContextMenu {
|
|||||||
this.sendLoraToWorkflow(true);
|
this.sendLoraToWorkflow(true);
|
||||||
break;
|
break;
|
||||||
case 'preview':
|
case 'preview':
|
||||||
this.currentCard.querySelector('.fa-image')?.click();
|
// Open example images folder instead of showing preview image dialog
|
||||||
|
openExampleImagesFolder(this.currentCard.dataset.sha256);
|
||||||
|
break;
|
||||||
|
case 'replace-preview':
|
||||||
|
// Add a new action for replacing preview images
|
||||||
|
replacePreview(this.currentCard.dataset.filepath);
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
// Call showDeleteModal directly instead of clicking the trash button
|
// Call showDeleteModal directly instead of clicking the trash button
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { showToast, openCivitai, copyToClipboard, sendLoraToWorkflow } from '../utils/uiHelpers.js';
|
import { showToast, openCivitai, copyToClipboard, sendLoraToWorkflow, openExampleImagesFolder } from '../utils/uiHelpers.js';
|
||||||
import { state } from '../state/index.js';
|
import { state } from '../state/index.js';
|
||||||
import { showLoraModal } from './loraModal/index.js';
|
import { showLoraModal } from './loraModal/index.js';
|
||||||
import { bulkManager } from '../managers/BulkManager.js';
|
import { bulkManager } from '../managers/BulkManager.js';
|
||||||
@@ -69,6 +69,12 @@ function handleLoraCardEvent(event) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.target.closest('.fa-folder-open')) {
|
||||||
|
event.stopPropagation();
|
||||||
|
openExampleImagesFolder(card.dataset.sha256);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If no specific element was clicked, handle the card click (show modal or toggle selection)
|
// If no specific element was clicked, handle the card click (show modal or toggle selection)
|
||||||
if (state.bulkMode) {
|
if (state.bulkMode) {
|
||||||
// Toggle selection using the bulk manager
|
// Toggle selection using the bulk manager
|
||||||
@@ -300,8 +306,8 @@ export function createLoraCard(lora) {
|
|||||||
<span class="model-name">${lora.model_name}</span>
|
<span class="model-name">${lora.model_name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<i class="fas fa-image"
|
<i class="fas fa-folder-open"
|
||||||
title="Replace Preview Image">
|
title="Open Example Images Folder">
|
||||||
</i>
|
</i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -420,3 +420,35 @@ export async function sendLoraToWorkflow(loraSyntax, replaceMode = false, syntax
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the example images folder for a specific model
|
||||||
|
* @param {string} modelHash - The SHA256 hash of the model
|
||||||
|
*/
|
||||||
|
export async function openExampleImagesFolder(modelHash) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/open-example-images-folder', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model_hash: modelHash
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
showToast('Opening example images folder', 'success');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
showToast(result.error || 'Failed to open example images folder', 'error');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to open example images folder:', error);
|
||||||
|
showToast('Failed to open example images folder', 'error');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,8 @@
|
|||||||
<div class="context-menu-item" data-action="civitai"><i class="fas fa-external-link-alt"></i> View on CivitAI</div>
|
<div class="context-menu-item" data-action="civitai"><i class="fas fa-external-link-alt"></i> View on CivitAI</div>
|
||||||
<div class="context-menu-item" data-action="refresh-metadata"><i class="fas fa-sync"></i> Refresh Civitai Data</div>
|
<div class="context-menu-item" data-action="refresh-metadata"><i class="fas fa-sync"></i> Refresh Civitai Data</div>
|
||||||
<div class="context-menu-item" data-action="copyname"><i class="fas fa-copy"></i> Copy Model Filename</div>
|
<div class="context-menu-item" data-action="copyname"><i class="fas fa-copy"></i> Copy Model Filename</div>
|
||||||
<div class="context-menu-item" data-action="preview"><i class="fas fa-image"></i> Replace Preview</div>
|
<div class="context-menu-item" data-action="preview"><i class="fas fa-folder-open"></i> Open Examples Folder</div>
|
||||||
|
<div class="context-menu-item" data-action="replace-preview"><i class="fas fa-image"></i> Replace Preview</div>
|
||||||
<div class="context-menu-item" data-action="set-nsfw"><i class="fas fa-exclamation-triangle"></i> Set Content Rating</div>
|
<div class="context-menu-item" data-action="set-nsfw"><i class="fas fa-exclamation-triangle"></i> Set Content Rating</div>
|
||||||
<div class="context-menu-separator"></div>
|
<div class="context-menu-separator"></div>
|
||||||
<div class="context-menu-item" data-action="move"><i class="fas fa-folder-open"></i> Move to Folder</div>
|
<div class="context-menu-item" data-action="move"><i class="fas fa-folder-open"></i> Move to Folder</div>
|
||||||
|
|||||||
@@ -18,6 +18,9 @@
|
|||||||
<i class="fas fa-exchange-alt"></i> Send to Workflow (Replace)
|
<i class="fas fa-exchange-alt"></i> Send to Workflow (Replace)
|
||||||
</div>
|
</div>
|
||||||
<div class="context-menu-item" data-action="preview">
|
<div class="context-menu-item" data-action="preview">
|
||||||
|
<i class="fas fa-folder-open"></i> Open Examples Folder
|
||||||
|
</div>
|
||||||
|
<div class="context-menu-item" data-action="replace-preview">
|
||||||
<i class="fas fa-image"></i> Replace Preview
|
<i class="fas fa-image"></i> Replace Preview
|
||||||
</div>
|
</div>
|
||||||
<div class="context-menu-item" data-action="set-nsfw">
|
<div class="context-menu-item" data-action="set-nsfw">
|
||||||
|
|||||||
Reference in New Issue
Block a user