mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-07-03 07:51:16 -03:00
Introduce an agent skill framework for LLM-driven metadata enrichment: - AgentCLI (py/agent_cli/): in-process wrappers around internal services using standard relative imports, eliminating the need for sys.path hacks - LLMService: centralized BYOK (bring-your-own-key) LLM client supporting OpenAI, Ollama, and custom OpenAI-compatible endpoints - PostProcessor: deterministic engine that applies LLM output via AgentCLI (replaces old handler.py + _BASE_MODEL_ALIASES approach) - SkillRegistry: filesystem-based skill discovery (skill.yaml + prompt.md) - AgentService: orchestrates skill execution with WebSocket progress - Frontend AgentManager: WebSocket listeners, skill execution, config UI - Context menu entries (single + bulk) for "Enrich Metadata (Agent)" - Settings UI for AI Provider configuration (BYOK) - Full i18n support across 9 locales Bug fixes found during review: - aiohttp.web.json_response: status_code= -> status= - settings_modal cancelEditApiKey: wrong argument position - AgentManager.isLlmConfigured: allow Ollama without API key - PostProcessor._merge_tags: lowercase all tags to match TagUpdateService
129 lines
4.9 KiB
JavaScript
129 lines
4.9 KiB
JavaScript
import { BaseContextMenu } from './BaseContextMenu.js';
|
|
import { ModelContextMenuMixin } from './ModelContextMenuMixin.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();
|
|
}
|
|
|
|
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-agent':
|
|
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');
|
|
|
|
// Check if LLM is configured
|
|
const configured = await agentManager.isLlmConfigured();
|
|
if (!configured) {
|
|
showToast('toast.agent.llmNotConfigured', {}, 'warning');
|
|
return;
|
|
}
|
|
|
|
// Connect WebSocket for progress
|
|
agentManager.connect();
|
|
|
|
// Set up one-time completion handler
|
|
const onComplete = (data) => {
|
|
const idx = agentManager.completeCallbacks.indexOf(onComplete);
|
|
if (idx >= 0) agentManager.completeCallbacks.splice(idx, 1);
|
|
|
|
if (data.status === 'completed') {
|
|
showToast('toast.agent.enrichComplete', { summary: data.summary || 'Done' }, 'success');
|
|
// Soft reload to reflect updated metadata
|
|
if (typeof resetAndReload === 'function') {
|
|
resetAndReload();
|
|
}
|
|
} else if (data.status === 'error') {
|
|
showToast('toast.agent.enrichFailed', { error: data.error || 'Unknown error' }, 'error');
|
|
}
|
|
};
|
|
agentManager.onComplete(onComplete);
|
|
|
|
// Show progress toast
|
|
showToast('toast.agent.enrichStarted', {}, 'info');
|
|
|
|
try {
|
|
await agentManager.executeSkill('enrich_hf_metadata', [filePath]);
|
|
} catch (error) {
|
|
showToast('toast.agent.enrichFailed', { error: error.message }, 'error');
|
|
}
|
|
}
|
|
|
|
sendLoraToWorkflow(replaceMode) {
|
|
const card = this.currentCard;
|
|
const usageTips = JSON.parse(card.dataset.usage_tips || '{}');
|
|
const loraSyntax = buildLoraSyntax(card.dataset.file_name, usageTips);
|
|
|
|
sendLoraToWorkflow(loraSyntax, replaceMode, 'lora');
|
|
}
|
|
}
|
|
|
|
// Mix in shared methods
|
|
Object.assign(LoraContextMenu.prototype, ModelContextMenuMixin);
|