A bar over the canvas appears when an opened workflow uses models this computer does not have: how many, and how many are here under another name or folder (same hash), with one press to put those in. The doctor page is redone on the scan page's layout: what needs you first, why in plain words, and a button per row (put in, use a same-size file, pick one, look it up on Civitai); models that are fine fold away. - model_check.js: the check (no DOM, changes nothing) and the fixes, which call the node's widget hooks so ComfyUI drops its red mark; the node picker does the same now. - Opening the doctor no longer rewrites the workflow by itself. - Removed: the auto-detect switch (nothing read it), the doctor's own deep hash scan and its routes (the resolver hashes same-size files on demand; the page points to the scan page), the hash dialog, the old resolver in hash_resolver.js, the unused pick-for-node path in ui_detail.js, 45 old strings. - Node Assistant's diagnoseNode and LoRA insertion moved to ui_node_assistant.js. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
273 lines
10 KiB
JavaScript
273 lines
10 KiB
JavaScript
/**
|
|
* Toolbox catalog, fixed shortcut actions, and tool dispatch.
|
|
*/
|
|
|
|
import { app } from "../../../scripts/app.js";
|
|
import { translate } from './locales.js';
|
|
import { openModelSourcesModal } from './ui_model_sources.js';
|
|
import { CATALOG_TOOLS, getToolDefinition } from './tool_registry.js';
|
|
import { setScanButtonState } from './scan_runner.js';
|
|
|
|
const t = (key, params) => translate(key, params);
|
|
|
|
export function createToolbox(owner, {
|
|
container,
|
|
toolboxIcon,
|
|
isScanning,
|
|
getSettingsButton,
|
|
onBeforeOpen,
|
|
}) {
|
|
const toolboxBtn = document.createElement('button');
|
|
toolboxBtn.id = 'anomalous-toolbox-btn';
|
|
toolboxBtn.className = 'anomalous-tooltip-target';
|
|
toolboxBtn.removeAttribute('title');
|
|
toolboxBtn.setAttribute('aria-label', t('sidebarToolbox'));
|
|
toolboxBtn.setAttribute('data-tooltip', t('sidebarToolbox'));
|
|
toolboxBtn.setAttribute('data-tooltip-pos', 'right');
|
|
toolboxBtn.innerHTML = toolboxIcon;
|
|
|
|
|
|
const toolboxModal = document.createElement('div');
|
|
toolboxModal.id = 'anomalous-toolbox-modal';
|
|
toolboxModal.style.display = 'none';
|
|
|
|
const closeToolbox = (e) => {
|
|
if (toolboxModal.style.display !== 'none' && !toolboxModal.contains(e.target) && !toolboxBtn.contains(e.target)) {
|
|
toolboxModal.style.display = 'none';
|
|
toolboxBtn.classList.remove('is-active');
|
|
document.removeEventListener('mousedown', closeToolbox);
|
|
}
|
|
};
|
|
|
|
toolboxBtn.onclick = (e) => {
|
|
e.stopPropagation();
|
|
onBeforeOpen();
|
|
if (toolboxModal.style.display === 'none') {
|
|
toolboxModal.style.display = 'flex';
|
|
toolboxBtn.classList.add('is-active');
|
|
setTimeout(() => document.addEventListener('mousedown', closeToolbox), 10);
|
|
} else {
|
|
toolboxModal.style.display = 'none';
|
|
toolboxBtn.classList.remove('is-active');
|
|
document.removeEventListener('mousedown', closeToolbox);
|
|
}
|
|
};
|
|
|
|
const executeToolAction = async (toolId) => {
|
|
switch (toolId) {
|
|
case 'scan':
|
|
owner.openScanPage();
|
|
break;
|
|
case 'doctor':
|
|
owner.openDoctorPage();
|
|
break;
|
|
case 'assistant':
|
|
owner.enterToolPage?.('assistant');
|
|
owner.hideAllPanels();
|
|
owner.assistantPanel.style.display = 'flex';
|
|
if (!owner.assistantPanelInitialized) {
|
|
owner.initAssistantPanel();
|
|
}
|
|
if (Object.keys(app.canvas?.selected_nodes || {}).length > 0) {
|
|
const firstSelected = Object.values(app.canvas.selected_nodes)[0];
|
|
owner.diagnoseNode(firstSelected);
|
|
} else {
|
|
owner.diagnoseNode(null);
|
|
}
|
|
break;
|
|
case 'materials':
|
|
owner.goTo('materials');
|
|
break;
|
|
case 'workflow-transfer':
|
|
if (window.AMB_WorkflowShare && typeof window.AMB_WorkflowShare.showUnifiedModal === 'function') {
|
|
window.AMB_WorkflowShare.showUnifiedModal();
|
|
}
|
|
break;
|
|
case 'prompt-studio':
|
|
if (typeof owner.openPromptStudio === 'function') {
|
|
owner.openPromptStudio();
|
|
} else if (typeof owner.openMaterialLibrary === 'function') {
|
|
owner.openMaterialLibrary();
|
|
}
|
|
break;
|
|
case 'prompt-translator':
|
|
if (typeof owner.openPromptTranslator === 'function') {
|
|
owner.openPromptTranslator();
|
|
}
|
|
break;
|
|
case 'model-sources':
|
|
openModelSourcesModal('workflow');
|
|
break;
|
|
case 'prompt-notes':
|
|
if (typeof owner.showNotebooks === 'function') {
|
|
owner.showNotebooks();
|
|
}
|
|
break;
|
|
default: {
|
|
const custom = (owner.customToolboxItems || []).find(it => it.id === toolId);
|
|
if (custom && typeof custom.action === 'function') {
|
|
custom.action();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
owner.executeToolAction = executeToolAction;
|
|
|
|
const renderShortcutActions = () => {
|
|
owner.railTools.replaceChildren();
|
|
|
|
// Materials has its own page on the rail; the toolbox comes last, settings at the bottom.
|
|
const defaultLayout = ['scan', 'doctor', 'assistant'];
|
|
for (const toolId of defaultLayout) {
|
|
const def = getToolDefinition(toolId);
|
|
if (!def) continue;
|
|
|
|
const btn = document.createElement('button');
|
|
btn.id = def.domId;
|
|
btn.setAttribute('data-tool-id', toolId);
|
|
btn.className = 'anomalous-tooltip-target';
|
|
btn.removeAttribute('title');
|
|
btn.setAttribute('aria-label', t(def.nameKey));
|
|
btn.setAttribute('data-tooltip', t(def.nameKey));
|
|
btn.setAttribute('data-tooltip-pos', 'right');
|
|
|
|
if (toolId === 'scan') {
|
|
setScanButtonState(btn, isScanning());
|
|
} else {
|
|
btn.innerHTML = def.icon;
|
|
}
|
|
|
|
btn.onclick = (e) => {
|
|
e.stopPropagation();
|
|
executeToolAction(toolId);
|
|
};
|
|
|
|
owner.railTools.appendChild(btn);
|
|
}
|
|
|
|
owner.railTools.appendChild(toolboxBtn);
|
|
owner.railFoot.replaceChildren(getSettingsButton());
|
|
};
|
|
owner.renderShortcutActions = renderShortcutActions;
|
|
|
|
const renderToolboxModal = () => {
|
|
toolboxModal.replaceChildren();
|
|
|
|
const headerRow = document.createElement('div');
|
|
headerRow.style.display = 'flex';
|
|
headerRow.style.alignItems = 'center';
|
|
headerRow.style.justifyContent = 'space-between';
|
|
headerRow.style.padding = '1px 2px 3px';
|
|
headerRow.style.borderBottom = '1px solid rgba(255, 255, 255, 0.06)';
|
|
|
|
const titleBox = document.createElement('div');
|
|
titleBox.style.display = 'flex';
|
|
titleBox.style.alignItems = 'center';
|
|
titleBox.style.gap = '5px';
|
|
|
|
const titleIcon = document.createElement('span');
|
|
titleIcon.textContent = '🧰';
|
|
titleIcon.style.fontSize = '11px';
|
|
|
|
const titleText = document.createElement('span');
|
|
titleText.textContent = t('toolboxTitle');
|
|
titleText.style.fontWeight = '600';
|
|
titleText.style.fontSize = '11px';
|
|
titleText.style.color = '#fff';
|
|
|
|
titleBox.append(titleIcon, titleText);
|
|
|
|
const closeModalBtn = document.createElement('div');
|
|
closeModalBtn.innerHTML = '×';
|
|
closeModalBtn.style.cursor = 'pointer';
|
|
closeModalBtn.style.color = '#888';
|
|
closeModalBtn.style.fontSize = '14px';
|
|
closeModalBtn.style.lineHeight = '1';
|
|
closeModalBtn.style.padding = '1px 3px';
|
|
closeModalBtn.style.borderRadius = '3px';
|
|
closeModalBtn.onmouseover = () => { closeModalBtn.style.color = '#fff'; closeModalBtn.style.background = 'rgba(255,255,255,0.08)'; };
|
|
closeModalBtn.onmouseout = () => { closeModalBtn.style.color = '#888'; closeModalBtn.style.background = 'transparent'; };
|
|
closeModalBtn.onclick = () => {
|
|
toolboxModal.style.display = 'none';
|
|
toolboxBtn.classList.remove('is-active');
|
|
};
|
|
|
|
headerRow.appendChild(titleBox);
|
|
headerRow.appendChild(closeModalBtn);
|
|
toolboxModal.appendChild(headerRow);
|
|
|
|
const gridContainer = document.createElement('div');
|
|
gridContainer.className = 'anomalous-toolbox-grid';
|
|
|
|
// 已经在左侧图标栏上的工具(扫描、医生、助手、素材、工具箱、设置)不再放进工具箱
|
|
const outsideToolIds = new Set(['scan', 'doctor', 'assistant', 'materials', 'toolbox', 'settings']);
|
|
const allTools = [...CATALOG_TOOLS, ...(owner.customToolboxItems || [])];
|
|
const toolboxTools = allTools.filter(tool => !outsideToolIds.has(tool.id));
|
|
|
|
const COLOR_ICONS = {
|
|
'workflow-transfer': '🔄',
|
|
'prompt-studio': '🎛️',
|
|
'prompt-translator': '🌐',
|
|
'model-sources': '🔗',
|
|
'prompt-notes': '📝',
|
|
};
|
|
|
|
toolboxTools.forEach(tool => {
|
|
const tile = document.createElement('div');
|
|
tile.className = 'anomalous-toolbox-tile';
|
|
tile.setAttribute('data-tool-id', tool.id);
|
|
|
|
const iconEl = document.createElement('div');
|
|
iconEl.className = 'anomalous-toolbox-tile-icon';
|
|
iconEl.innerHTML = COLOR_ICONS[tool.id] || tool.icon || '🔧';
|
|
tile.appendChild(iconEl);
|
|
|
|
const labelEl = document.createElement('div');
|
|
labelEl.className = 'anomalous-toolbox-tile-label';
|
|
labelEl.textContent = t(tool.labelKey) || t(tool.nameKey);
|
|
tile.appendChild(labelEl);
|
|
|
|
tile.onclick = (e) => {
|
|
e.stopPropagation();
|
|
toolboxModal.style.display = 'none';
|
|
toolboxBtn.classList.remove('is-active');
|
|
executeToolAction(tool.id);
|
|
};
|
|
|
|
gridContainer.appendChild(tile);
|
|
});
|
|
|
|
toolboxModal.appendChild(gridContainer);
|
|
};
|
|
owner.renderToolboxModal = renderToolboxModal;
|
|
owner.registerToolboxItem = (item) => {
|
|
owner.customToolboxItems = owner.customToolboxItems || [];
|
|
owner.customToolboxItems.push(item);
|
|
if (owner.renderToolboxModal) owner.renderToolboxModal();
|
|
};
|
|
owner.openModelSources = (scope = 'workflow') => openModelSourcesModal(scope);
|
|
|
|
container.appendChild(toolboxModal);
|
|
|
|
const close = () => {
|
|
toolboxModal.style.display = 'none';
|
|
toolboxBtn.classList.remove('is-active');
|
|
document.removeEventListener('mousedown', closeToolbox);
|
|
};
|
|
|
|
return {
|
|
button: toolboxBtn,
|
|
modal: toolboxModal,
|
|
close,
|
|
mount() {
|
|
renderToolboxModal();
|
|
renderShortcutActions();
|
|
},
|
|
refreshLanguage() {
|
|
renderToolboxModal();
|
|
renderShortcutActions();
|
|
},
|
|
};
|
|
}
|