Compare commits

..
Author SHA1 Message Date
DemonGatanjieuandClaude Opus 5.5 6331b25241 chore(release): v1.57.3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 15:59:03 +08:00
DemonGatanjieuandClaude Opus 5.5 c8689d56e4 fix: offline scans stay offline, workshop snap dock, 400 on malformed requests
From an outside audit (same change as 32a3719 on feature/audio-engines):
- The scan wizard's offline mode still sent the greyed-out rename and
  overwrite options, and scraper.py fetched Civitai model pages and preview
  media for models whose .info held a Civitai id. Offline now implies no
  media and no model page; the folder scan and scan_all refuse a missing or
  non-object body instead of defaulting to an online scan.
- The Prompt Workshop's snap dock referenced leftSearch from the source deck
  module (ReferenceError on click); the deck now exposes focusSearch().
- translate, delete_model, delete_gallery_image and the image-material
  routes answer malformed bodies with 400 instead of 500.
- Removed six unused imports in ui_doctor.js and 16 duplicate locale keys
  per language (the later, effective entry is kept, so nothing changes on
  screen).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 15:44:04 +08:00
14 changed files with 49 additions and 55 deletions
+7
View File
@@ -1,5 +1,12 @@
# 📈 Anomalous Model Browser Changelog
## v1.57.3 Beta (Hotfix: Offline Scan, Prompt Workshop) — 2026-09-29
### 🔌 Offline scan and small fixes (离线扫描与小修复)
- **Offline scan stays offline (离线扫描不再联网)**: "Offline fast read" no longer sends the greyed-out rename and overwrite options, and an offline scan never fetches Civitai model pages or preview images, even for models whose `.info` came from Civitai. Scan requests without options are refused instead of starting an online scan.
- Clicking the drop zone at the end of the Prompt Workshop's assembly track focuses the card search again instead of failing silently.
- Malformed requests to translation, deletion, scanning and image-material routes get a clear 400 instead of a server error.
## v1.57.2 Beta (Hotfix: Canvas Performance, Saving Workflows) — 2026-09-27
### 💾 Saving workflows and Prompt Notes (保存工作流与提示词笔记)
+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.57.2_Beta-blue?style=for-the-badge)](CHANGELOG.md)
[![Changelog](https://img.shields.io/badge/📖_Changelog-v1.57.3_Beta-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/BV1a1bv68EuA/)
[![YouTube Video](https://img.shields.io/badge/YouTube-Video_Demo-red?style=for-the-badge&logo=youtube)](https://youtu.be/hAvsj7uiaCw)
+5
View File
@@ -83,6 +83,11 @@ async def api_get_gallery_images(request):
async def api_delete_gallery_image(request):
try:
data = await request.json()
except ValueError:
data = None
if not isinstance(data, dict):
return web.json_response({"status": "error", "message": "Invalid request body"}, status=400)
try:
filename = data.get("filename")
subfolder = data.get("subfolder", "")
+2
View File
@@ -25,6 +25,8 @@ def _material_assets_dir(materials_dir, filename, create=False):
def _inspect_source_image(source_image):
source = _normalise_source_image(source_image)
if source is None:
raise ValueError("Missing source image")
if not source["filename"].lower().endswith(".png"):
raise ValueError("Only PNG output images can contain reusable workflow metadata")
source_path = _output_source_path(source)
+5
View File
@@ -75,6 +75,11 @@ def _reset_model_cover(base_path):
async def api_delete_model(request):
try:
data = await request.json()
except ValueError:
data = None
if not isinstance(data, dict):
return web.json_response({"status": "error", "message": "Invalid request body"}, status=400)
try:
folder_type = data.get('type', 'checkpoints')
subfolder = data.get('subfolder', '/')
filename = data.get('filename', '')
+8 -4
View File
@@ -274,8 +274,10 @@ async def api_scan_folder(request):
try:
data = await request.json()
except Exception:
data = {}
except ValueError:
data = None
if not isinstance(data, dict):
return web.json_response({"status": "error", "message": "Invalid request body"}, status=400)
offline_only = data.get("offline_only", False)
skip_rename = data.get("skip_rename", False)
@@ -361,8 +363,10 @@ async def api_scan_all(request):
try:
data = await request.json()
except Exception:
data = {}
except ValueError:
data = None
if not isinstance(data, dict):
return web.json_response({"status": "error", "message": "Invalid request body"}, status=400)
offline_only = data.get("offline_only", False)
use_local_metadata = data.get("use_local_metadata", True)
+6 -1
View File
@@ -73,7 +73,12 @@ def _translate_with_mymemory(text, tl):
async def api_translate(request):
try:
data = await request.json()
text = str(data.get("text", "")).strip()
except ValueError:
data = None
if not isinstance(data, dict):
return web.json_response({"status": "error", "message": "Invalid request body"}, status=400)
text = str(data.get("text", "")).strip()
try:
tl = str(data.get("target_lang", "zh-CN")).strip()
if not text:
return web.json_response({"translated": "", "status": "success"})
+4 -1
View File
@@ -230,6 +230,9 @@ def main():
parser.add_argument("--folder-type", default="", help="由 ComfyUI 传入的模型目录类型,用于执行安全策略")
parser.add_argument("--progress-file", default="", help=argparse.SUPPRESS)
args = parser.parse_args()
if args.offline_only:
# Offline means no network at all: previews and model pages come from Civitai.
args.skip_media = True
target_folder = args.folder
if not os.path.isdir(target_folder):
@@ -396,7 +399,7 @@ def main():
# --- 额外获取模型主页的说明文字 ---
model_id = civitai_data.get("modelId")
if model_id and model_id != -1:
if model_id and model_id != -1 and not args.offline_only:
try:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
+1 -1
View File
@@ -6,7 +6,7 @@ try {
} catch (_) {}
import { app } from "../../scripts/app.js";
import { createBrowserEntry } from "./modules/browser_entry.js?v=20260927-hotfix-1";
import { createBrowserEntry } from "./modules/browser_entry.js?v=20260929-hotfix-1";
import { createInterfaceSettings, getCurrentLanguage, setAbyssalScarletTheme, t } from "./modules/interface_settings.js";
export { setAbyssalScarletTheme };
-32
View File
@@ -218,11 +218,7 @@ export const i18n = {
recipeScopePill_partial: "局部子图",
recipeViewGrid: "网格视图",
recipeViewList: "列表视图",
recipeDragHint: "按住拖拽至画布打开新工作流",
recipeDragTargetCanvas: "释放以在画布打开新工作流",
recipeStatusReady: "模型全部就绪",
recipeStatusUnverified: "模型待校验",
recipeStatusMissing: "模型缺失",
recipeReadinessHint: "检测工作流所需模型在本地环境中的就绪状态,点击卡片进入详情可进行智能比对与替换",
promptEnablePart: "启用这个片段",
promptUnnamedPart: "未命名片段",
@@ -262,21 +258,9 @@ export const i18n = {
notebookCompanionModels: "配套模型",
notebookMore: "更多",
notebookDoneEditing: "📑 查看双语分词",
editRaw: "✏️ 编辑原始文本",
saveNotebook: "保存笔记",
deleteNotebook: "删除笔记",
delSure: "确认删除?",
sendToCanvas: "发送到画布",
createNotebook: "新建笔记",
newNotebookName: "新笔记名称...",
recipeSearchAndReplace: "词条替换与目标语言",
findPlaceholder: "查找...",
replacePlaceholder: "替换为...",
replaceAll: "全部替换",
recipeDiffNone: "未选择",
loras: "LoRA 模型",
baseModel: "底模",
mainModel: "主模型",
nbNavHeading: "快速直达",
nbNotesHeading: "我的笔记",
nbNavModels: "配套模型",
@@ -1529,11 +1513,7 @@ export const i18n = {
recipeScopePill_partial: "Sub-graph",
recipeViewGrid: "Grid View",
recipeViewList: "List View",
recipeDragHint: "Hold & drag onto canvas to load workflow",
recipeDragTargetCanvas: "Drop to load workflow in canvas",
recipeStatusReady: "All Models Ready",
recipeStatusUnverified: "Models to Verify",
recipeStatusMissing: "Models Missing",
recipeReadinessHint: "Detects readiness of required models in local environment; click card details for smart matching and replacement",
promptEnablePart: "Enable this fragment",
promptUnnamedPart: "Untitled fragment",
@@ -1573,21 +1553,9 @@ export const i18n = {
notebookCompanionModels: "Companion models",
notebookMore: "More",
notebookDoneEditing: "📑 View Bilingual Tags",
editRaw: "✏️ Edit Raw Prompt",
saveNotebook: "Save Note",
deleteNotebook: "Delete Note",
delSure: "Confirm Delete?",
sendToCanvas: "Send to Canvas",
createNotebook: "New Note",
newNotebookName: "New note name...",
recipeSearchAndReplace: "Find, Replace & Language",
findPlaceholder: "Find...",
replacePlaceholder: "Replace with...",
replaceAll: "Replace All",
recipeDiffNone: "None",
loras: "LoRA Models",
baseModel: "Base Model",
mainModel: "Main Model",
nbNavHeading: "Quick Jump",
nbNotesHeading: "My Notes",
nbNavModels: "Companion Models",
+1 -9
View File
@@ -1,19 +1,11 @@
import { applyNodeMaterialValues } from './node_material_actions.js';
import { applyMaterialToSelectedNode } from './ui_material_application.js';
import { app } from "../../../scripts/app.js";
import { translate } from './locales.js';
import { escapeHtml } from './safe_dom.js';
import {
analyzeModelChainInsertion,
getModelChainInsertionCapabilities,
spliceModelChainNode,
} from './graph_splice.js';
import {
collectMainModelContextRequests,
formatModelTypeLabel,
getBaseModelFamily,
inferPickerModelType,
} from './model_picker.js';
import { inferPickerModelType } from './model_picker.js';
import { requiresHashForModelRecovery } from './model_policies.js';
import { findModelComboWidget, getNativeWidgetValues } from './ui_node_model_picker.js';
import { renderParameterPresets } from './ui_node_presets.js';
+1
View File
@@ -789,5 +789,6 @@ export function createPromptSourceDeck(workbenchGrid, drawer, scope, addSourceCa
refresh: renderSourceCardsList,
sync: syncMaterialsIntoSourceDeck,
hidePreview: (force = false) => hideCardPreviewPopover(force),
focusSearch: () => leftSearch.focus(),
};
}
+1 -1
View File
@@ -704,7 +704,7 @@ export function createPromptWorkbench(owner, container, scope, options) {
`;
setupDropzoneListeners(snapDock);
snapDock.onclick = () => {
leftSearch?.focus?.();
sourceDeck.focusSearch();
showWorkbenchToast(window.anomalous_browser_lang === 'zh' ? '点击左侧词卡即可直接加入此处' : 'Click any card on left to add here');
};
}
+7 -5
View File
@@ -533,12 +533,14 @@ export function openScanWizard({ isGlobal = false, targetFiles = null } = {}) {
// Execute Scan Logic
const doScan = async () => {
try {
// Offline greys out renaming and overwriting, so none of them may be sent.
const offline = scanMode === 'offline';
const reqBody = {
offline_only: scanMode === 'offline',
skip_rename: !enableRename,
virtual_rename: enableRename ? enableVirtualRename : false,
physical_rename: enableRename ? enablePhysicalRename : false,
force_overwrite: enableForceOverwrite
offline_only: offline,
skip_rename: offline || !enableRename,
virtual_rename: !offline && enableRename && enableVirtualRename,
physical_rename: !offline && enableRename && enablePhysicalRename,
force_overwrite: !offline && enableForceOverwrite
};
if (targetMode === 'custom') {