Files
DemonGatanjieu-Anomalous_Mo…/web/modules/browser.js
T
DemonGatanjieuandClaude Opus 5.5 302fe7e6e8 Keep gallery results as a workflow, a combo or prompts
Every PNG in the gallery has a star that asks what to keep: the whole
workflow as a Workflow Recipe, its main model, LoRAs and positive prompt
as a combo (models looked up on this computer, missing ones left out), or
its prompts as a saved prompt. What is kept remembers its image, so
GET /anomalous/kept_images fills the star and the menu opens what was
kept. The image workbench's Keep opens the same menu instead of its name
and tags form. Things are named after the prompt's first tags.

The Material Library's Recent shelf and its route are gone: the gallery
lists the same images. Gallery cards moved to ui_gallery_card.js, and
deleting asks in the usual dialog. Two missing locale keys are fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 22:32:51 +08:00

218 lines
11 KiB
JavaScript

import { showDetail } from './ui_detail.js';
import { showEditModal } from './ui_model_editor.js';
import { _openAdvancedModelSelector, setWidgetValuePath } from './ui_model_selector.js';
import { loadModels, applyModelToCanvas, stopMediaInContainer } from './ui_grid.js';
import { createDOM, renderSidebar, loadFolders } from './ui_sidebar.js';
import { closeWorkspace, hideAllPanels } from './ui_browser_navigation.js';
import { triggerDirectModelScan } from './scan_runner.js';
import { openScanPage, renderScanPage, leaveScanPage } from './ui_scan_page.js';
import { loadGalleryImages, refreshGalleryImages, showGeneratedGallery, showGallerySelectMode, showGalleryViewer } from './ui_gallery.js';
import { showNotebooks, openCombo, openComboByFilename, refreshNotebooks, saveCurrentNotebook, deleteCurrentNotebook } from './ui_notebooks.js';
import { renderNotebookEditor, fillNotebookGalleries } from './ui_notebook_editor.js';
import { useNotebook, sendNotebookToCanvas } from './notebook_canvas.js';
import { showRecipes, refreshRecipes } from './ui_recipe_catalog.js';
import { renderRecipeList, handleSaveRecipe, openRecipeByFilename } from './ui_recipes.js';
import { showMaterials, refreshMaterials, openSavedMaterial, openMaterialLibrary } from './ui_materials.js';
import { openPromptStudio } from './ui_prompt_composer.js';
import { closeUpdateGuide } from './ui_update_guide.js';
import { showImageWorkbench } from './ui_gallery_detail.js';
import { openDoctorPage } from './ui_doctor.js';
import { showModelSources } from './ui_model_sources.js';
import { initAssistantPanel, renderAssistantModelCard, _loadAssistantHistory, diagnoseNode, openCurrentNode, openLoraInsertionPicker } from './ui_node_assistant.js';
import { _openGalleryReplacer } from './ui_node_model_picker.js';
import { renderAudioStudio, stopAudioStudioPlayback } from './ui_audio_studio.js';
import { renderAudioGallery, stopGalleryAudio } from './ui_audio_gallery.js';
import { renderScriptPage } from './ui_script_page.js';
import { getActiveAudioFilter, setActiveAudioFilter, syncAudioSidebarSelection } from './ui_audio_sidebar.js';
import { voiceGroupKey } from './audio_engines.js';
import { getActiveDomain } from './ui_domain_switcher.js';
import { startPage } from './ui_shell_nav.js';
import { followComfyLanguage } from './interface_settings.js';
export class AnomalousBrowser {
constructor() {
this.modal = null;
this.sidebar = null;
this.grid = null;
this.detailPanel = null;
this.currentType = 'loras';
this.currentPathIdx = 0;
this.currentSubfolder = '/';
this.foldersData = null;
this.expandedFolders = new Set(['/', 'checkpoints', 'loras', 'unet', 'diffusion_models']);
// Video covers play on hover unless you chose "always": fewer videos decoding at once.
this.energySaving = localStorage.getItem('anomalous_energy_saving') !== 'false';
this.cardThumbnailMode = localStorage.getItem('anomalous_card_thumbnail_mode') === 'original'
? 'original'
: 'balanced';
this.entryMode = 'floating';
this.createDOM();
}
show() {
followComfyLanguage();
if (this._idleReleaseTimer) {
clearTimeout(this._idleReleaseTimer);
this._idleReleaseTimer = null;
}
this.setTriggerVisible(false);
this.modal.classList.add('visible');
if (!this.currentShellPage()) {
this.goTo(startPage());
return;
}
// Opened again: stay on the page, refresh what may have changed meanwhile.
if (getActiveDomain() === 'audio') {
const audioTabs = { 'audio-gallery': 'gallery', script: 'script' };
this.switchAudioTab(audioTabs[this.currentShellPage()] || 'presets');
} else if (!this.foldersData) {
this.loadFolders();
} else {
this.loadModels();
}
if (this.currentShellPage() === 'gallery') void this.refreshGalleryImages();
if (this.currentShellPage() === 'scan') void renderScanPage(this, this.scanPanel);
}
/** The image / audio switch: the first page of that side. */
handleDomainChange(domain) {
this.goTo(domain === 'audio' ? 'voices' : 'models');
}
/** Single entry for audio-domain navigation: the rail, the audio list's entries and the domain switch. */
switchAudioTab(tabName, filter = null) {
if (!(tabName === 'presets' && filter?.type === 'group' && this.currentShellPage() === 'script')) this.hideAllPanels();
if (tabName === 'gallery') {
setActiveAudioFilter({ type: 'gallery', value: null });
this.markShellPage?.('audio-gallery');
this.audioGalleryPanel.style.display = 'block';
renderAudioGallery(this.audioGalleryPanel, { owner: this });
} else if (tabName === 'script') {
this.markShellPage?.('script');
this.scriptPanel.style.display = 'flex';
const pending = this.pendingScript || {};
this.pendingScript = null;
renderScriptPage(this.scriptPanel, this, pending);
} else if (filter?.type === 'group' && this.currentShellPage() === 'script') {
// A character picked in the list while on Voice-over: it speaks there.
this.openScript(filter.value);
return;
} else {
if (filter) setActiveAudioFilter(filter);
else if (getActiveAudioFilter().type === 'gallery') setActiveAudioFilter(null);
this.markShellPage?.('voices');
this.audioStudioPanel.style.display = 'block';
renderAudioStudio(this.audioStudioPanel, { owner: this });
}
syncAudioSidebarSelection(this);
}
/** One character's voice card: the Anomalous_TTS node's "import or edit characters". */
openVoice(character) {
const filter = character ? { type: 'group', value: voiceGroupKey(character), character } : null;
if (this.currentShellPage() === 'voices') {
this.switchAudioTab('presets', filter);
return;
}
if (filter) setActiveAudioFilter(filter);
this.goTo('voices'); // keeps the filter just set
}
/**
* The Voices page's Voice-over view. `group`: a voice group key to choose (a card, the list);
* `script`: `{ speech, subfolder }` of a generated file to voice again (the audio gallery).
*/
openScript(group = null, script = null) {
this.pendingScript = { character: group, script };
if (this.currentShellPage() === 'script') this.switchAudioTab('script');
else this.goTo('script');
}
close() {
this.flushCanvasActivity?.();
closeUpdateGuide(this);
leaveScanPage(); // a running scan's progress floats over the canvas
this.modal.classList.remove('visible');
this.setTriggerVisible(true);
stopAudioStudioPlayback();
stopGalleryAudio();
const canvas = document.getElementById('graph-canvas');
if (canvas instanceof HTMLElement) canvas.focus({ preventScroll: true });
if (this._modelLoadController) this._modelLoadController.abort();
if (this._modelMediaObserver) this._modelMediaObserver.disconnect();
this.modal.querySelectorAll('video, audio').forEach(media => media.pause());
this.stopMediaInContainer(this.grid);
if (this._idleReleaseTimer) clearTimeout(this._idleReleaseTimer);
this._idleReleaseTimer = setTimeout(() => {
if (this.modal.classList.contains('visible')) return;
this.stopMediaInContainer(this.grid);
this.grid.replaceChildren();
this.models = [];
// The output gallery reloads its first page when opened again.
this.galleryGrid.querySelectorAll('.anomalous-gallery-card').forEach(card => card.remove());
this.galleryImagesList = [];
}, 90000);
}
setTriggerVisible(visible) {
const trigger = this.triggerButton || document.getElementById('anomalous-trigger-btn');
trigger?.classList.toggle('anomalous-trigger-hidden', !visible || this.entryMode !== 'floating');
}
}
AnomalousBrowser.prototype.openDoctorPage = function () { openDoctorPage(this); };
AnomalousBrowser.prototype.showModelSources = function (scope) { showModelSources(this, scope); };
AnomalousBrowser.prototype.diagnoseNode = diagnoseNode;
AnomalousBrowser.prototype.openCurrentNode = function () { openCurrentNode(this); };
AnomalousBrowser.prototype.initAssistantPanel = initAssistantPanel;
AnomalousBrowser.prototype.renderAssistantModelCard = renderAssistantModelCard;
AnomalousBrowser.prototype._loadAssistantHistory = _loadAssistantHistory;
AnomalousBrowser.prototype._openGalleryReplacer = _openGalleryReplacer;
AnomalousBrowser.prototype.openLoraInsertionPicker = openLoraInsertionPicker;
AnomalousBrowser.prototype.showNotebooks = showNotebooks;
AnomalousBrowser.prototype.openCombo = openCombo;
AnomalousBrowser.prototype.openComboByFilename = openComboByFilename;
AnomalousBrowser.prototype.closeWorkspace = closeWorkspace;
AnomalousBrowser.prototype.refreshNotebooks = refreshNotebooks;
AnomalousBrowser.prototype.saveCurrentNotebook = saveCurrentNotebook;
AnomalousBrowser.prototype.deleteCurrentNotebook = deleteCurrentNotebook;
AnomalousBrowser.prototype.renderNotebookEditor = renderNotebookEditor;
AnomalousBrowser.prototype.fillNotebookGalleries = fillNotebookGalleries;
AnomalousBrowser.prototype.sendNotebookToCanvas = sendNotebookToCanvas;
AnomalousBrowser.prototype.useNotebook = useNotebook;
AnomalousBrowser.prototype.showRecipes = showRecipes;
AnomalousBrowser.prototype.openRecipeByFilename = openRecipeByFilename;
AnomalousBrowser.prototype.refreshRecipes = refreshRecipes;
AnomalousBrowser.prototype.renderRecipeList = renderRecipeList;
AnomalousBrowser.prototype.handleSaveRecipe = handleSaveRecipe;
AnomalousBrowser.prototype.showMaterials = showMaterials;
AnomalousBrowser.prototype.openSavedMaterial = openSavedMaterial;
AnomalousBrowser.prototype.openMaterialLibrary = openMaterialLibrary;
AnomalousBrowser.prototype.openPromptStudio = openPromptStudio;
AnomalousBrowser.prototype.refreshMaterials = refreshMaterials;
AnomalousBrowser.prototype.loadGalleryImages = loadGalleryImages;
AnomalousBrowser.prototype.refreshGalleryImages = refreshGalleryImages;
AnomalousBrowser.prototype.showGeneratedGallery = showGeneratedGallery;
AnomalousBrowser.prototype.showGallerySelectMode = showGallerySelectMode;
AnomalousBrowser.prototype.showGalleryViewer = showGalleryViewer;
AnomalousBrowser.prototype.showImageWorkbench = showImageWorkbench;
AnomalousBrowser.prototype.createDOM = createDOM;
AnomalousBrowser.prototype.openScanPage = function (view) { openScanPage(this, view); };
AnomalousBrowser.prototype.scanSingleModel = triggerDirectModelScan;
AnomalousBrowser.prototype.renderSidebar = renderSidebar;
AnomalousBrowser.prototype.loadFolders = loadFolders;
AnomalousBrowser.prototype.hideAllPanels = hideAllPanels;
AnomalousBrowser.prototype.loadModels = loadModels;
AnomalousBrowser.prototype.applyModelToCanvas = applyModelToCanvas;
AnomalousBrowser.prototype.stopMediaInContainer = stopMediaInContainer;
AnomalousBrowser.prototype.showDetail = showDetail;
AnomalousBrowser.prototype.showEditModal = showEditModal;
AnomalousBrowser.prototype._openAdvancedModelSelector = _openAdvancedModelSelector;
AnomalousBrowser.prototype.setWidgetValuePath = setWidgetValuePath;