Compare commits

...
2 Commits
Author SHA1 Message Date
DemonGatanjieuandClaude Opus 5.5 8a4c5d5c72 v1.62.1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 01:52:28 +08:00
DemonGatanjieuandClaude Opus 5.5 b9fd30ad72 Fix Anomalous sometimes not starting
ComfyUI imports every script under web/ on its own and at once, so any module
of the big import cycle can run first. ui_ai_workbench.js wrote to a const of
ui_ai_results.js as it loaded, which failed when ui_ai_results.js or
ui_ai_step_body.js was entered first, and the whole plugin went with it until
a reload. The value now lives in a module that imports nothing, and
tests/module_cycles.mjs refuses the pattern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 01:52:28 +08:00
8 changed files with 96 additions and 5 deletions
+1 -1
View File
@@ -363,7 +363,7 @@ covered by the `styles.css` manifest.
task adds `links` {from, next}, begin_task gives an AI the task it continues and what was
decided there (`continues`). `ui_ai_task_links.js` shows them under the task's title and picks
one to link. `tests/test_ai_task_links.py`. `ui_ai_recent.js` the home page's list, `ui_ai_results.js` what a
step made and where it leads. `ui_ai_outputs.js` is what the whole task made: a line over the
step made and where it leads, `ui_ai_shared.js` what the page's parts share. `ui_ai_outputs.js` is what the whole task made: a line over the
timeline counting it, and the side column's Results view (`ai_task_view.taskOutputs`: the
pictures by the step that brought them, the workflow, the combos, the newest picture's prompts,
the models used and download requests — `ai_tasks.results_of` names the downloads — and the sounds),
+5
View File
@@ -1,5 +1,10 @@
# 📈 Anomalous Model Browser Changelog
## v1.62.1 (Fix: Anomalous sometimes did not start) — 2026-11-05
- 修好一个偶发的启动故障:有时打开 ComfyUI,AMB 整个没加载,悬浮按钮和窗口都没有,刷新一下又好了。
- Fixed a startup failure that came now and then: ComfyUI opened with no Anomalous at all, no button and no window, until a reload.
## v1.62.0 (AI Workbench, AI Builds Workflows on the Canvas, Repaint an Area, Materials Side Panel) — 2026-10-29
- 音频库搬进了“角色语音”页:页面顶上的标签现在是 **角色 | 配音 | 音频库**,左边栏少了一项。加了 AI 工作台以后左边栏有 10 项,数字键只有 1–9,音频库就没键可按了;现在 9 打开角色语音,并回到你上次停的那个标签。
+1 -1
View File
@@ -9,7 +9,7 @@
[![ComfyUI Manager](https://img.shields.io/badge/ComfyUI-Manager-green?style=for-the-badge&logo=comfyui)](https://github.com/ltdrdata/ComfyUI-Manager)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow?style=for-the-badge)](LICENSE)
[![Changelog](https://img.shields.io/badge/📖_Changelog-v1.62.0-blue?style=for-the-badge)](CHANGELOG.md)
[![Changelog](https://img.shields.io/badge/📖_Changelog-v1.62.1-blue?style=for-the-badge)](CHANGELOG.md)
[![Bilibili Video](https://img.shields.io/badge/Bilibili-视频演示-00A1D6?style=for-the-badge&logo=bilibili)](https://www.bilibili.com/video/BV1boHx61Enc/)
[![Bilibili Voices](https://img.shields.io/badge/Bilibili-配音演示-00A1D6?style=for-the-badge&logo=bilibili)](https://www.bilibili.com/video/BV1bkpZ6uEZQ/)
[![Reddit](https://img.shields.io/badge/Reddit-r%2Fcomfyui-FF4500?style=for-the-badge&logo=reddit)](https://www.reddit.com/r/comfyui/s/n38ihf3DLf)
+10
View File
@@ -123,3 +123,13 @@ body:has(#anomalous-toolbox-modal[style*="display: flex"]) #anomalous-sidebar-to
**以后怎么做**
- `node tools/check_structure.mjs` 现在会拒绝 `:has()` 里出现 `[style…]`,报出文件和行号。
- 同一个坑第二次出现,就把它做成检查,不再只补一段经验。
## 2026-11-05 · 偶尔打开 ComfyUI,AMB 整个不见了
**现象**:没有悬浮按钮,没有窗口,控制台里是几十条 `Cannot access 'shared' before initialization`;刷新一下又好了。大约十几次加载碰到一次。
**原因**:ComfyUI 把 `web/` 下每个 .js 都当成扩展,同时各自导入一遍,谁先跑完全看网络。AMB 的模块有一个很大的循环导入,`ui_ai_workbench.js` 加载时就要写 `ui_ai_results.js` 里的 `const shared`。从 `main.js` 进来顺序是对的;碰巧先从 `ui_ai_results.js` 或 `ui_ai_step_body.js` 进来,`shared` 还没初始化,这个模块就报错,而且错误会被缓存,整个 AMB 跟着起不来。
**做法**:`shared` 搬进不导入任何东西的 `ui_ai_shared.js`。`tests/module_cycles.mjs` 查“加载时就读同一个循环里的 const”,这种写法以后过不了测试。
**以后**:会在加载时用到的值,放在循环外面的模块里。别只从 `main.js` 想加载顺序。要逐个模块验证,在浏览器里给每个模块开一个 iframe 单独 import。
+69
View File
@@ -0,0 +1,69 @@
// ComfyUI imports every .js under web/ on its own, all at once, so any module of an import cycle can happen
// to run first. A module that uses, as it loads, a `const` of a module in its own cycle then fails now and
// then, and AMB with it: no button, no window (docs/architecture/lessons.md). Such a value goes in a module
// outside the cycle.
import assert from 'node:assert/strict';
import { readFileSync, readdirSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const folder = join(dirname(fileURLToPath(import.meta.url)), '..', 'web', 'modules');
const IMPORT = /^import\s+\{([^}]*)\}\s+from\s+'\.\/([\w.]+\.js)'/gm;
const modules = new Map(readdirSync(folder).filter(name => name.endsWith('.js')).map((name) => {
const text = readFileSync(join(folder, name), 'utf8').replace(/\r\n/g, '\n');
const imports = [...text.matchAll(IMPORT)].map(([, names, from]) => ({ from, names: names.split(',').map(item => item.trim().split(/\s+as\s+/)).filter(([first]) => first) }));
return [name, { text, imports, to: new Set(imports.map(item => item.from)) }];
}));
/** The modules in cycles, each with the set of those in the same cycle (Tarjan, on the imports). */
function cycles() {
const index = new Map();
const low = new Map();
const stack = [];
const groups = new Map();
const visit = (name) => {
index.set(name, index.size);
low.set(name, index.get(name));
stack.push(name);
for (const next of modules.get(name)?.to || []) {
if (!modules.has(next)) continue;
if (!index.has(next)) {
visit(next);
low.set(name, Math.min(low.get(name), low.get(next)));
} else if (stack.includes(next)) low.set(name, Math.min(low.get(name), index.get(next)));
}
if (low.get(name) !== index.get(name)) return;
const group = new Set();
let member;
do {
member = stack.pop();
group.add(member);
} while (member !== name);
for (const item of group) groups.set(item, group);
};
for (const name of modules.keys()) if (!index.has(name)) visit(name);
return groups;
}
/** Is `local` used by a line at the top of `text`, that is one run as the module loads? */
function usedAsItLoads(text, local) {
return text.split('\n').some(line => /^[\w(]/.test(line)
&& !/^(import|function|async function|export (async )?function|export default function|class)\b/.test(line)
&& new RegExp(`\\b${local}\\b`).test(line.split('=>')[0]));
}
const groups = cycles();
const found = [];
for (const [name, { text, imports }] of modules) {
for (const { from, names } of imports) {
if (!groups.get(name)?.has(from) || from === name) continue;
const source = modules.get(from).text;
for (const [exported, local = exported] of names) {
const isValue = new RegExp(`^export (const|let) ${exported}\\b`, 'm').test(source);
if (isValue && usedAsItLoads(text, local)) found.push(`${name} uses ${exported} of ${from} as it loads`);
}
}
}
assert.deepEqual(found, []);
console.log('Module cycles: nothing reads a value of its own cycle as it loads.');
+1 -2
View File
@@ -22,8 +22,7 @@ import { canvasChanges } from './ui_canvas_changes.js';
import { estimateBox, runProgress } from './ui_ai_run_live.js';
import { runMapBox } from './ui_ai_run_map.js';
import { iconButton } from './ui_ai_icons.js';
export const shared = { owner: null, taskId: null, entries: new Map(), player: null, openMap: null }; // openMap: ui_ai_workbench.js
import { shared } from './ui_ai_shared.js';
/** A run step's progress with its stop button (while the task shown is the run's). */
const liveRun = progress => runProgress(progress, shared.taskId ? { task: shared.taskId, step: progress.step } : null);
+7
View File
@@ -0,0 +1,7 @@
/**
* What the parts of the AI page share: AMB's window, the task shown, its canvas entries, the player and
* the way to a run's map. It imports nothing, so it stands before any of them runs: ui_ai_workbench.js
* writes here as it loads, and ComfyUI may run any of AMB's modules first (tests/module_cycles.mjs).
*/
export const shared = { owner: null, taskId: null, entries: new Map(), player: null, openMap: null };
+2 -1
View File
@@ -44,7 +44,8 @@ import { iconButton } from './ui_ai_icons.js';
import { listToggle, renderTaskList, stateIcon } from './ui_ai_task_list.js';
import { flowMap } from './ui_ai_flow.js';
import { drawRecentAiTasks } from './ui_ai_recent.js';
import { locateNode, openModel, resultLinks, shared, updateRunProgress } from './ui_ai_results.js';
import { locateNode, openModel, resultLinks, updateRunProgress } from './ui_ai_results.js';
import { shared } from './ui_ai_shared.js';
import { drawOutputsLine, newOutputs } from './ui_ai_outputs.js';
import { compareBar, setMarker } from './ui_ai_images.js';
import { openMarker } from './ui_ai_marker.js';