Files
DemonGatanjieu-Anomalous_Mo…/web/modules/ui_toolbox.js
T
DemonGatanjieuandClaude Opus 5.5 fb21e6c6e9 feat(scan): the scan page, scan runner and scanner changes (completes 5af9b6d)
5af9b6d was committed with only the removal of ui_scan_wizard.js staged;
this adds everything its message describes, so the branch works again:

- ui_scan_page.js / scan_runner.js / 18-scan-page.css: the scan page with
  matched / unmatched / not-scanned counts, one scan button, "look up the
  unmatched models again", progress in the page, Advanced options.
- api/scan_summary.py: GET /anomalous/scan_summary.
- scraper.py: --retry-unmatched (saved hash when the file is unchanged,
  covers kept); display names set in the editor survive scans.
- api/scanner.py: one request-to-switch mapping (_scraper_flags) and one
  cache-clearing helper for both scan routes.
- model_identity.py: sidecar_info / is_unmatched shared by scraper and API.
- Shell wiring (rail, Home, panel lists, close), the provenance switch in
  Settings -> Model settings, locale keys, docs and CHANGELOG.

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

283 lines
11 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.enterToolPage?.('doctor');
owner.hideAllPanels();
owner.doctorPanel.style.display = 'flex';
if (!owner.doctorPanelInitialized) {
owner.initDoctorPanel();
}
if (window.anomalous_reload_hashes) await window.anomalous_reload_hashes();
if (window.anomalous_resolve_all_missing_nodes) {
await window.anomalous_resolve_all_missing_nodes(true, false);
}
owner.renderGlobalDashboard();
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();
},
};
}