From 3f36ebee896ad09f23736fcc501dc2edd67b0d34 Mon Sep 17 00:00:00 2001 From: DemonGatanjieu <207458273+DemonGatanjieu@users.noreply.github.com> Date: Thu, 29 Oct 2026 15:26:36 +0800 Subject: [PATCH] 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 --- ARCHITECTURE.md | 3 ++- docs/architecture/lessons.md | 10 ++++++++++ web/modules/file_size.js | 7 +++++++ web/modules/ui_ai_blocks.js | 2 +- web/modules/ui_ai_outputs.js | 2 +- web/modules/ui_backup.js | 2 +- web/modules/ui_backup_downloads.js | 3 ++- web/modules/ui_gallery_card.js | 2 +- web/modules/ui_gallery_viewer.js | 2 +- web/modules/ui_model_download.js | 8 +------- web/modules/ui_model_import.js | 3 ++- web/modules/ui_model_tidy.js | 3 ++- web/modules/ui_model_usage.js | 2 +- web/modules/ui_recipe_package.js | 2 +- web/modules/ui_scan_picker.js | 2 +- 15 files changed, 34 insertions(+), 19 deletions(-) create mode 100644 web/modules/file_size.js diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 22588f9..3057233 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -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 diff --git a/docs/architecture/lessons.md b/docs/architecture/lessons.md index a7bee1d..ae56674 100644 --- a/docs/architecture/lessons.md +++ b/docs/architecture/lessons.md @@ -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`,再提交。 diff --git a/web/modules/file_size.js b/web/modules/file_size.js new file mode 100644 index 0000000..3b85fe9 --- /dev/null +++ b/web/modules/file_size.js @@ -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`; +} diff --git a/web/modules/ui_ai_blocks.js b/web/modules/ui_ai_blocks.js index 804b0c8..f7b0f71 100644 --- a/web/modules/ui_ai_blocks.js +++ b/web/modules/ui_ai_blocks.js @@ -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 } diff --git a/web/modules/ui_ai_outputs.js b/web/modules/ui_ai_outputs.js index 8211fc1..27bef50 100644 --- a/web/modules/ui_ai_outputs.js +++ b/web/modules/ui_ai_outputs.js @@ -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'; diff --git a/web/modules/ui_backup.js b/web/modules/ui_backup.js index dcc11c4..f8fc7df 100644 --- a/web/modules/ui_backup.js +++ b/web/modules/ui_backup.js @@ -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']; diff --git a/web/modules/ui_backup_downloads.js b/web/modules/ui_backup_downloads.js index bed64f2..5eb5280 100644 --- a/web/modules/ui_backup_downloads.js +++ b/web/modules/ui_backup_downloads.js @@ -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 diff --git a/web/modules/ui_gallery_card.js b/web/modules/ui_gallery_card.js index 4e5633d..b3cdd85 100644 --- a/web/modules/ui_gallery_card.js +++ b/web/modules/ui_gallery_card.js @@ -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 = ``; const PLAY_ICON = ''; diff --git a/web/modules/ui_gallery_viewer.js b/web/modules/ui_gallery_viewer.js index eb90228..8d14b57 100644 --- a/web/modules/ui_gallery_viewer.js +++ b/web/modules/ui_gallery_viewer.js @@ -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) { diff --git a/web/modules/ui_model_download.js b/web/modules/ui_model_download.js index 99315df..7807be5 100644 --- a/web/modules/ui_model_download.js +++ b/web/modules/ui_model_download.js @@ -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(); diff --git a/web/modules/ui_model_import.js b/web/modules/ui_model_import.js index 15d1b2d..7610e4a 100644 --- a/web/modules/ui_model_import.js +++ b/web/modules/ui_model_import.js @@ -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'; diff --git a/web/modules/ui_model_tidy.js b/web/modules/ui_model_tidy.js index 18f0d3e..94b824e 100644 --- a/web/modules/ui_model_tidy.js +++ b/web/modules/ui_model_tidy.js @@ -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) { diff --git a/web/modules/ui_model_usage.js b/web/modules/ui_model_usage.js index 5fa9d4b..8e20a02 100644 --- a/web/modules/ui_model_usage.js +++ b/web/modules/ui_model_usage.js @@ -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'; diff --git a/web/modules/ui_recipe_package.js b/web/modules/ui_recipe_package.js index 43b53d4..6a469f9 100644 --- a/web/modules/ui_recipe_package.js +++ b/web/modules/ui_recipe_package.js @@ -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) { diff --git a/web/modules/ui_scan_picker.js b/web/modules/ui_scan_picker.js index 799a769..60047b3 100644 --- a/web/modules/ui_scan_picker.js +++ b/web/modules/ui_scan_picker.js @@ -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 = {