完善快速编辑参考图与生成占位
- 快速编辑支持最多8张额外参考图,并将原图自动作为最后一张参考图提交 - 快速编辑尺寸和模型沿用原图配置,按禁用态参数胶囊展示 - 快速编辑提交前同步改写原图引用为实际图序号 - 快速编辑生成改为创建独立画布生成占位,并支持键盘删除生成中对象 - 对齐生成类面板参考图图标、规范类固定参数和相关文档测试
This commit is contained in:
@@ -1,22 +1,47 @@
|
||||
import { X } from 'lucide-react';
|
||||
import { type CSSProperties, type Dispatch, type SetStateAction } from 'react';
|
||||
import { Cpu, ImageIcon, ImagePlus, X } from 'lucide-react';
|
||||
import {
|
||||
type CSSProperties,
|
||||
type Dispatch,
|
||||
type ReactNode,
|
||||
type RefObject,
|
||||
type SetStateAction,
|
||||
} from 'react';
|
||||
|
||||
import { PlatformActionButton } from '../common/PlatformActionButton';
|
||||
import {
|
||||
PlatformFloatingMenu,
|
||||
PlatformFloatingMenuItem,
|
||||
} from '../common/PlatformFloatingMenu';
|
||||
import { PlatformInlineOptionButton } from '../common/PlatformInlineOptionButton';
|
||||
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
|
||||
import { PlatformSelectField, PlatformTextField } from '../common/PlatformTextField';
|
||||
import { PlatformTextField } from '../common/PlatformTextField';
|
||||
import { EditorIconButton } from './ImageCanvasEditorPrimitives';
|
||||
import type {
|
||||
CanvasLayer,
|
||||
CharacterReferenceImage,
|
||||
QuickEditPanelState,
|
||||
} from './ImageCanvasEditorTypes';
|
||||
import {
|
||||
EDITOR_GENERATION_MUD_POINT_CONFIG,
|
||||
QUICK_EDIT_REFERENCE_LIMIT,
|
||||
} from './ImageCanvasGenerationModel';
|
||||
import { ImageCanvasReferenceSlot } from './ImageCanvasReferenceSlot';
|
||||
|
||||
type ImageCanvasQuickEditPanelViewProps = {
|
||||
panel: QuickEditPanelState;
|
||||
sourceLayer: CanvasLayer;
|
||||
style: CSSProperties;
|
||||
sizeOptions: string[];
|
||||
modelOptions: Array<{ label: string; value: string }>;
|
||||
referenceButtonRef?: RefObject<HTMLButtonElement | null>;
|
||||
isReferenceMenuOpen?: boolean;
|
||||
setIsReferenceMenuOpen?: Dispatch<SetStateAction<boolean>>;
|
||||
setIsPickingQuickEditReferenceFromCanvas?: Dispatch<SetStateAction<boolean>>;
|
||||
setQuickEditPanel: Dispatch<SetStateAction<QuickEditPanelState | null>>;
|
||||
renderEditorPortal?: (node: ReactNode) => ReactNode;
|
||||
buildPortalMenuStyle?: (
|
||||
anchor: HTMLElement | null,
|
||||
placement: 'above' | 'below',
|
||||
) => CSSProperties;
|
||||
onRequestUpload?: (target: 'quick-edit-reference') => void;
|
||||
onSubmit: () => void;
|
||||
};
|
||||
|
||||
@@ -30,118 +55,226 @@ function resetFailedPanelStatus<T extends { status: string; errorMessage?: strin
|
||||
};
|
||||
}
|
||||
|
||||
function QuickEditReferenceChip({
|
||||
reference,
|
||||
index,
|
||||
onRemove,
|
||||
}: {
|
||||
reference: CharacterReferenceImage;
|
||||
index: number;
|
||||
onRemove: () => void;
|
||||
}) {
|
||||
const label = `图${index + 1}`;
|
||||
return (
|
||||
<ImageCanvasReferenceSlot
|
||||
tone="quick-edit"
|
||||
icon={<ImageIcon className="h-4 w-4" aria-hidden="true" />}
|
||||
imageSrc={reference.src}
|
||||
label={label}
|
||||
ariaLabel={label}
|
||||
title={reference.label}
|
||||
onRemove={onRemove}
|
||||
removeLabel={`删除${label}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function ImageCanvasQuickEditPanelView({
|
||||
panel,
|
||||
sourceLayer,
|
||||
style,
|
||||
sizeOptions,
|
||||
modelOptions,
|
||||
referenceButtonRef,
|
||||
isReferenceMenuOpen = false,
|
||||
setIsReferenceMenuOpen,
|
||||
setIsPickingQuickEditReferenceFromCanvas,
|
||||
setQuickEditPanel,
|
||||
renderEditorPortal = (node) => node,
|
||||
buildPortalMenuStyle = () => ({}),
|
||||
onRequestUpload = () => {},
|
||||
onSubmit,
|
||||
}: ImageCanvasQuickEditPanelViewProps) {
|
||||
const isRedraw = panel.mode === 'redraw';
|
||||
const isGenerating = panel.status === 'generating';
|
||||
const panelLabel = isRedraw ? '重绘图片' : '快速编辑图片';
|
||||
const promptLabel = isRedraw ? '重绘提示词' : '快速编辑提示词';
|
||||
const references = panel.quickEditReferences ?? [];
|
||||
const sourceReferenceIndex = references.length + 1;
|
||||
const canAddReferences =
|
||||
!isRedraw && !isGenerating && references.length < QUICK_EDIT_REFERENCE_LIMIT;
|
||||
return (
|
||||
<form
|
||||
className="image-canvas-editor__quick-edit-panel"
|
||||
style={style}
|
||||
role="dialog"
|
||||
aria-label={panelLabel}
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
onSubmit();
|
||||
}}
|
||||
>
|
||||
<div className="image-canvas-editor__quick-edit-head">
|
||||
<div className="image-canvas-editor__quick-edit-reference">
|
||||
<img src={sourceLayer.src} alt={`${sourceLayer.title}参考图`} />
|
||||
<span>{sourceLayer.title}</span>
|
||||
<>
|
||||
<form
|
||||
className="image-canvas-editor__quick-edit-panel image-canvas-editor__generation-composer image-canvas-editor__generation-composer--image image-canvas-editor__generation-composer--quick-edit"
|
||||
style={style}
|
||||
role="dialog"
|
||||
aria-label={panelLabel}
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
onSubmit();
|
||||
}}
|
||||
>
|
||||
<div className="image-canvas-editor__reference-strip">
|
||||
{!isRedraw
|
||||
? references.map((reference, index) => (
|
||||
<QuickEditReferenceChip
|
||||
key={reference.id}
|
||||
reference={reference}
|
||||
index={index}
|
||||
onRemove={() =>
|
||||
setQuickEditPanel((currentPanel) =>
|
||||
currentPanel
|
||||
? {
|
||||
...resetFailedPanelStatus(currentPanel),
|
||||
quickEditReferences: (
|
||||
currentPanel.quickEditReferences ?? []
|
||||
).filter((item) => item.id !== reference.id),
|
||||
}
|
||||
: currentPanel,
|
||||
)
|
||||
}
|
||||
/>
|
||||
))
|
||||
: null}
|
||||
{!isRedraw ? (
|
||||
<ImageCanvasReferenceSlot
|
||||
buttonRef={referenceButtonRef}
|
||||
tone="quick-edit"
|
||||
icon={<ImagePlus className="h-4 w-4" aria-hidden="true" />}
|
||||
label="参考图"
|
||||
ariaLabel="添加快速编辑参考图"
|
||||
disabled={!canAddReferences}
|
||||
isAdd
|
||||
onClick={() => setIsReferenceMenuOpen?.((open) => !open)}
|
||||
/>
|
||||
) : null}
|
||||
<ImageCanvasReferenceSlot
|
||||
tone="quick-edit"
|
||||
icon={<ImageIcon className="h-4 w-4" aria-hidden="true" />}
|
||||
imageSrc={sourceLayer.src}
|
||||
label={`图${sourceReferenceIndex}`}
|
||||
ariaLabel={`原图 图${sourceReferenceIndex}`}
|
||||
title={sourceLayer.title}
|
||||
/>
|
||||
</div>
|
||||
<PlatformTextField
|
||||
variant="textarea"
|
||||
aria-label={promptLabel}
|
||||
value={panel.prompt}
|
||||
disabled={isGenerating}
|
||||
size="sm"
|
||||
density="compact"
|
||||
className="image-canvas-editor__generation-prompt image-canvas-editor__quick-edit-prompt"
|
||||
onChange={(event) =>
|
||||
setQuickEditPanel((currentPanel) =>
|
||||
currentPanel
|
||||
? {
|
||||
...resetFailedPanelStatus(currentPanel),
|
||||
prompt: event.target.value,
|
||||
}
|
||||
: currentPanel,
|
||||
)
|
||||
}
|
||||
/>
|
||||
{panel.status === 'failed' ? (
|
||||
<PlatformStatusMessage
|
||||
tone="error"
|
||||
surface="platform"
|
||||
size="xs"
|
||||
className="image-canvas-editor__generate-status"
|
||||
role="alert"
|
||||
>
|
||||
{panel.errorMessage}
|
||||
</PlatformStatusMessage>
|
||||
) : null}
|
||||
<div className="image-canvas-editor__generation-composer-footer image-canvas-editor__quick-edit-footer">
|
||||
<div className="image-canvas-editor__readonly-generation-option">
|
||||
<PlatformInlineOptionButton
|
||||
className="image-canvas-editor__option-cluster image-canvas-editor__option-cluster--dimensions"
|
||||
aria-label={`${isRedraw ? '重绘' : '快速编辑'}尺寸 ${panel.size}`}
|
||||
disabled
|
||||
>
|
||||
{panel.size}
|
||||
</PlatformInlineOptionButton>
|
||||
</div>
|
||||
<div className="image-canvas-editor__readonly-generation-option">
|
||||
<PlatformInlineOptionButton
|
||||
className="image-canvas-editor__option-cluster image-canvas-editor__option-cluster--model"
|
||||
aria-label={`${isRedraw ? '重绘' : '快速编辑'}模型 ${panel.model}`}
|
||||
disabled
|
||||
>
|
||||
<span className="image-canvas-editor__model-trigger-label">
|
||||
<span
|
||||
className="image-canvas-editor__model-icon"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Cpu />
|
||||
</span>
|
||||
<span>{panel.model}</span>
|
||||
</span>
|
||||
</PlatformInlineOptionButton>
|
||||
</div>
|
||||
<PlatformActionButton
|
||||
type="submit"
|
||||
tone="secondary"
|
||||
size="xs"
|
||||
shape="pill"
|
||||
className="image-canvas-editor__generation-submit image-canvas-editor__quick-edit-submit"
|
||||
disabled={isGenerating}
|
||||
aria-label={isGenerating ? '生成中' : '生成'}
|
||||
>
|
||||
{isGenerating ? (
|
||||
'生成中'
|
||||
) : isRedraw ? (
|
||||
'生成'
|
||||
) : (
|
||||
<>
|
||||
<span>生成</span>
|
||||
<span className="image-canvas-editor__mud-point-inline">
|
||||
{EDITOR_GENERATION_MUD_POINT_CONFIG.image}泥点
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</PlatformActionButton>
|
||||
</div>
|
||||
<EditorIconButton
|
||||
className="image-canvas-editor__generation-close"
|
||||
label={`关闭${panelLabel}`}
|
||||
title="关闭"
|
||||
icon={X}
|
||||
variant="surfaceFloating"
|
||||
disabled={isGenerating}
|
||||
onClick={() => setQuickEditPanel(null)}
|
||||
/>
|
||||
</div>
|
||||
<PlatformTextField
|
||||
variant="textarea"
|
||||
aria-label={promptLabel}
|
||||
value={panel.prompt}
|
||||
disabled={isGenerating}
|
||||
size="sm"
|
||||
density="compact"
|
||||
className="image-canvas-editor__quick-edit-prompt"
|
||||
onChange={(event) =>
|
||||
setQuickEditPanel((currentPanel) =>
|
||||
currentPanel
|
||||
? {
|
||||
...resetFailedPanelStatus(currentPanel),
|
||||
prompt: event.target.value,
|
||||
}
|
||||
: currentPanel,
|
||||
</form>
|
||||
{!isRedraw && isReferenceMenuOpen && referenceButtonRef
|
||||
? renderEditorPortal(
|
||||
<PlatformFloatingMenu
|
||||
className="image-canvas-editor__spec-menu image-canvas-editor__portal-menu"
|
||||
label="快速编辑参考图来源"
|
||||
placement="top-start"
|
||||
style={buildPortalMenuStyle(referenceButtonRef.current, 'above')}
|
||||
>
|
||||
<PlatformFloatingMenuItem
|
||||
onClick={() => {
|
||||
setIsReferenceMenuOpen?.(false);
|
||||
setIsPickingQuickEditReferenceFromCanvas?.(true);
|
||||
}}
|
||||
>
|
||||
从画布中选择
|
||||
</PlatformFloatingMenuItem>
|
||||
<PlatformFloatingMenuItem
|
||||
onClick={() => {
|
||||
setIsReferenceMenuOpen?.(false);
|
||||
setIsPickingQuickEditReferenceFromCanvas?.(false);
|
||||
onRequestUpload('quick-edit-reference');
|
||||
}}
|
||||
>
|
||||
上传图片
|
||||
</PlatformFloatingMenuItem>
|
||||
</PlatformFloatingMenu>,
|
||||
)
|
||||
}
|
||||
/>
|
||||
<div className="image-canvas-editor__quick-edit-controls">
|
||||
<PlatformSelectField
|
||||
aria-label={isRedraw ? '重绘尺寸' : '快速编辑尺寸'}
|
||||
value={panel.size}
|
||||
disabled={isGenerating}
|
||||
size="xs"
|
||||
density="compact"
|
||||
onChange={(event) =>
|
||||
setQuickEditPanel((currentPanel) =>
|
||||
currentPanel
|
||||
? { ...currentPanel, size: event.target.value }
|
||||
: currentPanel,
|
||||
)
|
||||
}
|
||||
>
|
||||
{sizeOptions.map((size) => (
|
||||
<option key={size} value={size}>
|
||||
{size}
|
||||
</option>
|
||||
))}
|
||||
</PlatformSelectField>
|
||||
<PlatformSelectField
|
||||
aria-label={isRedraw ? '重绘模型' : '快速编辑模型'}
|
||||
value={panel.model}
|
||||
disabled={isGenerating}
|
||||
size="xs"
|
||||
density="compact"
|
||||
onChange={(event) =>
|
||||
setQuickEditPanel((currentPanel) =>
|
||||
currentPanel
|
||||
? { ...currentPanel, model: event.target.value }
|
||||
: currentPanel,
|
||||
)
|
||||
}
|
||||
>
|
||||
{modelOptions.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</PlatformSelectField>
|
||||
</div>
|
||||
{panel.status === 'failed' ? (
|
||||
<PlatformStatusMessage tone="error" surface="platform" size="xs" role="alert">
|
||||
{panel.errorMessage}
|
||||
</PlatformStatusMessage>
|
||||
) : null}
|
||||
<PlatformActionButton
|
||||
type="submit"
|
||||
tone="secondary"
|
||||
size="sm"
|
||||
className="image-canvas-editor__quick-edit-submit"
|
||||
disabled={isGenerating}
|
||||
>
|
||||
{isGenerating ? '生成中' : '生成'}
|
||||
</PlatformActionButton>
|
||||
</form>
|
||||
: null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user