Fix AMB not loading: the viewer's size words pulled in a cycle

ui_gallery_viewer.js (8c044d0) imported formatSize from
ui_model_download.js, which leads through the settings page and
browser.js back to ui_ai_workbench.js; with ui_ai_images.js ->
ui_gallery_detail.js -> ui_gallery_viewer.js at the start of that chain,
ui_ai_workbench.js called setMarker before ui_ai_images.js had run, and
the whole plugin failed to load ("Cannot access 'marker' before
initialization"). formatSize now lives in file_size.js, which imports
nothing, and every module takes it from there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DemonGatanjieu
2026-10-29 15:26:36 +08:00
co-authored by Claude Opus 5.5
parent efe281c757
commit 3f36ebee89
15 changed files with 34 additions and 19 deletions
+2 -1
View File
@@ -1096,7 +1096,8 @@ covered by the `styles.css` manifest.
follows the downloads and puts each finished file into its nodes (the placement rules
are `download_places.js`, without imports so they are tested alone); `ui_model_download.js`
is the page's Download buttons, progress and the dialog that shows and changes where
each file goes. `ui_node_assistant.js` owns the current-node panel (model actions, LoRA
each file goes. `file_size.js` says a file's size in KB/MB/GB, without imports: a module low
in the import graph (the gallery's viewer) takes it from there, not from a page module. `ui_node_assistant.js` owns the current-node panel (model actions, LoRA
insertion, model cards and history), `ui_node_model_picker.js` owns native combo
replacement, `ui_node_parameters.js` renders the panel's parameters section (each saved
set as a card with the picture made with it and where it came from, values as bars or
+10
View File
@@ -136,3 +136,13 @@ body:has(#anomalous-toolbox-modal[style*="display: flex"]) #anomalous-sidebar-to
- 缩略图失败的兜底只能是“小文件原样”或占位图,永远不是大原图(`media_routes.ORIGINAL_CARD_BYTES`)。
- 超过 Pillow 上限的图在子进程里缩(`huge_picture.py`),内存用完就还,ComfyUI 进程不碰它。
- 要比缩略图大的地方用 `/anomalous/output_preview`,不用 `/view` 原图;只有用户点“打开原图”才给原图。
## 2026-10-29 · 为一个小函数引出循环导入,AMB 整个页面加载失败
**现象**:隔离的 ComfyUI 上 AMB 没有加载,`window.anomalousBrowserInstance` 不存在,AI 的每个画布操作都报“页面没有及时回应”。控制台:`Cannot access 'marker' before initialization`。
**根因**:看大图的模块 `ui_gallery_viewer.js` 为了 `formatSize` 去导入 `ui_model_download.js`,后者经设置页、`browser.js`、导航一路导回 `ui_ai_workbench.js`;而 `ui_ai_images.js → ui_gallery_detail.js → ui_gallery_viewer.js` 又在这条链的开头。于是 `ui_ai_workbench.js` 在模块顶层调用 `setMarker` 时,`ui_ai_images.js` 还没执行完。结构检查和测试都只看语法和单个模块,抓不到。8197 上看过的页面恰好没触发这个加载顺序。
**以后怎么做**
- 底层模块(看图、卡片、纯函数)只从同样底层的模块取工具函数,不从“页面”模块(下载、设置、浏览器主对象)取,哪怕只是一个小函数;需要就挪到没有导入的小文件里(`file_size.js`)。
- 改了导入关系,在真的 ComfyUI 页面上确认 `window.anomalousBrowserInstance` 存在、控制台没有 `before initialization`,再提交。
+7
View File
@@ -0,0 +1,7 @@
/** A file's size in words people read: KB, MB or GB. No imports, so any module can use it. */
export function formatSize(bytes) {
const value = Number(bytes) || 0;
if (value >= 1024 ** 3) return `${(value / 1024 ** 3).toFixed(2)} GB`;
if (value >= 1024 ** 2) return `${(value / 1024 ** 2).toFixed(1)} MB`;
return `${Math.max(1, Math.round(value / 1024))} KB`;
}
+1 -1
View File
@@ -13,7 +13,7 @@ import { resolveLocale, translate as t } from './locales.js';
import { sideHead } from './ui_ai_side.js';
import { openManager } from './ui_pack_usage.js';
import { downloadFrom } from './model_download.js';
import { formatSize } from './ui_model_download.js';
import { formatSize } from './file_size.js';
let reading = null; // the list being read
let shown = null; // { blocks, jobs } last read, or { error }
+1 -1
View File
@@ -16,7 +16,7 @@ import { imagePrompts } from './image_keep.js';
import { keepPictureAs } from './ui_keep_menu.js';
import { anomalousAlert, copyText } from './ui_dialog.js';
import { showTab } from './mcp_task_tab.js';
import { formatSize } from './ui_model_download.js';
import { formatSize } from './file_size.js';
import { imageResults } from './ui_ai_images.js';
import { icon, iconButton } from './ui_ai_icons.js';
import { openComboNamed, openModel, playButton } from './ui_ai_results.js';
+1 -1
View File
@@ -9,7 +9,7 @@
*/
import { translate as t } from './locales.js';
import { formatSize } from './ui_model_download.js';
import { formatSize } from './file_size.js';
import { openRestoreDownloads } from './ui_backup_downloads.js';
const PARTS = ['recipes', 'combos', 'materials', 'parameters', 'comfy_workflows'];
+2 -1
View File
@@ -8,7 +8,8 @@
import { translate as t } from './locales.js';
import { defaultRoot, downloadFor, fetchDownloadSettings, hfMirrorOn, startDownload, watchDownloads } from './model_download.js';
import { errorText, formatSize, jobView } from './ui_model_download.js';
import { formatSize } from './file_size.js';
import { errorText, jobView } from './ui_model_download.js';
import { openDialog } from './ui_backup.js';
const LOOKUP_BATCH = 100; // the lookup route takes this many at once
+1 -1
View File
@@ -17,7 +17,7 @@ import { keptKey } from './image_keep.js';
import { isComparePicked, pressCompare } from './ui_param_compare.js';
import { openGalleryCardMenu } from './gallery_card_menu.js';
import { isGalleryVideo } from './gallery_filter.js';
import { formatSize } from './ui_model_download.js';
import { formatSize } from './file_size.js';
const DETAILS_ICON = `<svg class="anomalous-gallery-details-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><line x1="2" y1="5" x2="8" y2="5"></line><line x1="12" y1="5" x2="14" y2="5"></line><circle cx="10" cy="5" r="2"></circle><line x1="2" y1="11" x2="4" y2="11"></line><line x1="8" y1="11" x2="14" y2="11"></line><circle cx="6" cy="11" r="2"></circle></svg>`;
const PLAY_ICON = '<svg viewBox="0 0 24 24" width="10" height="10" fill="currentColor" aria-hidden="true"><polygon points="5 3 19 12 5 21 5 3"/></svg>';
+1 -1
View File
@@ -7,7 +7,7 @@
*/
import { translate as t } from './locales.js';
import { formatSize } from './ui_model_download.js';
import { formatSize } from './file_size.js';
/** Where a picture address is shown from: an output picture's (`/view?…&type=output`) through output_preview, others as they are. */
export function shownUrl(src) {
+1 -7
View File
@@ -9,6 +9,7 @@
*/
import { translate as t } from './locales.js';
import { formatSize } from './file_size.js';
import { destinationFor, unsafeFormat } from './download_places.js';
import {
cancelDownload, defaultRoot, doneCount, downloadFor, hfSearchUrl, lookupDownloads, rememberRoot, startDownload, syncDownloads,
@@ -36,13 +37,6 @@ function button(className, label, onClick) {
return node;
}
export function formatSize(bytes) {
const value = Number(bytes) || 0;
if (value >= 1024 ** 3) return `${(value / 1024 ** 3).toFixed(2)} GB`;
if (value >= 1024 ** 2) return `${(value / 1024 ** 2).toFixed(1)} MB`;
return `${Math.max(1, Math.round(value / 1024))} KB`;
}
const fileName = (value) => String(value).split(/[\\/]/).pop();
+2 -1
View File
@@ -12,7 +12,8 @@ import { defaultRoot, rememberRoot } from './model_download.js';
import {
IMPORT_KINDS, afterImport, fetchImportFolders, identifyFile, importDestination, inspectFile, modelFiles, placeFile, uploadFile,
} from './model_import.js';
import { formatSize, joinPath } from './ui_model_download.js';
import { formatSize } from './file_size.js';
import { joinPath } from './ui_model_download.js';
import { createViewScope } from './ui_lifecycle.js';
import { showWorkbenchToast } from './ui_prompt_toast.js';
import { openSettingsPage } from './ui_settings_page.js';
+2 -1
View File
@@ -14,7 +14,8 @@ import { app } from "../../../scripts/app.js";
import { translate as t } from './locales.js';
import { checkPlacement, moveErrorText, moveModel, recycleModel, tidyMisplaced } from './model_placement.js';
import { fetchImportFolders } from './model_import.js';
import { formatSize, joinPath } from './ui_model_download.js';
import { formatSize } from './file_size.js';
import { joinPath } from './ui_model_download.js';
import { renderUsageSection } from './ui_model_usage.js';
function el(tag, className, text) {
+1 -1
View File
@@ -6,7 +6,7 @@
import { translate as t } from './locales.js';
import { recycleModel } from './model_placement.js';
import { formatSize } from './ui_model_download.js';
import { formatSize } from './file_size.js';
import { openSettingsPage } from './ui_settings_page.js';
import { button, el, lastUsed, notUsedSince, usageSection } from './usage_view.js';
+1 -1
View File
@@ -8,7 +8,7 @@
import { translate as t } from './locales.js';
import { openDialog } from './ui_backup.js';
import { applyRecipeToCanvas } from './ui_recipe_detail.js';
import { formatSize } from './ui_model_download.js';
import { formatSize } from './file_size.js';
import { showWorkbenchToast } from './ui_prompt_toast.js';
function el(tag, className, text) {
+1 -1
View File
@@ -12,7 +12,7 @@
import { translate as t } from './locales.js';
import { createViewScope } from './ui_lifecycle.js';
import { typeLabel } from './ui_model_types.js';
import { formatSize } from './ui_model_download.js';
import { formatSize } from './file_size.js';
const STATES = ['new', 'unmatched', 'damaged', 'matched', 'skip'];
const STATE_KEYS = {