Files
DemonGatanjieu-Anomalous_Mo…/web/modules/ui_toolbox.js
T
DemonGatanjieuandClaude Opus 5.5 96736560af feat(node): translate prompts on the node; the separate translator goes
Current node lists the selected node's prompt boxes (prompt_boxes.js, so
each is marked positive or negative by its wiring). Chinese text gets
"translate to English", written back only if the box still holds the same
text when the translation arrives (one undo); English text can show its
Chinese meaning without writing.

The standalone Prompt Translator (ui_prompt_translator.js, 838 lines, its
four fallback ways of guessing a text box, its styles and its toolbox
entry) is removed, with Prompt Studio's appendPromptToStudio that only it
used. Prompt Studio keeps its own translate buttons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 06:14:05 +08:00

267 lines
9.9 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 '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': '🎛️',
'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();
},
};
}