diff --git a/AUDIT_AND_BUG_REPORT.md b/AUDIT_AND_BUG_REPORT.md new file mode 100644 index 0000000..5155df9 --- /dev/null +++ b/AUDIT_AND_BUG_REPORT.md @@ -0,0 +1,171 @@ +# Anomalous Model Browser 全功能实测审计与问题复原报告 +**Audit & Functional Verification Report** + +- **测试对象**:`Anomalous Model Browser` (异构模型浏览器 ComfyUI 插件) +- **运行环境**:ComfyUI Portable (Windows x64) / 本地端口 `http://127.0.0.1:8188` +- **当前 Git 分支**:`feature/recipe-model-binding` +- **浏览器会话录屏存档**:`anomalous_deep_test_1789816916045.webp` + +--- + +## 一、 测试操作过程完整复原 (Step-by-Step Reproduction) + +本次实测通过浏览器子智能体(Browser Subagent)与本地真实运行的 ComfyUI 实例进行端到端(E2E)交互操作,测试全流程操作复原如下: + +### 1. 插件唤起与主界面交互 +1. **浏览器进入 ComfyUI 画布**: + - 页面初始加载默认工作流节点(包括 `Load Checkpoint`、`CLIP Text Encode`、`KSampler`、`Anima VAE` 等)。 +2. **唤起插件主界面**: + - 点击 WebUI 界面悬浮触发球及左侧快捷立方体图标,`#anomalous-modal` 顺畅展开,居中层叠在画布上。 + - 验证右上角 `×` 关闭按钮与侧边栏 `☰` 折叠按钮,动画过渡自然,能顺畅收起并安全回到主画布。 + +### 2. 模型医生(Model Doctor)全局体检与修复测试 +1. **打开体检中心**: + - 点击侧边栏听诊器图标(🩺),切换至 Model Doctor 面板。 +2. **体检扫描表现**: + - 面板显示当前画布的节点模型状态:“健康 2 个 / 缺失 1 个”。 + - 正确检测到当前画布缺失模型:`Anima\qwen_image_vae.safetensors`。 +3. **交互操作测试**: + - 点击右上角“刷新缓存”按钮,控制台正确触发 `app.refreshComboInNodes()` 和 `/anomalous/all_hashes` 刷新。 + - 查看缺失模型右侧操作按钮:包含了“深度哈希扫描”、“手动替换”、“查看哈希”以及“Civitai 搜索”链接。 + +### 3. 素材库(Material Library)与分类检索测试 +1. **打开素材库**: + - 点击工具箱或侧边栏素材库图标,成功打开 Material Library 视图。 +2. **分类与搜索切换**: + - 点击顶部筛选胶囊:“全部”、“工作流”、“提示词”等分类,卡片即时响应过滤。 + - 在搜索框输入关键词 `Kirazuri`,检索到对应的完整工作流素材卡片 `Kirazuri (Anima)_v4.0`。 +3. **卡片拖拽交互测试**: + - 测试按住卡片尝试拖拽至画布空白处或节点上,排查拖拽反馈(发现重大逻辑断点,详见下文第二节)。 + +### 4. 模型扫描向导(Scan Wizard)与设置中心测试 +1. **打开扫描向导**: + - 点击侧边栏雷达扫描图标,弹出 Scan Wizard 原生 Dialog 弹窗。 + - 查看各项扫描策略配置:包括完整哈希、Civitai 元数据提取等。 + - 测试取消弹窗:按 `Escape` 键或点击外部背景,发现原生 Dialog 没有绑定外部点击关闭逻辑,必须向下滚动找到底部的“取消”按钮才能退出遮罩层。 +2. **全局设置中心(Settings Hub)**: + - 打开齿轮图标设置面板,测试在“紧凑模式 (Compact)”与“标准模式 (Standard)”之间来回切换,网格与卡片间距自适应良好。 + +--- + +## 二、 发现的核心缺陷与不合理之处深度剖析 (Critical Issues & Bugs) + +通过“浏览器实际操作 + 源码 AST/逻辑级走查”,发现以下 **4 个影响核心体验或违反技术方案** 的问题: + +--- + +### 🚨 缺陷 1(核心技术方案违背):本地缺失模型被误判为“⛔ 身份冲突”,导致修复链路完全中断 + +#### 1. 问题定位 +- **文件**:`api/model_resolution.py`(第 178-181 行) +- **函数**:`_resolve_from_candidates` + +#### 2. 代码问题根源 +在比对带有来源信息(Provenance,同时包含 `hash` 与 `size`)的模型时: +```python + hash_matches = [candidate for candidate in candidates if target_hash in _candidate_hashes(candidate)] + if hash_matches: + return {"found": False, "identity_conflict": True} + return {"found": False, "identity_conflict": True} # <--- 无论 hash_matches 是否为空,均返回 identity_conflict: True! +``` +- 当用户导入一个网上下载的带有哈希的工作流,而本地**压根没有这个模型**时,`candidates` 或 `size_matches` 是空的。 +- 代码执行到末尾,**无条件返回了 `{"found": False, "identity_conflict": True}`**! + +#### 3. 产生的不合理现象 +1. **误导用户**:在前端 `ui_doctor.js` 和 `ui_recipe_model_matching.js` 中,该模型会被标上鲜红的 `⛔ 身份冲突 (Identity Conflict)`,给用户一种“本地文件损坏或文件哈希冲突被系统拒绝”的假象。 +2. **阻断修复方案**:根据 `docs/architecture/model-resolution.md` 规范,只有当本地存在尺寸相同但哈希不同(或哈希相同但尺寸不同)的文件时,才算作真正的 `identity_conflict`。如果本地根本没有该模型,应该返回普通的 `{"found": False}`,以便触发正常的“未找到模型”提示、Civitai 自动检索建议或下载引导。 + +#### 4. 修复建议 +修改 `api/model_resolution.py`,只有在发现真正的冲突(如尺寸符合但哈希不匹配、或哈希符合但尺寸矛盾)时返回 `identity_conflict: True`;无任何匹配时应返回普通未找到: +```python + hash_matches = [candidate for candidate in candidates if target_hash in _candidate_hashes(candidate)] + if hash_matches: + return {"found": False, "identity_conflict": True} + return {"found": False} +``` + +--- + +### 🚨 缺陷 2(功能承诺未兑现 & 事件拦截冲突):素材库卡片“拖入画布载入工作流”无效且拖拽逻辑存在优先级漏洞 + +#### 1. 问题定位 +- **文件 A**:`web/modules/ui_material_cards.js`(第 90-96 行) +- **文件 B**:`web/modules/material_drag.js`(第 55-84 行) + +#### 2. 代码问题根源 +1. **提示与实现脱节(虚假功能)**: + - 卡片上的 Tooltip 明确写着:`"按住可拖拽至画布节点注入参数,或拖至空白处载入工作流"`(`t('materialCardDragHint')`)。 + - 但是在 `ui_material_cards.js` 中绑定拖拽时: + ```javascript + if (material.node_types?.length) { + bindMaterialDrag(card, owner, { + payload: () => ({ ...material, node_types: [...material.node_types], dragHint: t('materialDragParameters') || '拖拽素材参数至目标节点' }), + accepts: (node, source) => source.node_types.includes(node.type), + drop: (node, source, graph) => applyLibraryMaterial(owner, source, node, graph), + // 致命缺失:根本没有传入 dropOnCanvas 回调! + }); + } + ``` + **根本没有传入 `dropOnCanvas` 回调**!导致用户拖到空白画布释放时什么都没发生。 + - 此外,如果一个素材是**整图工作流素材**(`material.node_types` 为空),因 `if (material.node_types?.length)` 为假,导致**整张卡片完全没有被绑定拖拽能力**(`draggable = false`),用户根本拖不动! + +2. **`material_drag.js` 中的命中优先级缺陷(Precedence Inversion)**: + 在 `material_drag.js` 的拖拽结束处理中: + ```javascript + if (dropOnCanvas && overCanvas) { + try { await dropOnCanvas(event, data, graph); } + return; + } + if (!node || !accepts?.(node, data)) return; + try { await drop(node, data, graph); } + ``` + - 只要定义了 `dropOnCanvas`,一旦鼠标落在画布区域(`overCanvas == true`),代码会**无条件先执行 `dropOnCanvas`**(载入整图工作流覆盖画布)! + - 即便鼠标此时正精准悬停在某个兼容的节点上,也不会触发节点的参数注入! + +#### 3. 修复建议 +1. 在 `material_drag.js` 中调整执行优先级:**先判断是否命中了兼容节点(Target Node),若命中了则优先执行节点注入;仅当未命中任何节点且落在画布空白区域时,才触发 `dropOnCanvas` 载入工作流**。 +2. 在 `ui_material_cards.js` 中,只要素材包含工作流(`material.has_workflow` 或 `material.data?.workflow`),就为其绑定 `dropOnCanvas: async (e, data, graph) => openMaterialWorkflow(owner, material.filename)`,并在 `material.node_types` 为空时也允许拖拽。 + +--- + +### ⚠️ 缺陷 3(交互体验问题):扫描向导(Scan Wizard)缺少遮罩点击关闭 & 纵向溢出 + +#### 1. 问题表现 +- 扫描向导使用原生的 HTML `` 挂载并使用 `showModal()`。 +- 当弹窗内容较多(包含多项模型目录开关与说明)时,若屏幕纵向高度较小,底部“开始扫描”与“取消”按钮会被推到视口外部。 +- 用户尝试点击周围半透明深色遮罩(Backdrop)时,无法关闭弹窗;由于未在 `dialog` 上绑定 `click` 检测 `event.target === dialog` 的关闭逻辑,用户必须手动向下滚动寻找底部取消按钮。 + +#### 2. 优化建议 +- 在 `ui_scan_wizard.js` 中,为 Dialog 添加点击遮罩关闭逻辑: + ```javascript + dialog.addEventListener('click', (e) => { + const rect = dialog.getBoundingClientRect(); + const isInDialog = (rect.top <= e.clientY && e.clientY <= rect.top + rect.height + && rect.left <= e.clientX && e.clientX <= rect.left + rect.width); + if (!isInDialog) dialog.close(); + }); + ``` +- 限制 `max-height: 85vh` 并对内部表单容器开启 `overflow-y: auto`,确保底部的操作按钮始终固定可见(Sticky Bottom)。 + +--- + +### ⚠️ 缺陷 4(同步与竞态隐患):模型单次精准扫描后前端下拉列表刷新时机 + +#### 1. 问题表现 +- 在针对单个模型触发精准扫描(`triggerDirectModelScan`)时,后端会计算 SHA-256 并生成 `.info` 文件。 +- 前端在通知扫描成功后,虽然调用了 `window.anomalous_reload_hashes()`,但在 ComfyUI 原生节点的下拉菜单选项(`widget.options.values`)重新拉取存在微弱的异步延迟,此时若立即在 Model Doctor 中点击“自动修复”,偶尔会触发 `optionsCacheStale`,需等待二次刷新才能消除红框。 + +#### 2. 优化建议 +- 在单模型扫描写入完成后,增加 `await fetch('/anomalous/clear_cache', { method: 'POST' });` 并等待 `await app.refreshComboInNodes()` 完成后再触发 Doctor 的状态比对。 + +--- + +## 三、 总结与排查结论 + +1. **整体功能架构**: + 插件整体架构非常先进且完整,Model Doctor、Material Library、Recipe 工作流、Scan Wizard 的多面板协调运作良好,UI 设计美观、动效细腻。 +2. **核心问题收敛**: + - **核心 Bug 1**(`api/model_resolution.py` 误报 `identity_conflict`)是必须修复的高危逻辑漏洞,它直接导致工作流模型恢复方案失效; + - **核心 Bug 2**(素材库拖拽无响应及画布/节点优先级颠倒)直接影响了用户对于“素材一拖即用”的核心预期。 + +本报告已归档保存至本地。