From 04485e384f8db8fe85847acd29d0640129ac951d Mon Sep 17 00:00:00 2001 From: Will Miao Date: Wed, 9 Sep 2026 17:30:42 +0800 Subject: [PATCH] feat(checkpoints): add Enrich HF Metadata (AI) to card context menu The option only existed in the LoRA page menu. Move updateEnrichMenuItem and enrichWithAgent into ModelContextMenuMixin so both pages share the implementation, and add the menu item to the checkpoints template. --- .../ContextMenu/CheckpointContextMenu.js | 1 + .../components/ContextMenu/LoraContextMenu.js | 78 +------------------ .../ContextMenu/ModelContextMenuMixin.js | 76 ++++++++++++++++++ templates/checkpoints.html | 3 + 4 files changed, 81 insertions(+), 77 deletions(-) diff --git a/static/js/components/ContextMenu/CheckpointContextMenu.js b/static/js/components/ContextMenu/CheckpointContextMenu.js index 0d06f618..0c11991f 100644 --- a/static/js/components/ContextMenu/CheckpointContextMenu.js +++ b/static/js/components/ContextMenu/CheckpointContextMenu.js @@ -25,6 +25,7 @@ export class CheckpointContextMenu extends BaseContextMenu { showMenu(x, y, card) { super.showMenu(x, y, card); this.updateExcludeMenuItem(); + this.updateEnrichMenuItem(card); // Update the "Move to other root" label based on current model type const moveOtherItem = this.menu.querySelector('[data-action="move-other"]'); diff --git a/static/js/components/ContextMenu/LoraContextMenu.js b/static/js/components/ContextMenu/LoraContextMenu.js index b9806cee..6a4bb9c5 100644 --- a/static/js/components/ContextMenu/LoraContextMenu.js +++ b/static/js/components/ContextMenu/LoraContextMenu.js @@ -1,8 +1,7 @@ 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 { copyLoraSyntax, sendLoraToWorkflow, buildLoraSyntax } from '../../utils/uiHelpers.js'; import { showExcludeModal, showDeleteModal } from '../../utils/modalUtils.js'; import { moveManager } from '../../managers/MoveManager.js'; @@ -27,16 +26,6 @@ export class LoraContextMenu extends BaseContextMenu { 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)) { @@ -75,9 +64,6 @@ export class LoraContextMenu extends BaseContextMenu { 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; @@ -87,68 +73,6 @@ export class LoraContextMenu extends BaseContextMenu { } } - 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 || '{}'); diff --git a/static/js/components/ContextMenu/ModelContextMenuMixin.js b/static/js/components/ContextMenu/ModelContextMenuMixin.js index abd33ed9..42a116c7 100644 --- a/static/js/components/ContextMenu/ModelContextMenuMixin.js +++ b/static/js/components/ContextMenu/ModelContextMenuMixin.js @@ -278,6 +278,79 @@ export const ModelContextMenuMixin = { setTimeout(() => urlInput.focus(), 50); }, + // HF metadata enrichment (AI agent) methods + 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 → Link to HuggingFace)'; + }, + + 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'); + } + }, + parseModelId(value) { if (value === undefined || value === null || value === '') { return null; @@ -388,6 +461,9 @@ export const ModelContextMenuMixin = { case 'link-hf': this.showLinkHfModal(); return true; + case 'enrich-hf-llm': + this.enrichWithAgent(this.currentCard.dataset.filepath); + return true; case 'set-nsfw': this.showNSFWLevelSelector(null, null, this.currentCard); return true; diff --git a/templates/checkpoints.html b/templates/checkpoints.html index 9f1d265f..1518f9a4 100644 --- a/templates/checkpoints.html +++ b/templates/checkpoints.html @@ -25,6 +25,9 @@ +
+ {{ t('loras.contextMenu.enrichHfAgent') }} +
{{ t('loras.contextMenu.copyFilename') }}