feat: add ComfyUI integration for image generation and management
- Implemented ComfyGenerateRequest type and associated functions for generating images using various architectures and modes. - Added functions for listing generation options and handling image uploads. - Created workflows for different generation modes including SDXL, Z-Image, Z-Image Turbo, and Anima. - Introduced GenerationJobStatus component to display the status of ongoing generation jobs. - Developed MaskControls for managing mask operations and displaying mask analysis. - Created palette items for tool selection, layer management, and generation settings.
This commit is contained in:
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState, type RefObject } from "react";
|
||||
import { CaretDown, CaretUp } from "@phosphor-icons/react";
|
||||
import { commandIds } from "@commands/ids";
|
||||
import type { AppStore } from "@editor/store";
|
||||
import type { GenerationOptions, GenerationResourcesState } from "@editor/state";
|
||||
import { generateArchitectureDefaults } from "@editor/tools";
|
||||
import type { GenerateArchitecture, GenerateMode, GenerateModel, GenerateSettings } from "@editor/tools";
|
||||
import { BottomControlSelectMenu, type BottomControlSelectOption } from "./SelectMenu";
|
||||
@@ -43,56 +44,23 @@ const inpaintMaskedContentOptions = [
|
||||
|
||||
export type GenerateControlsProps = {
|
||||
settings: GenerateSettings;
|
||||
resources: GenerationResourcesState;
|
||||
dispatch: AppStore["dispatch"];
|
||||
};
|
||||
|
||||
type ComfyArchitectureOption = {
|
||||
value: GenerateArchitecture;
|
||||
label: string;
|
||||
defaultModel: string;
|
||||
models: string[];
|
||||
supportedModes: GenerateMode[];
|
||||
};
|
||||
|
||||
type ComfyOptionsResponse = {
|
||||
architectures?: ComfyArchitectureOption[];
|
||||
models?: string[];
|
||||
textEncoders?: string[];
|
||||
vaes?: string[];
|
||||
samplers?: string[];
|
||||
schedulers?: string[];
|
||||
};
|
||||
|
||||
export function GenerateControls({ settings, dispatch }: GenerateControlsProps) {
|
||||
const [comfyOptions, setComfyOptions] = useState<ComfyOptionsResponse>();
|
||||
export function GenerateControls({ settings, resources, dispatch }: GenerateControlsProps) {
|
||||
const comfyOptions = resources.options;
|
||||
const [advancedOpen, setAdvancedOpen] = useState(false);
|
||||
const [outpaintOpen, setOutpaintOpen] = useState(false);
|
||||
const [inpaintOpen, setInpaintOpen] = useState(false);
|
||||
const [sizeOpen, setSizeOpen] = useState(false);
|
||||
const sizeRef = useRef<HTMLDivElement>(null);
|
||||
const [error, setError] = useState<string>();
|
||||
const modelOptions = resolveModelOptions(settings, comfyOptions);
|
||||
const supportOptions = resolveSupportOptions(settings, comfyOptions);
|
||||
const samplerOptions = resolveStringOptions(comfyOptions?.samplers, settings.sampler);
|
||||
const schedulerOptions = resolveStringOptions(comfyOptions?.schedulers, settings.scheduler);
|
||||
const modeOptions = resolveModeOptions(settings, comfyOptions);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
void fetch("/api/comfy/models")
|
||||
.then((response) => response.ok ? response.json() : Promise.reject(new Error("Unable to load ComfyUI models")))
|
||||
.then((body: ComfyOptionsResponse) => {
|
||||
if (cancelled) return;
|
||||
setComfyOptions(body);
|
||||
})
|
||||
.catch((reason: unknown) => {
|
||||
if (!cancelled) setError(reason instanceof Error ? reason.message : "Unable to load ComfyUI models");
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!sizeOpen) return;
|
||||
const close = (event: PointerEvent) => {
|
||||
@@ -104,7 +72,7 @@ export function GenerateControls({ settings, dispatch }: GenerateControlsProps)
|
||||
|
||||
return (
|
||||
<div className="grid gap-5 pb-2 tabular-nums">
|
||||
{error ? <p className="rounded-full bg-red-500/10 px-3 py-2 text-xs text-red-200">{error}</p> : null}
|
||||
{resources.error ? <p className="rounded-full bg-red-500/10 px-3 py-2 text-xs text-red-200">{resources.error}</p> : null}
|
||||
|
||||
<section className={panelSectionClass()}>
|
||||
<label className="grid gap-2">
|
||||
@@ -229,7 +197,7 @@ export function GenerateControls({ settings, dispatch }: GenerateControlsProps)
|
||||
);
|
||||
}
|
||||
|
||||
function resolveModelOptions(settings: GenerateSettings, comfyOptions: ComfyOptionsResponse | undefined): readonly BottomControlSelectOption<GenerateModel>[] {
|
||||
function resolveModelOptions(settings: GenerateSettings, comfyOptions: GenerationOptions | undefined): readonly BottomControlSelectOption<GenerateModel>[] {
|
||||
const architecture = comfyOptions?.architectures?.find((option) => option.value === settings.architecture);
|
||||
const models = architecture?.models ?? (settings.architecture === "sdxl" ? comfyOptions?.models : undefined) ?? [];
|
||||
const fallbackModel = architecture?.defaultModel ?? generateArchitectureDefaults[settings.architecture].model;
|
||||
@@ -237,7 +205,7 @@ function resolveModelOptions(settings: GenerateSettings, comfyOptions: ComfyOpti
|
||||
return values.map((model) => ({ value: model, label: model === "auto" ? "Auto" : model }));
|
||||
}
|
||||
|
||||
function resolveSupportOptions(settings: GenerateSettings, comfyOptions: ComfyOptionsResponse | undefined) {
|
||||
function resolveSupportOptions(settings: GenerateSettings, comfyOptions: GenerationOptions | undefined) {
|
||||
const defaults = generateArchitectureDefaults[settings.architecture];
|
||||
return {
|
||||
textEncoders: resolveStringOptions([...(comfyOptions?.textEncoders ?? []), defaults.textEncoder].filter((value) => value !== "auto"), settings.textEncoder),
|
||||
@@ -249,7 +217,7 @@ function resolveStringOptions(values: string[] | undefined, current: string): re
|
||||
return unique([...(values ?? []), current]).map((value) => ({ value, label: value }));
|
||||
}
|
||||
|
||||
function resolveModeOptions(settings: GenerateSettings, comfyOptions: ComfyOptionsResponse | undefined): readonly BottomControlSelectOption<GenerateMode>[] {
|
||||
function resolveModeOptions(settings: GenerateSettings, comfyOptions: GenerationOptions | undefined): readonly BottomControlSelectOption<GenerateMode>[] {
|
||||
const architecture = comfyOptions?.architectures?.find((option) => option.value === settings.architecture);
|
||||
const supportedModes = architecture?.supportedModes?.length ? architecture.supportedModes : generateArchitectureDefaults[settings.architecture].supportedModes;
|
||||
const availableModes = modes.filter((mode) => supportedModes.includes(mode.value));
|
||||
|
||||
Reference in New Issue
Block a user