Files
DemonGatanjieu-Anomalous_Mo…/web/modules/ui_toolbox.js
T
DemonGatanjieuandClaude Opus 5.5 2358649f03 feat(doctor): Model Doctor checks each opened workflow and says what to do
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>
2026-10-02 03:36:41 +08:00

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 = '&times;';
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();
},
};
}