mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-07-03 07:51:16 -03:00
feat(agent): add LLM-powered metadata enrichment system with AgentCLI and PostProcessor
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
This commit is contained in:
@@ -12,6 +12,9 @@
|
||||
<div class="context-menu-item" data-action="check-updates">
|
||||
<i class="fas fa-bell"></i> <span>{{ t('loras.contextMenu.checkUpdates') }}</span>
|
||||
</div>
|
||||
<div class="context-menu-item" data-action="enrich-hf-agent">
|
||||
<i class="fas fa-wand-magic-sparkles"></i> <span>{{ t('loras.contextMenu.enrichHfAgent') }}</span>
|
||||
</div>
|
||||
<div class="context-menu-item" data-action="relink-civitai">
|
||||
<i class="fas fa-link"></i> <span>{{ t('loras.contextMenu.relinkCivitai') }}</span>
|
||||
</div>
|
||||
@@ -83,6 +86,9 @@
|
||||
<div class="context-menu-item" data-action="resume-metadata-refresh">
|
||||
<i class="fas fa-redo"></i> <span>{{ t('loras.bulkOperations.resumeMetadataRefresh') }}</span>
|
||||
</div>
|
||||
<div class="context-menu-item" data-action="enrich-hf-agent-bulk">
|
||||
<i class="fas fa-wand-magic-sparkles"></i> <span>{{ t('loras.bulkOperations.enrichHfAgent') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="context-menu-section" data-section="workflow">
|
||||
<div class="context-menu-section-header">{{ t('loras.bulkOperations.sections.workflow') }}</div>
|
||||
|
||||
@@ -144,6 +144,96 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI Provider Configuration (BYOK) -->
|
||||
<div class="settings-subsection">
|
||||
<div class="settings-subsection-header">
|
||||
<h4>{{ t('settings.aiProvider.title') }}</h4>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<label for="llmProvider">{{ t('settings.aiProvider.provider') }}</label>
|
||||
<i class="fas fa-info-circle info-icon" data-tooltip="{{ t('settings.aiProvider.providerHelp') }}"></i>
|
||||
</div>
|
||||
<div class="setting-control select-control">
|
||||
<select id="llmProvider" onchange="settingsManager.saveSelectSetting('llmProvider', 'llm_provider')">
|
||||
<option value="openai">OpenAI</option>
|
||||
<option value="ollama">Ollama (local)</option>
|
||||
<option value="custom">{{ t('settings.aiProvider.custom') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<label for="llmApiBase">{{ t('settings.aiProvider.apiBase') }}</label>
|
||||
<i class="fas fa-info-circle info-icon" data-tooltip="{{ t('settings.aiProvider.apiBaseHelp') }}"></i>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<div class="text-input-wrapper">
|
||||
<input type="text" id="llmApiBase"
|
||||
value="{{ settings.get('llm_api_base', '') }}"
|
||||
placeholder="{{ t('settings.aiProvider.apiBasePlaceholder') }}"
|
||||
onblur="settingsManager.saveInputSetting('llmApiBase', 'llm_api_base')"
|
||||
onkeydown="if(event.key === 'Enter') { this.blur(); }" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item api-key-item">
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<label>{{ t('settings.aiProvider.apiKey') }}</label>
|
||||
<i class="fas fa-info-circle info-icon" data-tooltip="{{ t('settings.aiProvider.apiKeyHelp') }}"></i>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<div id="llmApiKeyStatus" class="api-key-status">
|
||||
<span id="llmApiKeyStatusText" class="api-key-status-text api-key-status--unconfigured">
|
||||
<i class="fas fa-times-circle text-error"></i>
|
||||
{{ t('settings.aiProvider.apiKeyNotSet') }}
|
||||
</span>
|
||||
<button type="button" class="secondary-btn" id="llmApiKeyActionBtn" onclick="settingsManager.editApiKey('llm_api_key', 'llmApiKey')">
|
||||
{{ t('settings.aiProvider.apiKeySet') }}
|
||||
</button>
|
||||
</div>
|
||||
<div id="llmApiKeyEdit" class="api-key-edit is-hidden">
|
||||
<div class="api-key-input">
|
||||
<input type="text"
|
||||
id="llmApiKey"
|
||||
class="api-key-masked"
|
||||
placeholder="{{ t('settings.aiProvider.apiKeyPlaceholder') }}"
|
||||
autocomplete="off"
|
||||
data-mask="css" />
|
||||
<button type="button" class="toggle-visibility">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" class="primary-btn" onclick="settingsManager.saveApiKey('llm_api_key', 'llmApiKey')">{{ t('common.actions.save') }}</button>
|
||||
<button type="button" class="secondary-btn" onclick="settingsManager.cancelEditApiKey(true, 'llmApiKey')">{{ t('common.actions.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<label for="llmModel">{{ t('settings.aiProvider.model') }}</label>
|
||||
<i class="fas fa-info-circle info-icon" data-tooltip="{{ t('settings.aiProvider.modelHelp') }}"></i>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<div class="text-input-wrapper">
|
||||
<input type="text" id="llmModel"
|
||||
value="{{ settings.get('llm_model', '') }}"
|
||||
placeholder="e.g. gpt-4o-mini"
|
||||
onblur="settingsManager.saveInputSetting('llmModel', 'llm_model')"
|
||||
onkeydown="if(event.key === 'Enter') { this.blur(); }" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-subsection">
|
||||
<div class="settings-subsection-header">
|
||||
<h4>{{ t('settings.sections.downloads') }}</h4>
|
||||
|
||||
Reference in New Issue
Block a user