Files
Genarrative/src/components/image-editor/ImageCanvasQuickEditPanelView.tsx
T
kdletters bb51c7e0d2 快速编辑直接覆盖原图
将图片快速编辑提交切到 /api/editor/images/edits,并用结果回写源图层

保留重绘作为右侧新结果图层,角色重绘继续走 character 生成契约

更新快速编辑文档和定向回归测试
2026-06-27 20:06:24 +08:00

173 lines
6.3 KiB
TypeScript

import { ImageIcon } from 'lucide-react';
import {
type CSSProperties,
type Dispatch,
type RefObject,
type SetStateAction,
type ReactNode,
} from 'react';
import { ImageCanvasBasicGenerationComposerView } from './ImageCanvasBasicGenerationComposerView';
import { QUICK_EDIT_REFERENCE_LIMIT } from './ImageCanvasGenerationModel';
import type {
CanvasLayer,
GenerateDialogState,
QuickEditPanelState,
UploadTarget,
} from './ImageCanvasEditorTypes';
export type ImageCanvasQuickEditPanelViewProps = {
panel: QuickEditPanelState;
sourceLayer: CanvasLayer;
style: CSSProperties;
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: UploadTarget) => void;
onRememberImageModel?: (model: string) => void;
onSubmit: () => void;
};
function resetFailedPanelStatus<
T extends { status: string; errorMessage?: string },
>(panel: T) {
return {
...panel,
status: panel.status === 'failed' ? 'idle' : panel.status,
errorMessage: panel.status === 'failed' ? undefined : panel.errorMessage,
};
}
function createQuickEditDialog(
panel: QuickEditPanelState,
): GenerateDialogState {
return {
mode: 'quick-edit',
prompt: panel.prompt,
status: panel.status,
sourceLayerId: panel.sourceLayerId,
generationReferences: panel.quickEditReferences,
imageModel: panel.model,
aspectRatio: panel.aspectRatio,
imageSize: panel.imageSize,
};
}
function syncPanelFromDialogUpdate(
panel: QuickEditPanelState,
dialog: GenerateDialogState,
) {
const normalizedPanel = resetFailedPanelStatus(panel);
const normalizedDialog = resetFailedPanelStatus(dialog);
return {
...normalizedPanel,
size: panel.size,
prompt: dialog.prompt,
aspectRatio:
normalizedDialog.aspectRatio ?? normalizedPanel.aspectRatio ?? '1:1',
imageSize: normalizedDialog.imageSize ?? normalizedPanel.imageSize ?? '1K',
model: normalizedDialog.imageModel ?? normalizedPanel.model,
quickEditReferences: normalizedDialog.generationReferences,
status: normalizedDialog.status,
errorMessage: normalizedDialog.errorMessage,
};
}
export function ImageCanvasQuickEditPanelView({
panel,
sourceLayer,
style,
referenceButtonRef,
isReferenceMenuOpen = false,
setIsReferenceMenuOpen,
setIsPickingQuickEditReferenceFromCanvas,
setQuickEditPanel,
renderEditorPortal = (node) => node,
buildPortalMenuStyle = () => ({}),
onRequestUpload,
onRememberImageModel = () => {},
onSubmit,
}: ImageCanvasQuickEditPanelViewProps) {
void sourceLayer;
const isRedraw = panel.mode === 'redraw';
const referenceCount = panel.quickEditReferences?.length ?? 0;
const canAddReferences =
!isRedraw && referenceCount < QUICK_EDIT_REFERENCE_LIMIT;
const quickEditDialog = createQuickEditDialog(panel);
return (
<ImageCanvasBasicGenerationComposerView
dialog={quickEditDialog}
style={style}
setGenerateDialog={(valueOrUpdater) => {
setQuickEditPanel((currentPanel) => {
if (!currentPanel) {
return currentPanel;
}
const currentDialog = createQuickEditDialog(currentPanel);
const nextDialog =
typeof valueOrUpdater === 'function'
? valueOrUpdater(currentDialog)
: valueOrUpdater;
if (!nextDialog) {
return currentPanel;
}
return syncPanelFromDialogUpdate(currentPanel, nextDialog);
});
}}
generationReferenceButtonRef={referenceButtonRef}
isGenerationReferenceMenuOpen={isReferenceMenuOpen}
setIsGenerationReferenceMenuOpen={setIsReferenceMenuOpen}
setIsPickingGenerationReferenceFromCanvas={
setIsPickingQuickEditReferenceFromCanvas
}
onRequestUpload={onRequestUpload ?? (() => {})}
onPickReferenceFromCanvas={() =>
setIsPickingQuickEditReferenceFromCanvas?.(true)
}
onToggleReferenceMenu={() => setIsReferenceMenuOpen?.((open) => !open)}
onRememberImageModel={onRememberImageModel}
onSubmit={onSubmit}
dialogLabel={isRedraw ? '重绘图片' : '快速编辑图片'}
includeDimensions={true}
includeModel={true}
includeReferences={!isRedraw}
promptLabel={isRedraw ? '重绘提示词' : '快速编辑提示词'}
promptPlaceholder={
isRedraw ? '把画面重绘成什么样?' : '想怎么编辑这张图?'
}
optionLabelPrefix={isRedraw ? '重绘' : '快速编辑'}
dimensionRatioAriaLabelPrefix="比例"
dimensionSizeAriaLabelPrefix="尺寸"
referenceUploadTarget="quick-edit-reference"
referenceButtonLabel={isRedraw ? '' : '添加快速编辑参考图'}
referenceButtonAriaLabel={isRedraw ? undefined : '添加快速编辑参考图'}
referenceMenuLabel={isRedraw ? '' : '快速编辑参考图来源'}
referenceSlotTone="quick-edit"
referenceSlotDisabled={!canAddReferences}
showReferenceAddButton={!isRedraw}
referenceLabelFormatter={(_, index) => `图${index + 1}`}
referenceAriaLabelFormatter={(_, index) => `图${index + 1}`}
referenceRemoveLabelFormatter={(_, index) => `删除图${index + 1}`}
formClassName="image-canvas-editor__quick-edit-panel image-canvas-editor__generation-composer image-canvas-editor__generation-composer--image image-canvas-editor__generation-composer--quick-edit"
footerClassName="image-canvas-editor__generation-composer-footer image-canvas-editor__quick-edit-footer"
promptClassName="image-canvas-editor__generation-prompt image-canvas-editor__quick-edit-prompt"
referenceSlotClassName="image-canvas-editor__quick-edit-reference"
submitButtonClassName="image-canvas-editor__generation-submit image-canvas-editor__quick-edit-submit"
submitLabel="生成"
submitAriaLabel="生成"
submittingStatusLabel="生成中"
renderEditorPortal={renderEditorPortal}
buildPortalMenuStyle={buildPortalMenuStyle}
/>
);
}