feat: add inpaint region functionality and related tools

- Enhanced cursor behavior for new tools: semantic select, mask lasso, and mask rectangle.
- Updated mask edit state to include mask asset ID and kind.
- Implemented inpaint region commands for adding, applying, and removing inpaint regions.
- Introduced new operations for lasso and semantic selection tools.
- Created UI components for candidate review and inpaint region management.
- Added tests for inpaint region commands to ensure functionality.
- Updated various components to support new inpaint features and improve user experience.
This commit is contained in:
syntaxbullet
2026-07-11 16:41:22 +02:00
parent f4e13b80e7
commit ff762b8f17
78 changed files with 1632 additions and 301 deletions

View File

@@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test";
import { buildAnimaWorkflow, buildSdxlWorkflow, buildZImageTurboWorkflow, buildZImageWorkflow, selectGeneratedOutputImage } from "./comfy";
import { buildAnimaWorkflow, buildSdxlWorkflow, buildSemanticSelectionWorkflow, buildZImageTurboWorkflow, buildZImageWorkflow, selectGeneratedOutputImage, selectGeneratedOutputImages } from "./comfy";
import { handleComfyApi } from "./comfy-routes";
describe("Comfy adapter", () => {
@@ -26,6 +26,13 @@ describe("Comfy adapter", () => {
expect(image?.filename).toBe("image-studio-inpaint_00002_.png");
});
test("retains every generated image from a batch", () => {
expect(selectGeneratedOutputImages({ outputs: { "8": { images: [
{ filename: "image-studio-inpaint_1.png", type: "output" },
{ filename: "image-studio-inpaint_2.png", type: "output" },
] } } })).toHaveLength(2);
});
test("builds neutral inpaint with VAEEncodeForInpaint", () => {
const workflow = buildSdxlWorkflow(inpaintRequest({ maskedContent: "neutral" }));
@@ -45,6 +52,32 @@ describe("Comfy adapter", () => {
expect(workflow["6"]?.inputs.latent_image).toEqual(["13", 0]);
});
test("repeats inpaint latents for batches and applies Canny ControlNet", () => {
const workflow = buildSdxlWorkflow({
...inpaintRequest({ maskedContent: "neutral" }),
batchSize: 4,
inpaint: { maskedContent: "neutral", structureControl: "canny", controlModel: "controlnet-canny.safetensors", controlStrength: 0.6 },
});
expect(workflow["19"]).toMatchObject({ class_type: "RepeatLatentBatch", inputs: { amount: 4 } });
expect(workflow["20"]).toMatchObject({ class_type: "ControlNetLoader", inputs: { control_net_name: "controlnet-canny.safetensors" } });
expect(workflow["21"]?.class_type).toBe("Canny");
expect(workflow["22"]).toMatchObject({ class_type: "ControlNetApplyAdvanced", inputs: { strength: 0.6 } });
expect(workflow["6"]?.inputs).toMatchObject({ latent_image: ["19", 0], positive: ["22", 0], negative: ["22", 1] });
});
test("adds an optional low-denoise detail pass", () => {
const workflow = buildSdxlWorkflow({ ...inpaintRequest({ maskedContent: "neutral" }), refinePass: true, refineStrength: 18, seed: 40 });
expect(workflow["30"]).toMatchObject({ class_type: "KSampler", inputs: { seed: 41, denoise: 0.18, latent_image: ["6", 0] } });
expect(workflow["7"]?.inputs.samples).toEqual(["30", 0]);
});
test("builds native SAM3 point selection as a mask output", () => {
const workflow = buildSemanticSelectionWorkflow({ inputImage: "input.png", model: "sam3.safetensors", x: 24.4, y: 18.6 });
expect(workflow["1"]).toMatchObject({ class_type: "UNETLoader", inputs: { unet_name: "sam3.safetensors" } });
expect(workflow["3"]).toMatchObject({ class_type: "SAM3_Detect", inputs: { positive_coords: '[{"x":24,"y":19}]', refine_iterations: 2 } });
expect(workflow["4"]).toMatchObject({ class_type: "MaskToImage", inputs: { mask: ["3", 0] } });
});
test("builds Z-Image text-to-image with separated model loaders", () => {
const workflow = buildZImageWorkflow(textRequest({ architecture: "z-image", model: "z_image_bf16.safetensors", steps: 30, cfg: 4 }));