- 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.
85 lines
4.0 KiB
TypeScript
85 lines
4.0 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import type { Asset } from "@core/asset";
|
|
import type { AppStore } from "@editor/store";
|
|
import { analyzeMask, runMaskOperation, type MaskAnalysis, type MaskRasterOperation } from "@operations/masks/rasterActions";
|
|
|
|
export function MaskStatus({ asset }: { asset: Asset }) {
|
|
const [analysis, setAnalysis] = useState<MaskAnalysis>();
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
void analyzeMask(asset)
|
|
.then((nextAnalysis) => {
|
|
if (!cancelled) setAnalysis(nextAnalysis);
|
|
})
|
|
.catch(() => {
|
|
if (!cancelled) setAnalysis(undefined);
|
|
});
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [asset.source, asset.intrinsicSize.w, asset.intrinsicSize.h]);
|
|
|
|
if (!analysis) return <span className="rounded-full bg-white/5 px-3 py-1 text-xs text-sky-100/45">Reading</span>;
|
|
return (
|
|
<span className="inline-flex min-w-0 items-center gap-2 rounded-full bg-white/5 px-2 py-1 text-xs text-sky-100/65">
|
|
<img src={analysis.thumbnail} alt="" className="h-7 w-10 rounded-md bg-black/30 object-cover ring-1 ring-white/10" />
|
|
<span className="whitespace-nowrap">Reveal {formatPercent(analysis.coverage)}</span>
|
|
<span className="whitespace-nowrap text-sky-100/45">Inpaint {formatPercent(analysis.hiddenCoverage)}</span>
|
|
</span>
|
|
);
|
|
}
|
|
|
|
export function MaskOperationButtons({ maskLayerId, maskAsset, dispatch }: { maskLayerId: string; maskAsset: Asset; dispatch: AppStore["dispatch"] }) {
|
|
return (
|
|
<>
|
|
<MaskOperationButton label="Invert" title="Invert mask" maskLayerId={maskLayerId} maskAsset={maskAsset} operation={{ type: "invert" }} dispatch={dispatch} />
|
|
<MaskOperationButton label="White" title="Fill mask white" maskLayerId={maskLayerId} maskAsset={maskAsset} operation={{ type: "fill", fill: "white" }} dispatch={dispatch} />
|
|
<MaskOperationButton label="Black" title="Fill mask black" maskLayerId={maskLayerId} maskAsset={maskAsset} operation={{ type: "fill", fill: "black" }} dispatch={dispatch} />
|
|
<MaskOperationButton label="Clear" title="Clear mask" maskLayerId={maskLayerId} maskAsset={maskAsset} operation={{ type: "fill", fill: "clear" }} dispatch={dispatch} />
|
|
<MaskOperationButton label="Feather" title="Feather mask edge" maskLayerId={maskLayerId} maskAsset={maskAsset} operation={{ type: "feather", radius: 3 }} dispatch={dispatch} />
|
|
<MaskOperationButton label="Expand" title="Expand mask" maskLayerId={maskLayerId} maskAsset={maskAsset} operation={{ type: "expand", radius: 3 }} dispatch={dispatch} />
|
|
<MaskOperationButton label="Contract" title="Contract mask" maskLayerId={maskLayerId} maskAsset={maskAsset} operation={{ type: "contract", radius: 3 }} dispatch={dispatch} />
|
|
<MaskOperationButton label="Blur" title="Blur mask edge" maskLayerId={maskLayerId} maskAsset={maskAsset} operation={{ type: "blur", radius: 2 }} dispatch={dispatch} />
|
|
<MaskOperationButton label="Clean" title="Despeckle mask" maskLayerId={maskLayerId} maskAsset={maskAsset} operation={{ type: "despeckle", strength: 8 }} dispatch={dispatch} />
|
|
</>
|
|
);
|
|
}
|
|
|
|
function MaskOperationButton({
|
|
label,
|
|
title,
|
|
maskLayerId,
|
|
maskAsset,
|
|
operation,
|
|
dispatch,
|
|
}: {
|
|
label: string;
|
|
title: string;
|
|
maskLayerId: string;
|
|
maskAsset: Asset;
|
|
operation: MaskRasterOperation;
|
|
dispatch: AppStore["dispatch"];
|
|
}) {
|
|
const [busy, setBusy] = useState(false);
|
|
return (
|
|
<button
|
|
type="button"
|
|
className={maskActionButtonClass()}
|
|
disabled={busy}
|
|
title={title}
|
|
onClick={() => {
|
|
setBusy(true);
|
|
void runMaskOperation(maskLayerId, maskAsset, operation, dispatch)
|
|
.finally(() => setBusy(false));
|
|
}}
|
|
>
|
|
{busy ? "..." : label}
|
|
</button>
|
|
);
|
|
}
|
|
|
|
|
|
function maskActionButtonClass() { return "rounded-full bg-white/5 px-2.5 py-1 text-[0.7rem] font-semibold text-white/60 transition hover:bg-white/10 hover:text-white disabled:pointer-events-none disabled:opacity-35"; }
|
|
function formatPercent(value: number) { return `${Math.round(value * 100)}%`; }
|