refactor: share matting model status checks

This commit is contained in:
Dariusz L
2026-08-13 04:41:24 +02:00
parent 18445387e4
commit 082e8b74f1
5 changed files with 99 additions and 16 deletions
+4 -8
View File
@@ -13,6 +13,7 @@ import { createModuleLogger } from "./log_system/log_funcs.js";
import { showErrorNotification, showSuccessNotification, showInfoNotification, showWarningNotification } from "./utils/NotificationUtils.js";
import { iconLoader, LAYERFORGE_TOOLS } from "./utils/IconLoader.js";
import { exportCanvasImage } from "./utils/CanvasExportUtils.js";
import { fetchMattingModelStatus } from "./utils/MattingUtils.js";
import { setupSAMDetectorHook } from "./SAMDetectorIntegration.js";
const log = createModuleLogger('Canvas_view');
const MATTING_SETTINGS_STORAGE_KEY = 'layerforge.matting.settings';
@@ -188,9 +189,8 @@ async function createCanvasWidget(node, widget, app) {
let modelOptions = [];
let modelStatusMessage = 'Model options are loaded from ComfyUI background-removal storage.';
try {
const response = await fetch('/matting/check-model');
if (response.ok) {
const status = await response.json();
const { ok, data: status } = await fetchMattingModelStatus();
if (ok) {
if (Array.isArray(status.models)) {
modelOptions = status.models.filter((option) => (option && typeof option.path === 'string' && typeof option.label === 'string'));
}
@@ -655,11 +655,7 @@ async function createCanvasWidget(node, widget, app) {
const mattingSettings = loadMattingSettings();
try {
// First check if model is available
const modelCheckUrl = mattingSettings.modelPath
? `/matting/check-model?model_path=${encodeURIComponent(mattingSettings.modelPath)}`
: "/matting/check-model";
const modelCheckResponse = await fetch(modelCheckUrl);
const modelStatus = await modelCheckResponse.json();
const { data: modelStatus } = await fetchMattingModelStatus(mattingSettings.modelPath);
if (!modelStatus.available) {
switch (modelStatus.reason) {
case 'missing_dependency':
+8
View File
@@ -0,0 +1,8 @@
export async function fetchMattingModelStatus(modelPath) {
const query = modelPath
? `?model_path=${encodeURIComponent(modelPath)}`
: '';
const response = await fetch(`/matting/check-model${query}`);
const data = await response.json();
return { ok: response.ok, data };
}
+4 -8
View File
@@ -19,6 +19,7 @@ import {createModuleLogger} from "./log_system/log_funcs.js";
import {showErrorNotification, showSuccessNotification, showInfoNotification, showWarningNotification} from "./utils/NotificationUtils.js";
import { iconLoader, LAYERFORGE_TOOLS } from "./utils/IconLoader.js";
import { exportCanvasImage, type CanvasExportAction, type CanvasExportVariant } from "./utils/CanvasExportUtils.js";
import { fetchMattingModelStatus } from "./utils/MattingUtils.js";
import { registerImageInClipspace, startSAMDetectorMonitoring, setupSAMDetectorHook } from "./SAMDetectorIntegration.js";
import type { ComfyNode, Layer, AddMode } from './types';
@@ -261,9 +262,8 @@ async function createCanvasWidget(node: ComfyNode, widget: any, app: ComfyApp):
let modelStatusMessage = 'Model options are loaded from ComfyUI background-removal storage.';
try {
const response = await fetch('/matting/check-model');
if (response.ok) {
const status = await response.json() as { models?: MattingModelOption[] };
const { ok, data: status } = await fetchMattingModelStatus<MattingModelOption>();
if (ok) {
if (Array.isArray(status.models)) {
modelOptions = status.models.filter((option) => (
option && typeof option.path === 'string' && typeof option.label === 'string'
@@ -815,11 +815,7 @@ async function createCanvasWidget(node: ComfyNode, widget: any, app: ComfyApp):
try {
// First check if model is available
const modelCheckUrl = mattingSettings.modelPath
? `/matting/check-model?model_path=${encodeURIComponent(mattingSettings.modelPath)}`
: "/matting/check-model";
const modelCheckResponse = await fetch(modelCheckUrl);
const modelStatus = await modelCheckResponse.json();
const { data: modelStatus } = await fetchMattingModelStatus(mattingSettings.modelPath);
if (!modelStatus.available) {
switch (modelStatus.reason) {
+23
View File
@@ -0,0 +1,23 @@
export interface MattingModelStatus<Model = unknown> {
available: boolean;
reason: string;
message: string;
models?: Model[];
}
export interface MattingModelStatusResult<Model = unknown> {
ok: boolean;
data: MattingModelStatus<Model>;
}
export async function fetchMattingModelStatus<Model = unknown>(
modelPath?: string,
): Promise<MattingModelStatusResult<Model>> {
const query = modelPath
? `?model_path=${encodeURIComponent(modelPath)}`
: '';
const response = await fetch(`/matting/check-model${query}`);
const data = await response.json() as MattingModelStatus<Model>;
return { ok: response.ok, data };
}
@@ -0,0 +1,60 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import test from 'node:test';
import { fetchMattingModelStatus } from '../js/utils/MattingUtils.js';
const canvasViewSource = await readFile(
new URL('../src/CanvasView.ts', import.meta.url),
'utf8',
);
test('shared matting status helper preserves endpoint, payload, and errors', async () => {
const originalFetch = globalThis.fetch;
const requests = [];
globalThis.fetch = async (url) => {
requests.push(url);
const isErrorResponse = requests.length === 3;
return {
ok: !isErrorResponse,
json: async () => ({
available: !isErrorResponse,
reason: isErrorResponse ? 'error' : 'ready',
message: isErrorResponse ? 'failed' : 'ready',
models: [],
}),
};
};
try {
const automatic = await fetchMattingModelStatus();
const selected = await fetchMattingModelStatus('models/my model.safetensors');
const failed = await fetchMattingModelStatus('models/missing.safetensors');
assert.deepEqual(requests, [
'/matting/check-model',
'/matting/check-model?model_path=models%2Fmy%20model.safetensors',
'/matting/check-model?model_path=models%2Fmissing.safetensors',
]);
assert.equal(automatic.ok, true);
assert.equal(automatic.data.reason, 'ready');
assert.equal(selected.data.message, 'ready');
assert.equal(failed.ok, false);
assert.equal(failed.data.reason, 'error');
globalThis.fetch = async () => {
throw new Error('offline');
};
await assert.rejects(fetchMattingModelStatus(), /offline/);
} finally {
globalThis.fetch = originalFetch;
}
});
test('CanvasView delegates both model status checks to the shared helper', () => {
assert.match(canvasViewSource, /fetchMattingModelStatus<MattingModelOption>\(\)/);
assert.match(canvasViewSource, /fetchMattingModelStatus\(mattingSettings\.modelPath\)/);
assert.doesNotMatch(canvasViewSource, /const modelCheckUrl =/);
assert.doesNotMatch(canvasViewSource, /fetch\(['"]\/matting\/check-model/);
});