mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-08-06 22:10:14 -03:00
When using full path lora syntax, the context menu (single/bulk) and bulk copy actions were passing only the file basename to buildLoraSyntax(), ignoring the folder prefix. This caused the output to look like legacy A1111 format even when full path mode was enabled. Aligns all entry points with ModelCard.handleSendToWorkflow(), which correctly includes the folder prefix. Also fixes selectAllVisibleModels() to cache the folder field, preventing missing prefix on select-all-then-send flows.
165 lines
6.5 KiB
JavaScript
165 lines
6.5 KiB
JavaScript
import { BaseContextMenu } from './BaseContextMenu.js';
|
|
import { ModelContextMenuMixin } from './ModelContextMenuMixin.js';
|
|
import { state } from '../../state/index.js';
|
|
import { getModelApiClient, resetAndReload } from '../../api/modelApiFactory.js';
|
|
import { copyLoraSyntax, sendLoraToWorkflow, buildLoraSyntax, showToast } from '../../utils/uiHelpers.js';
|
|
import { showExcludeModal, showDeleteModal } from '../../utils/modalUtils.js';
|
|
import { moveManager } from '../../managers/MoveManager.js';
|
|
|
|
export class LoraContextMenu extends BaseContextMenu {
|
|
constructor() {
|
|
super('loraContextMenu', '.model-card');
|
|
this.nsfwSelector = document.getElementById('nsfwLevelSelector');
|
|
this.modelType = 'lora';
|
|
this.resetAndReload = resetAndReload;
|
|
|
|
this.initNSFWSelector();
|
|
}
|
|
|
|
// Use the saveModelMetadata implementation from loraApi
|
|
async saveModelMetadata(filePath, data) {
|
|
return getModelApiClient().saveModelMetadata(filePath, data);
|
|
}
|
|
|
|
showMenu(x, y, card) {
|
|
super.showMenu(x, y, card);
|
|
this.updateExcludeMenuItem();
|
|
this.updateEnrichMenuItem(card);
|
|
}
|
|
|
|
updateEnrichMenuItem(card) {
|
|
const enrichItem = this.menu?.querySelector('[data-action="enrich-hf-llm"]');
|
|
if (!enrichItem) return;
|
|
const hasHfUrl = !!card.dataset.hf_url;
|
|
enrichItem.classList.toggle('disabled', !hasHfUrl);
|
|
enrichItem.title = hasHfUrl
|
|
? ''
|
|
: 'Link this model to a HuggingFace repo first (Link Model \u2192 Link to HuggingFace)';
|
|
}
|
|
|
|
handleMenuAction(action, menuItem) {
|
|
// First try to handle with common actions
|
|
if (ModelContextMenuMixin.handleCommonMenuActions.call(this, action)) {
|
|
return;
|
|
}
|
|
|
|
// Otherwise handle lora-specific actions
|
|
switch(action) {
|
|
case 'detail':
|
|
// Trigger the main card click which shows the modal
|
|
this.currentCard.click();
|
|
break;
|
|
case 'copyname':
|
|
// Generate and copy LoRA syntax
|
|
copyLoraSyntax(this.currentCard);
|
|
break;
|
|
case 'sendappend':
|
|
// Send LoRA to workflow (append mode)
|
|
this.sendLoraToWorkflow(false);
|
|
break;
|
|
case 'sendreplace':
|
|
// Send LoRA to workflow (replace mode)
|
|
this.sendLoraToWorkflow(true);
|
|
break;
|
|
case 'replace-preview':
|
|
// Add a new action for replacing preview images
|
|
getModelApiClient().replaceModelPreview(this.currentCard.dataset.filepath);
|
|
break;
|
|
case 'delete':
|
|
// Call showDeleteModal directly instead of clicking the trash button
|
|
showDeleteModal(this.currentCard.dataset.filepath);
|
|
break;
|
|
case 'move':
|
|
moveManager.showMoveModal(this.currentCard.dataset.filepath);
|
|
break;
|
|
case 'refresh-metadata':
|
|
getModelApiClient().refreshSingleModelMetadata(this.currentCard.dataset.filepath);
|
|
break;
|
|
case 'enrich-hf-llm':
|
|
this.enrichWithAgent(this.currentCard.dataset.filepath);
|
|
break;
|
|
case 'exclude':
|
|
showExcludeModal(this.currentCard.dataset.filepath);
|
|
break;
|
|
case 'restore':
|
|
this.restoreExcludedModel(this.currentCard.dataset.filepath);
|
|
break;
|
|
}
|
|
}
|
|
|
|
async enrichWithAgent(filePath) {
|
|
const { agentManager } = await import('../../managers/AgentManager.js');
|
|
|
|
const configured = await agentManager.isLlmConfigured();
|
|
if (!configured) {
|
|
showToast('toast.agent.llmNotConfigured', {}, 'warning');
|
|
return;
|
|
}
|
|
|
|
agentManager.connect();
|
|
|
|
const progressUI = state.loadingManager.showEnhancedProgress(
|
|
'Enriching metadata with AI...'
|
|
);
|
|
|
|
function cleanupCallbacks() {
|
|
const pIdx = agentManager.progressCallbacks.indexOf(onProgress);
|
|
if (pIdx >= 0) agentManager.progressCallbacks.splice(pIdx, 1);
|
|
const cIdx = agentManager.completeCallbacks.indexOf(onComplete);
|
|
if (cIdx >= 0) agentManager.completeCallbacks.splice(cIdx, 1);
|
|
const eIdx = agentManager.errorCallbacks.indexOf(onError);
|
|
if (eIdx >= 0) agentManager.errorCallbacks.splice(eIdx, 1);
|
|
}
|
|
|
|
const onProgress = (data) => {
|
|
if (data.status === 'processing' && data.current_path && data.updated_data && Object.keys(data.updated_data).length > 0) {
|
|
if (state.virtualScroller?.updateSingleItem) {
|
|
state.virtualScroller.updateSingleItem(data.current_path, data.updated_data);
|
|
}
|
|
const pct = data.total > 0 ? Math.floor((data.processed / data.total) * 100) : 0;
|
|
const name = data.current_path.split('/').pop();
|
|
progressUI.updateProgress(pct, name, `Processing ${name}`);
|
|
}
|
|
};
|
|
agentManager.onProgress(onProgress);
|
|
|
|
const onComplete = (data) => {
|
|
cleanupCallbacks();
|
|
|
|
if (data.status === 'completed') {
|
|
progressUI.complete(data.summary || 'Enrich complete');
|
|
showToast('toast.agent.enrichComplete', { summary: data.summary || 'Done' }, 'success');
|
|
}
|
|
};
|
|
agentManager.onComplete(onComplete);
|
|
|
|
const onError = (data) => {
|
|
cleanupCallbacks();
|
|
state.loadingManager.hide();
|
|
showToast('toast.agent.enrichFailed', { error: data.error || 'Unknown error' }, 'error');
|
|
};
|
|
agentManager.onError(onError);
|
|
|
|
try {
|
|
await agentManager.executeSkill('enrich_hf_metadata', [filePath]);
|
|
} catch (error) {
|
|
cleanupCallbacks();
|
|
state.loadingManager.hide();
|
|
showToast('toast.agent.enrichFailed', { error: error.message }, 'error');
|
|
}
|
|
}
|
|
|
|
sendLoraToWorkflow(replaceMode) {
|
|
const card = this.currentCard;
|
|
const usageTips = JSON.parse(card.dataset.usage_tips || '{}');
|
|
const folder = card.dataset.folder || '';
|
|
const loraName = folder ? `${folder}/${card.dataset.file_name}` : card.dataset.file_name;
|
|
const loraSyntax = buildLoraSyntax(loraName, usageTips);
|
|
|
|
sendLoraToWorkflow(loraSyntax, replaceMode, 'lora');
|
|
}
|
|
}
|
|
|
|
// Mix in shared methods
|
|
Object.assign(LoraContextMenu.prototype, ModelContextMenuMixin);
|