import { commandIds } from "@commands/ids";
import type { GenerationCandidate, GenerationCompareMode, GenerationState } from "@editor/state";
import type { GenerateSettings } from "@editor/tools";
import type { AppStore } from "@editor/store";
import type { GenerationWorkflow } from "@operations/generation/workflow";
import { currentGenerationJob, GenerationJobStatus } from "../GenerationJobStatus";
export type GenerateActionControlsProps = {
settings: GenerateSettings;
generation: GenerationState;
dispatch: AppStore["dispatch"];
workflow: GenerationWorkflow;
};
export function GenerateActionControls({ settings, generation, dispatch, workflow }: GenerateActionControlsProps) {
const job = currentGenerationJob(generation);
const busy = job?.status === "running";
const candidate = selectedCandidate(generation);
const precondition = workflow.precondition();
const canGenerate = precondition.ready && !busy;
const preconditionMessage = precondition.ready ? undefined : precondition.message;
return (
{preconditionMessage ? {preconditionMessage} : null}
{candidate ? (
<>
>
) : null}
);
}
function CandidatePicker({ generation, dispatch }: { generation: GenerationState; dispatch: AppStore["dispatch"] }) {
if (generation.candidates.length < 2) return null;
return (
{generation.candidates.map((candidate) => {
const selected = candidate.id === (generation.selectedCandidateId ?? generation.candidates[0]?.id);
return (
);
})}
);
}
function CandidateControls({
candidate,
compareMode,
settings,
busy,
dispatch,
workflow,
}: {
candidate: GenerationCandidate;
compareMode: GenerationCompareMode;
settings: GenerateSettings;
busy: boolean;
dispatch: AppStore["dispatch"];
workflow: GenerationWorkflow;
}) {
const rerun = (label: string, nextSettings: GenerateSettings) => {
void workflow.regenerate(candidate.id, nextSettings, label);
};
const disabled = busy;
return (
Seed {candidate.seed}
rerun("Regenerate", candidate.settings)} />
rerun("Lower", { ...candidate.settings, strength: Math.max(0, candidate.settings.strength - 10), seed: candidate.seed })}
/>
rerun("Reuse seed", { ...candidate.settings, seed: candidate.seed })} />
rerun("New seed", { ...candidate.settings, seed: -1 })} />
workflow.applyCandidateAsLayer(candidate.id)} />
{
void workflow.applyCandidateAsRefinementLayer(candidate.id);
}}
/>
{
void workflow.replaceCandidatePixels(candidate.id);
}}
/>
{
if (!candidate.inpaint) return;
dispatch(commandIds.selectionSet, { artboardId: candidate.placement.artboardId, layerIds: [candidate.inpaint.targetLayerId] });
dispatch(commandIds.toolEnterMaskEdit, { targetLayerId: candidate.inpaint.targetLayerId, maskLayerId: candidate.inpaint.maskLayerId });
}}
/>
dispatch(commandIds.generationRemoveCandidate, { candidateId: candidate.id })} />
{settings.seed !== candidate.seed ? null : Current settings reuse this seed}
);
}
function CandidateCompareControls({ compareMode, disabled, dispatch }: { compareMode: GenerationCompareMode; disabled: boolean; dispatch: AppStore["dispatch"] }) {
return (
{generationCompareOptions.map((option) => {
const active = compareMode === option.mode;
return (
);
})}
);
}
const generationCompareOptions: Array<{ mode: GenerationCompareMode; label: string; title: string }> = [
{ mode: "result", label: "After", title: "Show the generated result over the document" },
{ mode: "before", label: "Before", title: "Hide the generated result and show the source document" },
{ mode: "split", label: "Split", title: "Compare source on the left with result on the right" },
];
function CandidatePreview({ candidate }: { candidate: GenerationCandidate }) {
if (!candidate.inputImage) {
return
;
}
return (
{candidate.maskImage ?
: null}
);
}
function CandidateButton({ label, title, disabled, busy, onClick }: { label: string; title: string; disabled?: boolean; busy?: boolean; onClick: () => void }) {
return (
);
}
function selectedCandidate(generation: GenerationState): GenerationCandidate | undefined {
return generation.candidates.find((candidate) => candidate.id === generation.selectedCandidateId) ?? generation.candidates[0];
}