feat: unify AI edit workflow
This commit is contained in:
@@ -20,6 +20,7 @@ export function GenerateActionControls({ settings, generation, dispatch, workflo
|
||||
const precondition = workflow.precondition();
|
||||
const canGenerate = precondition.ready && !busy;
|
||||
const preconditionMessage = precondition.ready ? undefined : precondition.message;
|
||||
const repair = precondition.ready ? undefined : precondition.repair;
|
||||
|
||||
return (
|
||||
<div className="flex max-w-[calc(100vw-2rem)] flex-wrap items-center justify-center gap-2 px-2">
|
||||
@@ -34,7 +35,16 @@ export function GenerateActionControls({ settings, generation, dispatch, workflo
|
||||
>
|
||||
{busy && job?.kind === "generate" ? "Generating..." : "Generate"}
|
||||
</button>
|
||||
{preconditionMessage ? <span className="max-w-72 text-xs text-white/55" role="status">{preconditionMessage}</span> : null}
|
||||
{preconditionMessage ? (
|
||||
<span className="flex max-w-80 items-center gap-2 rounded-full bg-amber-300/10 px-3 py-2 text-xs text-amber-100/75" role="status">
|
||||
<span>{preconditionMessage}</span>
|
||||
{repair === "add-mask" ? (
|
||||
<button type="button" className="shrink-0 rounded-full bg-white px-3 py-1 font-semibold text-black" onClick={() => void workflow.prepareInpaintMask()}>Add mask</button>
|
||||
) : repair === "set-outpaint-padding" ? (
|
||||
<button type="button" className="shrink-0 rounded-full bg-white px-3 py-1 font-semibold text-black" onClick={() => dispatch(commandIds.toolSetGenerateSettings, { outpaint: { ...settings.outpaint, left: 128, right: 128 } })}>Add padding</button>
|
||||
) : null}
|
||||
</span>
|
||||
) : null}
|
||||
<GenerationJobStatus generation={generation} />
|
||||
{busy ? <button type="button" className="h-9 rounded-full bg-white/5 px-3 text-xs font-semibold text-white/70 hover:bg-white/10 hover:text-white" onClick={workflow.cancel}>Cancel</button> : null}
|
||||
{candidate ? (
|
||||
@@ -56,25 +66,26 @@ export function GenerateActionControls({ settings, generation, dispatch, workflo
|
||||
|
||||
function CandidatePicker({ generation, dispatch }: { generation: GenerationState; dispatch: AppStore["dispatch"] }) {
|
||||
return (
|
||||
<div className="subtle-scrollbar flex max-w-[min(28rem,calc(100vw-2rem))] items-center gap-1 overflow-x-auto rounded-full bg-white/[0.04] px-1.5 py-1 ring-1 ring-white/[0.05]" role="group" aria-label="Generation candidates">
|
||||
<div className="subtle-scrollbar flex max-w-[min(34rem,calc(100vw-2rem))] items-stretch gap-2 overflow-x-auto rounded-[1.25rem] bg-black/20 p-2 ring-1 ring-white/[0.08]" role="group" aria-label="Provisional generation candidates">
|
||||
{generation.candidates.map((candidate) => {
|
||||
const selected = candidate.id === (generation.selectedCandidateId ?? generation.candidates[0]?.id);
|
||||
return (
|
||||
<button
|
||||
key={candidate.id}
|
||||
type="button"
|
||||
className={`h-9 w-9 overflow-hidden rounded-full ring-2 transition ${selected ? "ring-white" : "ring-white/10 hover:ring-white/40"}`}
|
||||
title={`Candidate seed ${candidate.seed}`}
|
||||
className={`relative h-16 w-16 shrink-0 overflow-hidden rounded-xl ring-2 transition ${selected ? "ring-white" : "ring-white/10 hover:ring-white/40"}`}
|
||||
title={`Provisional candidate · seed ${candidate.seed}`}
|
||||
onClick={() => dispatch(commandIds.generationSelectCandidate, { candidateId: candidate.id })}
|
||||
>
|
||||
<img src={candidate.source} alt="" className="h-full w-full object-cover" />
|
||||
{selected ? <span className="absolute inset-x-1 bottom-1 rounded-full bg-black/70 px-1 py-0.5 text-[0.6rem] font-semibold text-white">Reviewing</span> : null}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
<button
|
||||
type="button"
|
||||
disabled={generation.candidates.length === 0}
|
||||
className="h-9 shrink-0 rounded-full px-3 text-xs font-semibold text-white/55 transition hover:bg-white/10 hover:text-white disabled:pointer-events-none disabled:opacity-35"
|
||||
className="min-h-16 shrink-0 rounded-xl px-3 text-xs font-semibold text-white/55 transition hover:bg-white/10 hover:text-white disabled:pointer-events-none disabled:opacity-35"
|
||||
title="Dismiss every candidate and reset comparison"
|
||||
onClick={() => dispatch(commandIds.generationClearCandidates, undefined)}
|
||||
>
|
||||
@@ -105,11 +116,9 @@ function CandidateControls({
|
||||
const disabled = busy;
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center justify-center gap-1 rounded-full bg-white/[0.04] px-2 py-1 ring-1 ring-white/[0.05]">
|
||||
<div className="flex flex-wrap items-center justify-center gap-1 rounded-[1.25rem] bg-white/[0.04] px-2 py-2 ring-1 ring-white/[0.05]">
|
||||
<CandidatePreview candidate={candidate} />
|
||||
<span className="px-2 text-xs font-medium text-white/55" title={`${candidate.settings.model} · ${candidate.mode}`}>
|
||||
Seed {candidate.seed}
|
||||
</span>
|
||||
<span className="px-2 text-xs font-medium text-white/55" title={`${candidate.settings.model} · ${candidate.mode}`}><strong className="block font-semibold text-white/75">Provisional result</strong>Seed {candidate.seed}</span>
|
||||
<CandidateCompareControls compareMode={compareMode} disabled={disabled} dispatch={dispatch} />
|
||||
<CandidateButton disabled={disabled} label="Regenerate" title="Regenerate same mask and crop" onClick={() => rerun("Regenerate", candidate.settings)} />
|
||||
<CandidateButton
|
||||
|
||||
Reference in New Issue
Block a user