修复画布快速编辑回写与原图元数据
快速编辑回到原图面板流程,避免新建生成器占位 提交快速编辑时传入目标图层并在后端成功后回写画布 快速编辑生成输入记录原图引用,图片信息可展示原图来源 同步前后端测试与图片画布文档记忆
This commit is contained in:
@@ -183,6 +183,48 @@ describe('ImageCanvasBasicGenerationComposerView', () => {
|
||||
expect(submitButton.textContent).toBe('生成5泥点');
|
||||
});
|
||||
|
||||
it('keeps quick edit to one prompt box and model selection', () => {
|
||||
render(
|
||||
<BasicGenerationHarness
|
||||
initialDialog={createDialog({
|
||||
mode: 'quick-edit',
|
||||
sourceLayerId: 'layer-source',
|
||||
generationReferences: [
|
||||
{
|
||||
id: 'ref-a',
|
||||
label: '旧参考图',
|
||||
src: 'data:image/png;base64,cmVmQQ==',
|
||||
},
|
||||
],
|
||||
imageModel: 'gpt-image-2',
|
||||
aspectRatio: '1:1',
|
||||
imageSize: '1K',
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
|
||||
const panel = screen.getByRole('dialog', { name: '快速编辑图片' });
|
||||
|
||||
expect(
|
||||
within(panel).getByRole('textbox', { name: '快速编辑提示词' }),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
within(panel).queryByRole('button', { name: '添加参考图' }),
|
||||
).toBeNull();
|
||||
expect(within(panel).queryByText('旧参考图')).toBeNull();
|
||||
expect(
|
||||
within(panel).queryByRole('button', {
|
||||
name: /快速编辑图片尺寸/u,
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(
|
||||
within(panel).getByRole('button', {
|
||||
name: '快速编辑图片模型 gpt-image-2',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
expect(within(panel).getByRole('button', { name: '修改' })).toBeTruthy();
|
||||
});
|
||||
|
||||
it('clicks the parent generation panel to collapse an opened option panel', () => {
|
||||
render(<BasicGenerationHarness />);
|
||||
|
||||
|
||||
@@ -145,13 +145,23 @@ export function ImageCanvasBasicGenerationComposerView({
|
||||
promptLabel ?? (isQuickEdit ? '快速编辑提示词' : '生成提示词');
|
||||
const resolvedPromptPlaceholder =
|
||||
promptPlaceholder ??
|
||||
(isQuickEdit ? '想怎么编辑这张图?' : '今天想生成什么画面?');
|
||||
(isQuickEdit ? '写下每个编号要怎么改' : '今天想生成什么画面?');
|
||||
const resolvedOptionLabelPrefix =
|
||||
optionLabelPrefix ?? (isQuickEdit ? '快速编辑图片' : '生成图片');
|
||||
const resolvedReferenceButtonLabel = referenceButtonLabel;
|
||||
const resolvedReferenceButtonAriaLabel =
|
||||
referenceButtonAriaLabel ?? `添加${resolvedReferenceButtonLabel}`;
|
||||
const hasReferenceMenu = includeReferences && onRequestUpload;
|
||||
const shouldIncludeReferences = isQuickEdit ? false : includeReferences;
|
||||
const shouldIncludeDimensions = isQuickEdit ? false : includeDimensions;
|
||||
const resolvedSubmitLabel =
|
||||
isQuickEdit && submitLabel === '生成' ? '修改' : submitLabel;
|
||||
const resolvedSubmitAriaLabel =
|
||||
isQuickEdit && submitAriaLabel === '生成' ? '修改' : submitAriaLabel;
|
||||
const resolvedSubmittingStatusLabel =
|
||||
isQuickEdit && submittingStatusLabel === '生成中'
|
||||
? '修改中'
|
||||
: submittingStatusLabel;
|
||||
const hasReferenceMenu = shouldIncludeReferences && onRequestUpload;
|
||||
const finalFormClassName =
|
||||
formClassName ??
|
||||
'image-canvas-editor__generation-composer image-canvas-editor__generation-composer--image';
|
||||
@@ -179,7 +189,7 @@ export function ImageCanvasBasicGenerationComposerView({
|
||||
}
|
||||
}}
|
||||
>
|
||||
{includeReferences ? (
|
||||
{shouldIncludeReferences ? (
|
||||
<div className="image-canvas-editor__reference-strip">
|
||||
{references.map((reference, index) => {
|
||||
const label =
|
||||
@@ -259,7 +269,7 @@ export function ImageCanvasBasicGenerationComposerView({
|
||||
<ImageCanvasGenerationImageOptionsView
|
||||
dialog={dialog}
|
||||
setGenerateDialog={setGenerateDialog}
|
||||
includeDimensions={includeDimensions}
|
||||
includeDimensions={shouldIncludeDimensions}
|
||||
includeModel={includeModel}
|
||||
onRememberImageModel={onRememberImageModel}
|
||||
dimensionRatioAriaLabelPrefix={dimensionRatioAriaLabelPrefix}
|
||||
@@ -269,8 +279,8 @@ export function ImageCanvasBasicGenerationComposerView({
|
||||
model: dialog.imageModel,
|
||||
imageSize: dialog.imageSize,
|
||||
})}
|
||||
submitLabel={submitLabel}
|
||||
submitAriaLabel={submitAriaLabel}
|
||||
submitLabel={resolvedSubmitLabel}
|
||||
submitAriaLabel={resolvedSubmitAriaLabel}
|
||||
submitButtonClassName={submitButtonClassName}
|
||||
renderEditorPortal={renderEditorPortal}
|
||||
buildPortalMenuStyle={buildPortalMenuStyle}
|
||||
@@ -284,7 +294,7 @@ export function ImageCanvasBasicGenerationComposerView({
|
||||
className="image-canvas-editor__generate-status"
|
||||
role="status"
|
||||
>
|
||||
{submittingStatusLabel}
|
||||
{resolvedSubmittingStatusLabel}
|
||||
</PlatformStatusMessage>
|
||||
) : null}
|
||||
{dialog.status === 'failed' ? (
|
||||
|
||||
@@ -566,14 +566,12 @@ describe('ImageCanvasEditorView generation integration', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: '快速编辑' }));
|
||||
|
||||
expect(
|
||||
await screen.findByRole('dialog', { name: '生成角色形象' }),
|
||||
await screen.findByRole('dialog', { name: '快速编辑图片' }),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
(screen.getByLabelText('角色设定') as HTMLTextAreaElement).value,
|
||||
).toBe('黑衣剑士');
|
||||
expect(screen.queryByRole('dialog', { name: '生成角色形象' })).toBeNull();
|
||||
});
|
||||
|
||||
it('opens the matching generator from image context quick edit for tagged uploaded images', async () => {
|
||||
it('opens quick edit from image context for tagged uploaded images', async () => {
|
||||
loadOrCreateRecentEditorProjectMock.mockResolvedValueOnce({
|
||||
projectId: 'editor-project-context-tagged-layer',
|
||||
title: '右键标签素材画布',
|
||||
@@ -617,13 +615,10 @@ describe('ImageCanvasEditorView generation integration', () => {
|
||||
),
|
||||
);
|
||||
|
||||
expect(screen.queryByRole('dialog', { name: '快速编辑图片' })).toBeNull();
|
||||
expect(
|
||||
await screen.findByRole('dialog', { name: '生成角色形象' }),
|
||||
await screen.findByRole('dialog', { name: '快速编辑图片' }),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
(screen.getByLabelText('角色设定') as HTMLTextAreaElement).value,
|
||||
).toBe('红披风骑士');
|
||||
expect(screen.queryByRole('dialog', { name: '生成角色形象' })).toBeNull();
|
||||
});
|
||||
|
||||
it('restores publication material composer fields and references from saved generator snapshots', async () => {
|
||||
|
||||
@@ -1161,6 +1161,7 @@ export function ImageCanvasEditorView() {
|
||||
setIsPickingUiDesignSpecFromCanvas,
|
||||
openCharacterAnimationPanel,
|
||||
openRedrawPanel,
|
||||
openQuickEditPanel,
|
||||
openCropExpandPanel,
|
||||
removeSelectedLayerBackground,
|
||||
extractUiDesignAssets,
|
||||
@@ -1903,7 +1904,7 @@ export function ImageCanvasEditorView() {
|
||||
onFocusExternalTask: focusExternalGenerationTask,
|
||||
onToggleTaskSidebar: generationSurface.toggleTaskSidebar,
|
||||
onCropExpandHandlePointerDown: generationSurface.startCropExpandFrameResize,
|
||||
onOpenQuickEditPanel: openLayerGenerationDialog,
|
||||
onOpenQuickEditPanel: openQuickEditPanel,
|
||||
onOpenRedrawPanel: openRedrawPanel,
|
||||
onOpenCropExpandPanel: openCropExpandPanel,
|
||||
onRemoveBackground: removeSelectedLayerBackground,
|
||||
|
||||
@@ -117,7 +117,7 @@ function renderComposer(
|
||||
}
|
||||
|
||||
describe('ImageCanvasGenerationComposerView', () => {
|
||||
it('让快速编辑复用普通图片生成面板结构', () => {
|
||||
it('让快速编辑只保留提示词和模型选择', () => {
|
||||
renderComposer({
|
||||
mode: 'quick-edit',
|
||||
prompt: '',
|
||||
@@ -142,6 +142,20 @@ describe('ImageCanvasGenerationComposerView', () => {
|
||||
);
|
||||
expect(within(panel).getByRole('textbox', { name: '快速编辑提示词' }))
|
||||
.toBeTruthy();
|
||||
expect(
|
||||
within(panel).queryByRole('button', { name: '添加参考图' }),
|
||||
).toBeNull();
|
||||
expect(
|
||||
within(panel).queryByRole('button', {
|
||||
name: /快速编辑图片尺寸/u,
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(
|
||||
within(panel).getByRole('button', {
|
||||
name: '快速编辑图片模型 gpt-image-2',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
expect(within(panel).getByRole('button', { name: '修改' })).toBeTruthy();
|
||||
expect(
|
||||
panel.querySelector('.image-canvas-editor__generation-composer-footer'),
|
||||
).toBeTruthy();
|
||||
|
||||
@@ -1198,11 +1198,7 @@ export function ImageCanvasGenerationComposerView({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{quickEditPanel &&
|
||||
(quickEditPanel.mode === 'redraw' ||
|
||||
quickEditPanel.status !== 'generating') &&
|
||||
quickEditSourceLayer &&
|
||||
quickEditPanelStyle ? (
|
||||
{quickEditPanel && quickEditSourceLayer && quickEditPanelStyle ? (
|
||||
<ImageCanvasQuickEditPanelView
|
||||
panel={quickEditPanel}
|
||||
sourceLayer={quickEditSourceLayer}
|
||||
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
updateSpecFormDialogValue,
|
||||
} from './ImageCanvasGenerationDialogModel';
|
||||
import {
|
||||
IMAGE_MODEL_NANOBANANA2,
|
||||
IMAGE_MODEL_GPT_IMAGE_2,
|
||||
} from './ImageCanvasGenerationModel';
|
||||
|
||||
function createLayer(overrides: Partial<CanvasLayer> = {}): CanvasLayer {
|
||||
@@ -649,21 +649,21 @@ describe('ImageCanvasGenerationDialogModel', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('normalizes legacy nanobanana model aliases in quick edit drafts', () => {
|
||||
it('locks quick edit drafts to gpt-image-2 despite legacy source models', () => {
|
||||
expect(
|
||||
createQuickEditPanelDraft(
|
||||
createLayer({
|
||||
model: 'nano-banana',
|
||||
}),
|
||||
).model,
|
||||
).toBe(IMAGE_MODEL_NANOBANANA2);
|
||||
).toBe(IMAGE_MODEL_GPT_IMAGE_2);
|
||||
expect(
|
||||
createQuickEditPanelDraft(
|
||||
createLayer({
|
||||
model: 'nanobanana2',
|
||||
}),
|
||||
).model,
|
||||
).toBe(IMAGE_MODEL_NANOBANANA2);
|
||||
).toBe(IMAGE_MODEL_GPT_IMAGE_2);
|
||||
});
|
||||
|
||||
it('creates character animation generation dialog drafts for character layers', () => {
|
||||
|
||||
@@ -1187,9 +1187,7 @@ export function createQuickEditPanelDraft(
|
||||
sourceLayer.originalWidth,
|
||||
sourceLayer.originalHeight,
|
||||
);
|
||||
const model = normalizeEditorImageModel(
|
||||
options.imageModel ?? sourceLayer.model,
|
||||
);
|
||||
const model = IMAGE_MODEL_GPT_IMAGE_2;
|
||||
return {
|
||||
mode: 'quick-edit',
|
||||
sourceLayerId: sourceLayer.id,
|
||||
@@ -1211,7 +1209,6 @@ export function createQuickEditGenerationDialogDraft({
|
||||
prompt,
|
||||
status = 'idle',
|
||||
references = [],
|
||||
model,
|
||||
aspectRatio,
|
||||
imageSize,
|
||||
frame,
|
||||
@@ -1234,7 +1231,7 @@ export function createQuickEditGenerationDialogDraft({
|
||||
composerOpen: false,
|
||||
sourceLayerId: sourceLayer.id,
|
||||
generationReferences: references.slice(0, 8),
|
||||
imageModel: normalizeEditorImageModel(model ?? sourceLayer.model),
|
||||
imageModel: IMAGE_MODEL_GPT_IMAGE_2,
|
||||
aspectRatio,
|
||||
imageSize,
|
||||
placeholder: {
|
||||
@@ -1259,6 +1256,7 @@ export function createRedrawPanelDraft(
|
||||
return {
|
||||
...createQuickEditPanelDraft(sourceLayer, options),
|
||||
mode: 'redraw',
|
||||
model: normalizeEditorImageModel(options.imageModel ?? sourceLayer.model),
|
||||
prompt: resolveUserGenerationPromptSnapshot(sourceLayer),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
getEditorImageModelDisplayName,
|
||||
IMAGE_MODEL_NANOBANANA2,
|
||||
normalizeEditorImageModel,
|
||||
QUICK_EDIT_MODEL_OPTIONS,
|
||||
resizeGenerationPlaceholderToImageSelection,
|
||||
resolveEditorImageSizeLabel,
|
||||
} from './ImageCanvasGenerationModel';
|
||||
@@ -135,6 +136,7 @@ export function ImageCanvasGenerationImageOptionsView({
|
||||
const [openPanel, setOpenPanel] = useState<OpenPanel>(null);
|
||||
const dimensionsButtonRef = useRef<HTMLButtonElement | null>(null);
|
||||
const modelButtonRef = useRef<HTMLButtonElement | null>(null);
|
||||
const isQuickEdit = dialog.mode === 'quick-edit';
|
||||
const normalizedLockedModel = lockedModel
|
||||
? normalizeEditorImageModel(lockedModel)
|
||||
: null;
|
||||
@@ -143,7 +145,17 @@ export function ImageCanvasGenerationImageOptionsView({
|
||||
? { ...dialog, imageModel: normalizedLockedModel }
|
||||
: dialog,
|
||||
);
|
||||
const selectedModelLabel = getEditorImageModelDisplayName(selection.model);
|
||||
const modelOptions = isQuickEdit
|
||||
? [...QUICK_EDIT_MODEL_OPTIONS]
|
||||
: [...EDITOR_IMAGE_MODEL_OPTIONS];
|
||||
const selectedModel = modelOptions.some(
|
||||
(option) => option.value === selection.model,
|
||||
)
|
||||
? selection.model
|
||||
: (modelOptions[0]?.value ?? selection.model);
|
||||
const selectedModelLabel =
|
||||
modelOptions.find((option) => option.value === selectedModel)?.label ??
|
||||
getEditorImageModelDisplayName(selectedModel);
|
||||
const selectedSizeLabel = resolveEditorImageSizeLabel({
|
||||
aspectRatio: selection.aspectRatio,
|
||||
imageSize: selection.imageSize,
|
||||
@@ -340,8 +352,8 @@ export function ImageCanvasGenerationImageOptionsView({
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
>
|
||||
<div className="image-canvas-editor__option-popover-items image-canvas-editor__option-popover-items--model">
|
||||
{EDITOR_IMAGE_MODEL_OPTIONS.map((option) => {
|
||||
const selected = selection.model === option.value;
|
||||
{modelOptions.map((option) => {
|
||||
const selected = selectedModel === option.value;
|
||||
return (
|
||||
<OptionChoice
|
||||
key={option.value}
|
||||
|
||||
@@ -348,6 +348,12 @@ describe('ImageCanvasGenerationModel', () => {
|
||||
).toEqual({
|
||||
fields: [{ title: '快速编辑提示词', value: '让图2下雨' }],
|
||||
references: [
|
||||
{
|
||||
title: '原图',
|
||||
label: '原图',
|
||||
refType: 'project-resource',
|
||||
refId: 'resource-source',
|
||||
},
|
||||
{
|
||||
title: '参考图 1',
|
||||
label: '天空',
|
||||
@@ -373,11 +379,9 @@ describe('ImageCanvasGenerationModel', () => {
|
||||
buildSpecPrompt('custom', { ...blankSpecValues, customPrompt: '自定义' }),
|
||||
).toBe('自定义');
|
||||
expect(buildQuickEditModelOptions('nano-banana')).toEqual([
|
||||
{ label: 'nanobanana2', value: DEFAULT_IMAGE_MODEL },
|
||||
{ label: 'gpt-image-2', value: 'gpt-image-2' },
|
||||
]);
|
||||
expect(buildQuickEditModelOptions('nanobanana2')).toEqual([
|
||||
{ label: 'nanobanana2', value: DEFAULT_IMAGE_MODEL },
|
||||
{ label: 'gpt-image-2', value: 'gpt-image-2' },
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -105,11 +105,13 @@ export const QUICK_EDIT_SIZE_PRESETS = [
|
||||
'2048x1152',
|
||||
'1024x1536',
|
||||
] as const;
|
||||
export const QUICK_EDIT_MODEL_OPTIONS = [
|
||||
export const EDITOR_IMAGE_MODEL_OPTIONS = [
|
||||
{ label: 'nanobanana2', value: IMAGE_MODEL_NANOBANANA2 },
|
||||
{ label: 'gpt-image-2', value: IMAGE_MODEL_GPT_IMAGE_2 },
|
||||
] as const;
|
||||
export const EDITOR_IMAGE_MODEL_OPTIONS = QUICK_EDIT_MODEL_OPTIONS;
|
||||
export const QUICK_EDIT_MODEL_OPTIONS = [
|
||||
{ label: 'gpt-image-2', value: IMAGE_MODEL_GPT_IMAGE_2 },
|
||||
] as const;
|
||||
export const EDITOR_IMAGE_DIMENSION_OPTIONS = {
|
||||
[IMAGE_MODEL_NANOBANANA2]: {
|
||||
aspectRatios: ['1:1', '4:3', '3:2', '2:3', '9:16', '16:9'],
|
||||
@@ -548,14 +550,9 @@ export function getEditorImageModelDisplayName(
|
||||
}
|
||||
|
||||
export function buildQuickEditModelOptions(currentModel: string) {
|
||||
const normalizedCurrentModel = normalizeEditorImageModel(currentModel);
|
||||
void currentModel;
|
||||
const options = [...QUICK_EDIT_MODEL_OPTIONS];
|
||||
return options.some((option) => option.value === normalizedCurrentModel)
|
||||
? options
|
||||
: [
|
||||
{ label: normalizedCurrentModel, value: normalizedCurrentModel },
|
||||
...options,
|
||||
];
|
||||
return options;
|
||||
}
|
||||
|
||||
export function buildCharacterSpecPrompt(values: SpecFormValues) {
|
||||
@@ -1233,7 +1230,7 @@ export function buildEditGenerationInputs(
|
||||
export function buildQuickEditGenerationInputs(
|
||||
title: '快速编辑提示词' | '重绘提示词',
|
||||
prompt: string,
|
||||
_sourceLayer: CanvasLayer,
|
||||
sourceLayer: CanvasLayer,
|
||||
references: CharacterReferenceImage[] | undefined,
|
||||
): CanvasGenerationInputs {
|
||||
const limitedReferences = (references ?? []).slice(
|
||||
@@ -1243,6 +1240,7 @@ export function buildQuickEditGenerationInputs(
|
||||
return {
|
||||
fields: createGenerationInputField(title, prompt),
|
||||
references: [
|
||||
...createLayerGenerationInputReference('原图', sourceLayer),
|
||||
...limitedReferences.flatMap((reference, index) =>
|
||||
createGenerationInputReference(`参考图 ${index + 1}`, reference),
|
||||
),
|
||||
|
||||
@@ -158,7 +158,14 @@ describe('ImageCanvasGenerationSubmissionModel', () => {
|
||||
assetKind: 'character',
|
||||
generationInputs: {
|
||||
fields: [{ title: '快速编辑提示词', value: '把当前图改成雨天' }],
|
||||
references: [],
|
||||
references: [
|
||||
{
|
||||
title: '原图',
|
||||
label: '源图',
|
||||
refType: 'project-resource',
|
||||
refId: 'resource-source',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
rememberImageModel: IMAGE_MODEL_GPT_IMAGE_2,
|
||||
|
||||
@@ -256,10 +256,9 @@ export function buildImageGenerationSubmissionPlan({
|
||||
if (!sourceLayer) {
|
||||
throw new Error('未找到要改造的图片');
|
||||
}
|
||||
const quickEditReferences = dialog.generationReferences ?? [];
|
||||
const basePrompt = dialog.prompt.trim() || '快速编辑图片';
|
||||
const normalizedQuickEditPrompt = basePrompt;
|
||||
const imageModel = normalizeEditorImageModel(dialog.imageModel);
|
||||
const imageModel = IMAGE_MODEL_GPT_IMAGE_2;
|
||||
const aspectRatio =
|
||||
dialog.aspectRatio ??
|
||||
inferEditorImageAspectRatio(
|
||||
@@ -292,7 +291,7 @@ export function buildImageGenerationSubmissionPlan({
|
||||
'快速编辑提示词',
|
||||
normalizedQuickEditPrompt,
|
||||
sourceLayer,
|
||||
quickEditReferences,
|
||||
[],
|
||||
),
|
||||
},
|
||||
rememberImageModel: imageModel,
|
||||
|
||||
@@ -81,7 +81,7 @@ function QuickEditPanelHarness({
|
||||
}
|
||||
|
||||
describe('ImageCanvasQuickEditPanelView', () => {
|
||||
it('updates prompt and clears failed state while keeping options editable', () => {
|
||||
it('updates prompt and clears failed state while keeping model editable', () => {
|
||||
const rememberImageModel = vi.fn();
|
||||
render(
|
||||
<QuickEditPanelHarness
|
||||
@@ -103,24 +103,22 @@ describe('ImageCanvasQuickEditPanelView', () => {
|
||||
target: { value: '新提示' },
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.getByPlaceholderText('写下每个编号要怎么改'),
|
||||
).toBeTruthy();
|
||||
expect(screen.getByLabelText('当前提示词').textContent).toBe('新提示');
|
||||
expect(screen.getByLabelText('当前尺寸').textContent).toBe('1024x1024');
|
||||
expect(screen.getByLabelText('当前模型').textContent).toBe(
|
||||
'gemini-3.1-flash-image-preview',
|
||||
);
|
||||
expect(screen.getByLabelText('当前模型').textContent).toBe('gpt-image-2');
|
||||
expect(screen.getByLabelText('当前状态').textContent).toBe('idle');
|
||||
expect(screen.getByLabelText('当前错误').textContent).toBe('-');
|
||||
fireEvent.click(
|
||||
screen.getByRole('button', { name: '快速编辑尺寸 1:1·1K' }),
|
||||
);
|
||||
fireEvent.click(screen.getByRole('button', { name: '比例 16:9' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '尺寸 2K' }));
|
||||
expect(screen.getByLabelText('当前比例').textContent).toBe('16:9');
|
||||
expect(screen.getByLabelText('当前图片规格').textContent).toBe('2K');
|
||||
expect(
|
||||
screen.queryByRole('button', { name: '快速编辑尺寸 1:1·1K' }),
|
||||
).toBeNull();
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByRole('button', { name: '快速编辑模型 nanobanana2' }),
|
||||
screen.getByRole('button', { name: '快速编辑模型 gpt-image-2' }),
|
||||
);
|
||||
expect(screen.queryByRole('button', { name: 'nanobanana2' })).toBeNull();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'gpt-image-2' }));
|
||||
expect(screen.getByLabelText('当前模型').textContent).toBe('gpt-image-2');
|
||||
expect(rememberImageModel).toHaveBeenCalledWith('gpt-image-2');
|
||||
@@ -143,7 +141,7 @@ describe('ImageCanvasQuickEditPanelView', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '生成' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '修改' }));
|
||||
|
||||
expect(submitQuickEdit).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
@@ -172,7 +170,7 @@ describe('ImageCanvasQuickEditPanelView', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('renders only extra references and keeps the implicit source image out of the strip', () => {
|
||||
it('hides quick edit references from the simplified panel', () => {
|
||||
render(
|
||||
<QuickEditPanelHarness
|
||||
initialPanel={{
|
||||
@@ -191,18 +189,15 @@ describe('ImageCanvasQuickEditPanelView', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByLabelText('图1')).toBeTruthy();
|
||||
expect(screen.queryByLabelText('图1')).toBeNull();
|
||||
expect(screen.queryByLabelText('原图 图2')).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '删除图1' }));
|
||||
|
||||
expect(screen.getByLabelText('参考图数量').textContent).toBe('0');
|
||||
expect(screen.getByLabelText('当前状态').textContent).toBe('idle');
|
||||
expect(screen.getByLabelText('当前错误').textContent).toBe('-');
|
||||
expect(screen.queryByLabelText('原图 图1')).toBeNull();
|
||||
expect(
|
||||
screen.queryByRole('button', { name: '添加快速编辑参考图' }),
|
||||
).toBeNull();
|
||||
expect(screen.getByLabelText('参考图数量').textContent).toBe('1');
|
||||
});
|
||||
|
||||
it('opens reference source menu and dispatches canvas picking or upload actions', () => {
|
||||
it('does not expose quick edit reference pick actions', () => {
|
||||
const requestUpload = vi.fn();
|
||||
render(
|
||||
<QuickEditPanelHarness
|
||||
@@ -220,17 +215,14 @@ describe('ImageCanvasQuickEditPanelView', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '添加快速编辑参考图' }));
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: '从画布中选择' }));
|
||||
expect(screen.getByLabelText('画布选择参考图').textContent).toBe('picking');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '添加快速编辑参考图' }));
|
||||
fireEvent.click(screen.getByRole('menuitem', { name: '上传图片' }));
|
||||
expect(requestUpload).toHaveBeenCalledWith('quick-edit-reference');
|
||||
expect(
|
||||
screen.queryByRole('button', { name: '添加快速编辑参考图' }),
|
||||
).toBeNull();
|
||||
expect(requestUpload).not.toHaveBeenCalled();
|
||||
expect(screen.getByLabelText('画布选择参考图').textContent).toBe('idle');
|
||||
});
|
||||
|
||||
it('disables adding quick edit references after eight extra images', () => {
|
||||
it('keeps quick edit reference controls hidden even with many references', () => {
|
||||
render(
|
||||
<QuickEditPanelHarness
|
||||
initialPanel={{
|
||||
@@ -251,12 +243,10 @@ describe('ImageCanvasQuickEditPanelView', () => {
|
||||
);
|
||||
|
||||
expect(
|
||||
(
|
||||
screen.getByRole('button', {
|
||||
name: '添加快速编辑参考图',
|
||||
}) as HTMLButtonElement
|
||||
).disabled,
|
||||
).toBe(true);
|
||||
screen.queryByRole('button', {
|
||||
name: '添加快速编辑参考图',
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(screen.queryByLabelText('原图 图9')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,7 +8,10 @@ import {
|
||||
} from 'react';
|
||||
|
||||
import { ImageCanvasBasicGenerationComposerView } from './ImageCanvasBasicGenerationComposerView';
|
||||
import { QUICK_EDIT_REFERENCE_LIMIT } from './ImageCanvasGenerationModel';
|
||||
import {
|
||||
IMAGE_MODEL_GPT_IMAGE_2,
|
||||
QUICK_EDIT_REFERENCE_LIMIT,
|
||||
} from './ImageCanvasGenerationModel';
|
||||
import type {
|
||||
CanvasLayer,
|
||||
GenerateDialogState,
|
||||
@@ -48,13 +51,14 @@ function resetFailedPanelStatus<
|
||||
function createQuickEditDialog(
|
||||
panel: QuickEditPanelState,
|
||||
): GenerateDialogState {
|
||||
const isQuickEdit = (panel.mode ?? 'quick-edit') === 'quick-edit';
|
||||
return {
|
||||
mode: panel.mode === 'redraw' ? 'generate' : 'quick-edit',
|
||||
mode: isQuickEdit ? 'quick-edit' : 'generate',
|
||||
prompt: panel.prompt,
|
||||
status: panel.status,
|
||||
sourceLayerId: panel.sourceLayerId,
|
||||
generationReferences: panel.quickEditReferences,
|
||||
imageModel: panel.model,
|
||||
imageModel: isQuickEdit ? IMAGE_MODEL_GPT_IMAGE_2 : panel.model,
|
||||
aspectRatio: panel.aspectRatio,
|
||||
imageSize: panel.imageSize,
|
||||
};
|
||||
@@ -66,6 +70,7 @@ function syncPanelFromDialogUpdate(
|
||||
) {
|
||||
const normalizedPanel = resetFailedPanelStatus(panel);
|
||||
const normalizedDialog = resetFailedPanelStatus(dialog);
|
||||
const isQuickEdit = (normalizedPanel.mode ?? 'quick-edit') === 'quick-edit';
|
||||
return {
|
||||
...normalizedPanel,
|
||||
size: panel.size,
|
||||
@@ -73,7 +78,9 @@ function syncPanelFromDialogUpdate(
|
||||
aspectRatio:
|
||||
normalizedDialog.aspectRatio ?? normalizedPanel.aspectRatio ?? '1:1',
|
||||
imageSize: normalizedDialog.imageSize ?? normalizedPanel.imageSize ?? '1K',
|
||||
model: normalizedDialog.imageModel ?? normalizedPanel.model,
|
||||
model: isQuickEdit
|
||||
? IMAGE_MODEL_GPT_IMAGE_2
|
||||
: (normalizedDialog.imageModel ?? normalizedPanel.model),
|
||||
quickEditReferences: normalizedDialog.generationReferences,
|
||||
status: normalizedDialog.status,
|
||||
errorMessage: normalizedDialog.errorMessage,
|
||||
@@ -137,12 +144,12 @@ export function ImageCanvasQuickEditPanelView({
|
||||
onRememberImageModel={onRememberImageModel}
|
||||
onSubmit={onSubmit}
|
||||
dialogLabel={isRedraw ? '重绘图片' : '快速编辑图片'}
|
||||
includeDimensions={true}
|
||||
includeDimensions={isRedraw}
|
||||
includeModel={true}
|
||||
includeReferences={!isRedraw}
|
||||
includeReferences={false}
|
||||
promptLabel={isRedraw ? '重绘提示词' : '快速编辑提示词'}
|
||||
promptPlaceholder={
|
||||
isRedraw ? '把画面重绘成什么样?' : '想怎么编辑这张图?'
|
||||
isRedraw ? '把画面重绘成什么样?' : '写下每个编号要怎么改'
|
||||
}
|
||||
optionLabelPrefix={isRedraw ? '重绘' : '快速编辑'}
|
||||
dimensionRatioAriaLabelPrefix="比例"
|
||||
@@ -153,7 +160,7 @@ export function ImageCanvasQuickEditPanelView({
|
||||
referenceMenuLabel={isRedraw ? '' : '快速编辑参考图来源'}
|
||||
referenceSlotTone="quick-edit"
|
||||
referenceSlotDisabled={!canAddReferences}
|
||||
showReferenceAddButton={!isRedraw}
|
||||
showReferenceAddButton={false}
|
||||
referenceLabelFormatter={(_, index) => `图${index + 1}`}
|
||||
referenceAriaLabelFormatter={(_, index) => `图${index + 1}`}
|
||||
referenceRemoveLabelFormatter={(_, index) => `删除图${index + 1}`}
|
||||
@@ -162,9 +169,9 @@ export function ImageCanvasQuickEditPanelView({
|
||||
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="生成中"
|
||||
submitLabel={isRedraw ? '生成' : '修改'}
|
||||
submitAriaLabel={isRedraw ? '生成' : '修改'}
|
||||
submittingStatusLabel={isRedraw ? '生成中' : '修改中'}
|
||||
renderEditorPortal={renderEditorPortal}
|
||||
buildPortalMenuStyle={buildPortalMenuStyle}
|
||||
/>
|
||||
|
||||
@@ -495,20 +495,23 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: '填写快速编辑' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '提交快速编辑' }));
|
||||
|
||||
expect(screen.getByTestId('quick-edit').textContent).toBe('-');
|
||||
expect(screen.getByTestId('dialog').textContent).toBe(
|
||||
'quick-edit:generating:closed:-:placeholder:-',
|
||||
expect(screen.getByTestId('quick-edit').textContent).toBe(
|
||||
'layer-source:generating:快速修图:-',
|
||||
);
|
||||
expect(screen.getByTestId('dialog').textContent).toBe('-');
|
||||
await waitFor(() => {
|
||||
expect(editEditorImageMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
prompt: '快速修图',
|
||||
sourceImageSrc: 'data:image/png;base64,source.png',
|
||||
size: '1024x768',
|
||||
model: 'gemini-3.1-flash-image-preview',
|
||||
model: 'gpt-image-2',
|
||||
}),
|
||||
);
|
||||
});
|
||||
expect(editEditorImageMock.mock.calls[0]?.[0]).not.toHaveProperty(
|
||||
'canvasCompletion',
|
||||
);
|
||||
expect(generateEditorImageMock).not.toHaveBeenCalled();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('layers').textContent).toContain(
|
||||
@@ -522,6 +525,49 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
|
||||
expect(screen.getByTestId('fit-count').textContent).toBe('0');
|
||||
});
|
||||
|
||||
it('clears quick edit panel after a queued edit reloads the project snapshot', async () => {
|
||||
const applyProjectSnapshot = vi.fn();
|
||||
const refreshTaskList = vi.fn();
|
||||
const backendProject = {
|
||||
projectId: 'editor-project-1',
|
||||
title: '队列修图项目',
|
||||
viewport: { x: 0, y: 0, scale: 1 },
|
||||
layers: [
|
||||
{
|
||||
...createLayer(),
|
||||
src: 'data:image/png;base64,queued-generated',
|
||||
},
|
||||
],
|
||||
resources: [],
|
||||
updatedAt: '2026-06-23T08:00:00.000Z',
|
||||
};
|
||||
loadEditorProjectMock.mockResolvedValueOnce(backendProject);
|
||||
editEditorImageMock.mockResolvedValueOnce({
|
||||
...createGenerated({ prompt: '快速修图' }),
|
||||
queueState: createQueueState(),
|
||||
});
|
||||
render(
|
||||
<SubmissionWorkflowHarness
|
||||
projectId="editor-project-1"
|
||||
applyProjectSnapshot={applyProjectSnapshot}
|
||||
onQueuedGenerationTask={refreshTaskList}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开快速编辑' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '填写快速编辑' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '提交快速编辑' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(loadEditorProjectMock).toHaveBeenCalledWith('editor-project-1');
|
||||
});
|
||||
expect(refreshTaskList).toHaveBeenCalledTimes(1);
|
||||
expect(applyProjectSnapshot).toHaveBeenCalledWith(backendProject);
|
||||
expect(screen.getByTestId('quick-edit').textContent).toBe('-');
|
||||
expect(screen.getByTestId('tool').textContent).toBe('select');
|
||||
expect(screen.getByTestId('sidebar').textContent).toBe('layers');
|
||||
});
|
||||
|
||||
it('submits quick-edit canvas generators with the source image reference', async () => {
|
||||
editEditorImageMock.mockResolvedValueOnce(
|
||||
createGenerated({ prompt: '把当前图改成雨天' }),
|
||||
@@ -561,6 +607,7 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
|
||||
size: '1024x768',
|
||||
model: 'gpt-image-2',
|
||||
sourceResourceId: 'resource-source',
|
||||
targetLayerId: 'layer-source',
|
||||
assetLabel: '源图 快速编辑',
|
||||
}),
|
||||
);
|
||||
@@ -633,7 +680,7 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('submits quick edits with the numbered red selection image as the final source reference', async () => {
|
||||
it('submits quick edits with the numbered red selection image as the source image', async () => {
|
||||
editEditorImageMock.mockResolvedValueOnce(
|
||||
createGenerated({
|
||||
prompt: '对1号红色圈选框里的内容做以下修改:改成蓝色',
|
||||
@@ -704,13 +751,99 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
|
||||
expect(editEditorImageMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sourceImageSrc: 'data:image/png;base64,marked-source',
|
||||
referenceImageSrcs: ['data:image/png;base64,style.png'],
|
||||
targetLayerId: 'layer-source',
|
||||
}),
|
||||
);
|
||||
});
|
||||
expect(editEditorImageMock.mock.calls[0]?.[0]).not.toHaveProperty(
|
||||
'referenceImageSrcs',
|
||||
);
|
||||
expect(generateEditorImageMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('submits canvas quick edits with numbered selection marks', async () => {
|
||||
editEditorImageMock.mockResolvedValueOnce(
|
||||
createGenerated({
|
||||
prompt: '对1号红色圈选框里的内容做以下修改:改成蓝色',
|
||||
}),
|
||||
);
|
||||
render(
|
||||
<SubmissionWorkflowHarness
|
||||
initialDialog={{
|
||||
id: 'generation-dialog-quick-edit-marked',
|
||||
mode: 'quick-edit',
|
||||
prompt: '对1号红色圈选框里的内容做以下修改:改成蓝色',
|
||||
status: 'idle',
|
||||
composerOpen: true,
|
||||
sourceLayerId: 'layer-source',
|
||||
imageModel: 'gpt-image-2',
|
||||
aspectRatio: '4:3',
|
||||
imageSize: '1K',
|
||||
placeholder: {
|
||||
x: 472,
|
||||
y: 140,
|
||||
width: 1024,
|
||||
height: 768,
|
||||
originalWidth: 1024,
|
||||
originalHeight: 768,
|
||||
},
|
||||
}}
|
||||
initialQuickEditSelectionState={{
|
||||
sourceLayerId: 'layer-source',
|
||||
tool: 'rect',
|
||||
model: 'gemini-3.1-flash-image-preview',
|
||||
marks: [
|
||||
{
|
||||
id: 'quick-edit-selection-mark-1',
|
||||
tool: 'rect',
|
||||
x: 24,
|
||||
y: 36,
|
||||
width: 160,
|
||||
height: 120,
|
||||
},
|
||||
],
|
||||
references: [],
|
||||
draftMark: null,
|
||||
status: 'idle',
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
renderUiDesignAssetExtractionMarkedImageMock.mockResolvedValueOnce(
|
||||
'data:image/png;base64,marked-source',
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '设置初始对话' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '提交当前生成' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(renderUiDesignAssetExtractionMarkedImageMock).toHaveBeenCalledWith(
|
||||
{
|
||||
source: 'data:image/png;base64,source.png',
|
||||
marks: [
|
||||
{
|
||||
id: 'quick-edit-selection-mark-1',
|
||||
tool: 'rect',
|
||||
x: 24,
|
||||
y: 36,
|
||||
width: 160,
|
||||
height: 120,
|
||||
},
|
||||
],
|
||||
showIndexLabels: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(editEditorImageMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sourceImageSrc: 'data:image/png;base64,marked-source',
|
||||
model: 'gpt-image-2',
|
||||
targetLayerId: 'layer-source',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps failed quick edit panels visible with the normalized prompt', async () => {
|
||||
const refreshWalletBalance = vi.fn();
|
||||
editEditorImageMock.mockRejectedValueOnce(new Error('修图失败'));
|
||||
@@ -864,7 +997,6 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
|
||||
resolution: '480p',
|
||||
mode: 'std',
|
||||
sound: 'off',
|
||||
referenceImageSrcs: ['data:image/png;base64,style.png'],
|
||||
referenceVideoSrcs: [
|
||||
'generated-character-drafts/editor/videos/source.mp4',
|
||||
],
|
||||
@@ -873,6 +1005,9 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
expect(generateEditorVideoMock.mock.calls[0]?.[0]).not.toHaveProperty(
|
||||
'referenceImageSrcs',
|
||||
);
|
||||
expect(generateEditorImageMock).not.toHaveBeenCalled();
|
||||
expect(generateEditorCharacterAnimationMock).not.toHaveBeenCalled();
|
||||
await waitFor(() => {
|
||||
@@ -888,24 +1023,13 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('submits character action quick edits through seedance fast character animation generation', async () => {
|
||||
generateEditorCharacterAnimationMock.mockResolvedValueOnce({
|
||||
taskId: 'action-quick-edit-task',
|
||||
model: 'seedance2.0-fast',
|
||||
prompt: '让图1跳得更高',
|
||||
previewVideoPath: '/generated-character-drafts/editor/action-preview.mp4',
|
||||
frames: [
|
||||
{
|
||||
frameIndex: 1,
|
||||
imageSrc: 'data:image/png;base64,action-frame-edited',
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
},
|
||||
],
|
||||
frameCount: 32,
|
||||
durationSeconds: 4,
|
||||
fps: 8,
|
||||
});
|
||||
it('submits character action quick edits through image edits', async () => {
|
||||
editEditorImageMock.mockResolvedValueOnce(
|
||||
createGenerated({
|
||||
prompt: '让当前图跳得更高',
|
||||
imageSrc: 'data:image/png;base64,action-frame-edited',
|
||||
}),
|
||||
);
|
||||
render(
|
||||
<SubmissionWorkflowHarness
|
||||
initialLayers={[
|
||||
@@ -945,24 +1069,22 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: '提交快速编辑' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(generateEditorCharacterAnimationMock).toHaveBeenCalledWith(
|
||||
expect(editEditorImageMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sourceLayerId: 'layer-source',
|
||||
sourceImageSrc: 'generated-character-drafts/editor/actions/frame.png',
|
||||
promptText: '让当前图跳得更高',
|
||||
resolution: '480p',
|
||||
ratio: 'same',
|
||||
frameCount: 32,
|
||||
durationSeconds: 4,
|
||||
model: 'seedance2.0-fast',
|
||||
prompt: '让当前图跳得更高',
|
||||
sourceImageSrc: 'data:image/png;base64,frame.png',
|
||||
model: 'gpt-image-2',
|
||||
assetLabel: '角色动作 快速编辑',
|
||||
targetLayerId: 'layer-source',
|
||||
}),
|
||||
);
|
||||
});
|
||||
expect(generateEditorCharacterAnimationMock).not.toHaveBeenCalled();
|
||||
expect(generateEditorImageMock).not.toHaveBeenCalled();
|
||||
expect(generateEditorVideoMock).not.toHaveBeenCalled();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('layers').textContent).toContain(
|
||||
'layer-character-animation-1:角色动作 快速编辑:resource-source:character-animation:image-sequence:data:image/png;base64,action-frame-edited:data:image/png;base64,action-frame-edited:4',
|
||||
'layer-source:角色动作:resource-source:character-animation:image-sequence:data:image/png;base64,action-frame-edited',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,6 +62,7 @@ import {
|
||||
DEFAULT_VIDEO_MODEL,
|
||||
ICON_FRAME_DISPLAY_SIZE,
|
||||
ICON_FRAME_ORIGINAL_SIZE,
|
||||
IMAGE_MODEL_GPT_IMAGE_2,
|
||||
inferEditorImageAspectRatio,
|
||||
inferEditorImageSizeLabel,
|
||||
isCanvasGenerationDialog,
|
||||
@@ -281,10 +282,6 @@ function isVideoQuickEditSource(layer: CanvasLayer) {
|
||||
return layer.mediaType === 'video' || layer.assetKind === 'video';
|
||||
}
|
||||
|
||||
function isCharacterAnimationQuickEditSource(layer: CanvasLayer) {
|
||||
return layer.assetKind === 'character-animation';
|
||||
}
|
||||
|
||||
export function useImageCanvasGenerationSubmissionWorkflow({
|
||||
layers,
|
||||
canvasSize,
|
||||
@@ -953,7 +950,9 @@ export function useImageCanvasGenerationSubmissionWorkflow({
|
||||
|
||||
const panelMode = quickEditPanel.mode ?? 'quick-edit';
|
||||
const quickEditReferences =
|
||||
panelMode === 'redraw' ? [] : (quickEditPanel.quickEditReferences ?? []);
|
||||
panelMode === 'redraw' || panelMode === 'quick-edit'
|
||||
? []
|
||||
: (quickEditPanel.quickEditReferences ?? []);
|
||||
const quickEditAspectRatio =
|
||||
quickEditPanel.aspectRatio ??
|
||||
inferEditorImageAspectRatio(
|
||||
@@ -977,7 +976,9 @@ export function useImageCanvasGenerationSubmissionWorkflow({
|
||||
(panelMode === 'redraw' ? '重绘图片' : '快速编辑图片');
|
||||
const normalizedPrompt = panelMode === 'redraw' ? basePrompt : basePrompt;
|
||||
let quickEditDialogId: string | undefined;
|
||||
if (panelMode === 'quick-edit') {
|
||||
const shouldUseCanvasPlaceholder =
|
||||
panelMode === 'quick-edit' && isVideoQuickEditSource(quickEditSourceLayer);
|
||||
if (shouldUseCanvasPlaceholder) {
|
||||
const quickEditDraft = createQuickEditGenerationDialogDraft({
|
||||
sourceLayer: quickEditSourceLayer,
|
||||
prompt: normalizedPrompt,
|
||||
@@ -1002,7 +1003,7 @@ export function useImageCanvasGenerationSubmissionWorkflow({
|
||||
: quickEditDraft.placeholder;
|
||||
quickEditDialogId = openCanvasGenerationDialog({
|
||||
...quickEditDraft,
|
||||
// 中文注释:快速编辑生成结果复用新建图片的避让落点,避免覆盖已有素材。
|
||||
// 中文注释:视频快速编辑仍落到独立视频结果占位,图片快速编辑直接替换源图。
|
||||
placeholder: quickEditPlacement,
|
||||
});
|
||||
setQuickEditPanel(null);
|
||||
@@ -1049,72 +1050,6 @@ export function useImageCanvasGenerationSubmissionWorkflow({
|
||||
originalWidth: quickEditOutputSize.width,
|
||||
originalHeight: quickEditOutputSize.height,
|
||||
});
|
||||
if (
|
||||
panelMode === 'quick-edit' &&
|
||||
isCharacterAnimationQuickEditSource(quickEditSourceLayer)
|
||||
) {
|
||||
const animationResolution = '480p' as const;
|
||||
const animationDurationSeconds = 4 as const;
|
||||
const animationFrameCount = 32 as const;
|
||||
const generationInputs = buildCharacterAnimationGenerationInputs(
|
||||
normalizedPrompt,
|
||||
quickEditSourceLayer,
|
||||
);
|
||||
const generated = await runEditorGenerationWithWalletRefresh(
|
||||
generateEditorCharacterAnimation({
|
||||
sourceLayerId: quickEditSourceLayer.id,
|
||||
sourceImageSrc:
|
||||
resolveCharacterAnimationSourceImageSrc(quickEditSourceLayer),
|
||||
sourceWidth: quickEditSourceLayer.originalWidth,
|
||||
sourceHeight: quickEditSourceLayer.originalHeight,
|
||||
promptText: normalizedPrompt,
|
||||
resolution: animationResolution,
|
||||
ratio: 'same',
|
||||
frameCount: animationFrameCount,
|
||||
durationSeconds: animationDurationSeconds,
|
||||
model: CHARACTER_ANIMATION_MODEL,
|
||||
projectId,
|
||||
generationInputs,
|
||||
sourceResourceId: quickEditSourceLayer.resourceId,
|
||||
...(projectId && quickEditCanvasCompletionPlaceholder
|
||||
? {
|
||||
canvasCompletion: {
|
||||
dialogId: quickEditDialogId,
|
||||
title: `${quickEditSourceLayer.title} 快速编辑`,
|
||||
placeholder: quickEditCanvasCompletionPlaceholder,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
}),
|
||||
onWalletBalanceMayHaveChanged,
|
||||
);
|
||||
if (
|
||||
await applyQueuedEditorGenerationProject(
|
||||
generated,
|
||||
projectId,
|
||||
applyProjectSnapshot,
|
||||
onQueuedGenerationTask,
|
||||
onWalletBalanceMayHaveChanged,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (generated.project && applyProjectSnapshot) {
|
||||
applyProjectSnapshot(generated.project);
|
||||
return;
|
||||
}
|
||||
addCharacterAnimationResultLayer(
|
||||
generated,
|
||||
`${quickEditSourceLayer.title} 快速编辑`,
|
||||
generationInputs,
|
||||
quickEditFrame,
|
||||
quickEditDialogId,
|
||||
{
|
||||
sourceLayer: quickEditSourceLayer,
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
panelMode === 'quick-edit' &&
|
||||
isVideoQuickEditSource(quickEditSourceLayer)
|
||||
@@ -1229,7 +1164,7 @@ export function useImageCanvasGenerationSubmissionWorkflow({
|
||||
prompt: normalizedPrompt,
|
||||
sourceImageSrc: sourceReferenceImageSrc,
|
||||
size: quickEditSize,
|
||||
model: normalizeEditorImageModel(quickEditPanel.model),
|
||||
model: IMAGE_MODEL_GPT_IMAGE_2,
|
||||
...(extraReferenceImageSrcs.length
|
||||
? { referenceImageSrcs: extraReferenceImageSrcs }
|
||||
: {}),
|
||||
@@ -1239,6 +1174,7 @@ export function useImageCanvasGenerationSubmissionWorkflow({
|
||||
assetFolderId,
|
||||
assetLabel: `${quickEditSourceLayer.title} 快速编辑`,
|
||||
sourceResourceId: quickEditSourceLayer.resourceId,
|
||||
targetLayerId: quickEditSourceLayer.id,
|
||||
}),
|
||||
onWalletBalanceMayHaveChanged,
|
||||
)
|
||||
@@ -1281,10 +1217,20 @@ export function useImageCanvasGenerationSubmissionWorkflow({
|
||||
onWalletBalanceMayHaveChanged,
|
||||
)
|
||||
) {
|
||||
if (panelMode === 'quick-edit') {
|
||||
setQuickEditPanel(null);
|
||||
setActiveTool('select');
|
||||
setActiveSidebarPanel('layers');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (generated.project && applyProjectSnapshot) {
|
||||
applyProjectSnapshot(generated.project);
|
||||
if (panelMode === 'quick-edit') {
|
||||
setQuickEditPanel(null);
|
||||
setActiveTool('select');
|
||||
setActiveSidebarPanel('layers');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (panelMode === 'quick-edit') {
|
||||
@@ -1448,10 +1394,22 @@ export function useImageCanvasGenerationSubmissionWorkflow({
|
||||
generationInputs: submissionPlan.generationInputs,
|
||||
});
|
||||
} else if (submissionPlan.kind === 'quick-edit') {
|
||||
const referenceImageSrc = await resolveEditorImageReferenceDataUrl(
|
||||
let referenceImageSrc = await resolveEditorImageReferenceDataUrl(
|
||||
submissionPlan.sourceLayer.objectKey?.trim() ||
|
||||
submissionPlan.sourceLayer.src,
|
||||
);
|
||||
const quickEditSelectionMarks =
|
||||
quickEditSelectionState?.sourceLayerId ===
|
||||
submissionPlan.sourceLayer.id
|
||||
? quickEditSelectionState.marks
|
||||
: [];
|
||||
if (quickEditSelectionMarks.length > 0) {
|
||||
referenceImageSrc = await renderUiDesignAssetExtractionMarkedImage({
|
||||
source: referenceImageSrc,
|
||||
marks: quickEditSelectionMarks,
|
||||
showIndexLabels: true,
|
||||
});
|
||||
}
|
||||
const generated = await runEditorGenerationWithWalletRefresh(
|
||||
editEditorImage({
|
||||
prompt: submissionPlan.normalizedPrompt,
|
||||
@@ -1463,6 +1421,7 @@ export function useImageCanvasGenerationSubmissionWorkflow({
|
||||
assetFolderId,
|
||||
assetLabel: submissionPlan.result.title,
|
||||
sourceResourceId: submissionPlan.sourceLayer.resourceId,
|
||||
targetLayerId: submissionPlan.sourceLayer.id,
|
||||
}),
|
||||
onWalletBalanceMayHaveChanged,
|
||||
);
|
||||
@@ -1701,6 +1660,7 @@ export function useImageCanvasGenerationSubmissionWorkflow({
|
||||
getGeneratingDialogPlaceholder,
|
||||
layerCounterRef,
|
||||
layers,
|
||||
quickEditSelectionState,
|
||||
rememberImageModel,
|
||||
projectId,
|
||||
assetFolderId,
|
||||
|
||||
@@ -231,6 +231,11 @@ function GenerationWorkflowHarness({
|
||||
? `${workflow.quickEditPanel.sourceLayerId}:${workflow.quickEditPanel.status}:${workflow.quickEditPanel.prompt || '-'}`
|
||||
: '-'}
|
||||
</span>
|
||||
<span data-testid="quick-edit-image-options">
|
||||
{workflow.quickEditPanel
|
||||
? `${workflow.quickEditPanel.mode ?? 'quick-edit'}:${workflow.quickEditPanel.model}:${workflow.quickEditPanel.aspectRatio ?? '-'}:${workflow.quickEditPanel.imageSize ?? '-'}`
|
||||
: '-'}
|
||||
</span>
|
||||
<span data-testid="crop-expand">
|
||||
{workflow.cropExpandPanel
|
||||
? `${workflow.cropExpandPanel.sourceLayerId}:${workflow.cropExpandPanel.ratio}:${workflow.cropExpandPanel.frame.x}:${workflow.cropExpandPanel.frame.y}:${workflow.cropExpandPanel.frame.width}:${workflow.cropExpandPanel.frame.height}:${workflow.cropExpandPanel.status}`
|
||||
@@ -833,18 +838,22 @@ describe('useImageCanvasGenerationWorkflow', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('opens the source generator for generated character quick edit', () => {
|
||||
it('opens quick edit for generated character images', () => {
|
||||
render(<GenerationWorkflowHarness />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '快速编辑角色图' }));
|
||||
|
||||
expect(screen.getByTestId('tool').textContent).toBe('character');
|
||||
expect(screen.getByTestId('selected').textContent).toBe('-');
|
||||
expect(screen.getByTestId('dialog').textContent).toBe(
|
||||
'character:idle:open:-:placeholder',
|
||||
expect(screen.getByTestId('tool').textContent).toBe('generate');
|
||||
expect(screen.getByTestId('selected').textContent).toBe(
|
||||
'layer-character-generated',
|
||||
);
|
||||
expect(screen.getByTestId('dialog').textContent).toBe('-');
|
||||
expect(screen.getByTestId('quick-edit').textContent).toBe(
|
||||
'layer-character-generated:idle:-',
|
||||
);
|
||||
expect(screen.getByTestId('quick-edit-selection').textContent).toBe(
|
||||
'layer-character-generated:none:0',
|
||||
);
|
||||
expect(screen.getByTestId('dialog-prompt').textContent).toBe('-');
|
||||
expect(screen.getByTestId('quick-edit-selection').textContent).toBe('-');
|
||||
});
|
||||
|
||||
it('opens the source generator for generated publication redraw', () => {
|
||||
@@ -864,7 +873,7 @@ describe('useImageCanvasGenerationWorkflow', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('opens the source generator for plain generated images with a source dialog', () => {
|
||||
it('opens quick edit for plain generated images without inheriting hidden source dimensions', () => {
|
||||
render(<GenerationWorkflowHarness />);
|
||||
|
||||
fireEvent.click(
|
||||
@@ -873,23 +882,27 @@ describe('useImageCanvasGenerationWorkflow', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: '快速编辑来源普通图' }));
|
||||
|
||||
expect(screen.getByTestId('tool').textContent).toBe('generate');
|
||||
expect(screen.getByTestId('dialog').textContent).toBe(
|
||||
'generate:idle:open:-:placeholder',
|
||||
expect(screen.getByTestId('dialog').textContent).toBe('-');
|
||||
expect(screen.getByTestId('quick-edit').textContent).toBe(
|
||||
'layer-source:idle:-',
|
||||
);
|
||||
expect(screen.getByTestId('dialog-image-options').textContent).toBe(
|
||||
'generate:gpt-image-2:2:3:2K',
|
||||
expect(screen.getByTestId('quick-edit-image-options').textContent).toBe(
|
||||
'quick-edit:gpt-image-2:4:3:1K',
|
||||
);
|
||||
expect(screen.getByTestId('quick-edit-selection').textContent).toBe(
|
||||
'layer-source:none:0',
|
||||
);
|
||||
expect(screen.getByTestId('quick-edit-selection').textContent).toBe('-');
|
||||
});
|
||||
|
||||
it('keeps plain images without a source generator on quick edit generator', () => {
|
||||
it('keeps plain images without a source generator on the quick edit panel', () => {
|
||||
render(<GenerationWorkflowHarness />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开快速编辑' }));
|
||||
|
||||
expect(screen.getByTestId('tool').textContent).toBe('generate');
|
||||
expect(screen.getByTestId('dialog').textContent).toBe(
|
||||
'quick-edit:idle:open:-:placeholder',
|
||||
expect(screen.getByTestId('dialog').textContent).toBe('-');
|
||||
expect(screen.getByTestId('quick-edit').textContent).toBe(
|
||||
'layer-source:idle:-',
|
||||
);
|
||||
expect(screen.getByTestId('quick-edit-selection').textContent).toBe(
|
||||
'layer-source:none:0',
|
||||
@@ -994,12 +1007,12 @@ describe('useImageCanvasGenerationWorkflow', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开生成' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开快速编辑' }));
|
||||
|
||||
expect(screen.getByTestId('dialog').textContent).toBe(
|
||||
'quick-edit:idle:open:-:placeholder',
|
||||
expect(screen.getByTestId('dialog').textContent).toBe('-');
|
||||
expect(screen.getByTestId('quick-edit').textContent).toBe(
|
||||
'layer-source:idle:-',
|
||||
);
|
||||
expect(screen.getByTestId('quick-edit').textContent).toBe('-');
|
||||
expect(screen.getByTestId('generation-dialogs').textContent).toBe(
|
||||
'generation-dialog-1:generate:closed|generation-dialog-2:quick-edit:open',
|
||||
'generation-dialog-1:generate:closed',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1019,7 +1032,7 @@ describe('useImageCanvasGenerationWorkflow', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps source image model and dimensions when opening quick edit', () => {
|
||||
it('keeps source image model and infers dimensions from the current image when opening quick edit', () => {
|
||||
render(
|
||||
<GenerationWorkflowHarness
|
||||
initialLayers={[
|
||||
@@ -1037,11 +1050,9 @@ describe('useImageCanvasGenerationWorkflow', () => {
|
||||
);
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开快速编辑' }));
|
||||
|
||||
expect(screen.getByTestId('dialog').textContent).toBe(
|
||||
'generate:idle:open:-:placeholder',
|
||||
);
|
||||
expect(screen.getByTestId('dialog-image-options').textContent).toBe(
|
||||
'generate:gpt-image-2:2:3:2K',
|
||||
expect(screen.getByTestId('dialog').textContent).toBe('-');
|
||||
expect(screen.getByTestId('quick-edit-image-options').textContent).toBe(
|
||||
'quick-edit:gpt-image-2:3:2:2K',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1483,7 +1494,9 @@ describe('useImageCanvasGenerationWorkflow', () => {
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开快速编辑' }));
|
||||
|
||||
expect(screen.getByTestId('quick-edit').textContent).toBe('-');
|
||||
expect(screen.getByTestId('quick-edit').textContent).toBe(
|
||||
'layer-source:idle:-',
|
||||
);
|
||||
expect(screen.getByTestId('quick-edit-selection').textContent).toBe(
|
||||
'layer-source:none:0',
|
||||
);
|
||||
@@ -1526,7 +1539,9 @@ describe('useImageCanvasGenerationWorkflow', () => {
|
||||
expect(screen.getByTestId('quick-edit-selection').textContent).toBe(
|
||||
'layer-source:rect:1',
|
||||
);
|
||||
expect(screen.getByTestId('quick-edit').textContent).toBe('-');
|
||||
expect(screen.getByTestId('quick-edit').textContent).toBe(
|
||||
'layer-source:idle:对1号红色圈选框里的内容做以下修改:',
|
||||
);
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByRole('button', { name: '选择快速编辑椭圆框选' }),
|
||||
@@ -1536,7 +1551,9 @@ describe('useImageCanvasGenerationWorkflow', () => {
|
||||
expect(screen.getByTestId('quick-edit-selection').textContent).toBe(
|
||||
'layer-source:ellipse:2',
|
||||
);
|
||||
expect(screen.getByTestId('quick-edit').textContent).toBe('-');
|
||||
expect(screen.getByTestId('quick-edit').textContent).toBe(
|
||||
'layer-source:idle:对1号红色圈选框里的内容做以下修改:\n对2号红色圈选框里的内容做以下修改:',
|
||||
);
|
||||
});
|
||||
|
||||
it('submits quick edits and updates the source layer directly', async () => {
|
||||
@@ -1546,8 +1563,8 @@ describe('useImageCanvasGenerationWorkflow', () => {
|
||||
render(<GenerationWorkflowHarness />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开快速编辑' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '填写快速编辑提示词' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '提交生成' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '填写快速编辑' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '提交快速编辑' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(editEditorImageMock).toHaveBeenCalledWith(
|
||||
@@ -1558,7 +1575,14 @@ describe('useImageCanvasGenerationWorkflow', () => {
|
||||
sourceResourceId: 'resource-source',
|
||||
generationInputs: {
|
||||
fields: [{ title: '快速编辑提示词', value: '快速修图' }],
|
||||
references: [],
|
||||
references: [
|
||||
{
|
||||
title: '原图',
|
||||
label: '源图',
|
||||
refType: 'project-resource',
|
||||
refId: 'resource-source',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -1755,7 +1779,9 @@ describe('useImageCanvasGenerationWorkflow', () => {
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开快速编辑' }));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('quick-edit').textContent).toBe('-');
|
||||
expect(screen.getByTestId('quick-edit').textContent).toBe(
|
||||
'layer-source:idle:-',
|
||||
);
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开修改状态' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开UI素材框选' }));
|
||||
|
||||
@@ -1024,8 +1024,6 @@ export function useImageCanvasGenerationWorkflow({
|
||||
);
|
||||
return createQuickEditPanelDraft(sourceLayer, {
|
||||
imageModel: sourceDialog?.imageModel,
|
||||
aspectRatio: sourceDialog?.aspectRatio,
|
||||
imageSize: sourceDialog?.imageSize,
|
||||
});
|
||||
},
|
||||
[canvasGenerationDialogs],
|
||||
@@ -1038,48 +1036,11 @@ export function useImageCanvasGenerationWorkflow({
|
||||
setUiAssetExtractionState(null);
|
||||
setCropExpandPanel(null);
|
||||
setCharacterAnimationPanel(null);
|
||||
const sourceDialog = findSourceGenerationDialog(
|
||||
canvasGenerationDialogs,
|
||||
sourceLayer,
|
||||
);
|
||||
const sameSourceDraft = createSameSourceGenerationDialogDraft({
|
||||
sourceLayer,
|
||||
canvasSize,
|
||||
viewport,
|
||||
sourceDialog,
|
||||
mode: 'quick-edit',
|
||||
sourceAnimationLayer: findSourceAnimationLayer(layers, sourceLayer),
|
||||
});
|
||||
if (sameSourceDraft) {
|
||||
openPlacedCanvasGenerationDialog(sameSourceDraft);
|
||||
setQuickEditPanel(null);
|
||||
setQuickEditSelectionState(null);
|
||||
selectSingleLayer(null);
|
||||
setActiveTool(getCanvasToolForGenerationMode(sameSourceDraft.mode));
|
||||
return;
|
||||
}
|
||||
const quickEditDraft = createQuickEditModeDraft(sourceLayer);
|
||||
const quickEditSize = resolveEditorImageGenerationPixelSize({
|
||||
model: quickEditDraft.model,
|
||||
aspectRatio: quickEditDraft.aspectRatio,
|
||||
imageSize: quickEditDraft.imageSize,
|
||||
});
|
||||
openPlacedCanvasGenerationDialog({
|
||||
...createQuickEditGenerationDialogDraft({
|
||||
sourceLayer,
|
||||
prompt: quickEditDraft.prompt,
|
||||
references: quickEditDraft.quickEditReferences ?? [],
|
||||
model: quickEditDraft.model,
|
||||
aspectRatio: quickEditDraft.aspectRatio,
|
||||
imageSize: quickEditDraft.imageSize,
|
||||
frame: {
|
||||
width: quickEditSize.width,
|
||||
height: quickEditSize.height,
|
||||
},
|
||||
}),
|
||||
composerOpen: true,
|
||||
});
|
||||
setQuickEditPanel(null);
|
||||
archiveActiveCanvasGenerationDialog();
|
||||
setGenerateDialog(null);
|
||||
setQuickEditPanel(quickEditDraft);
|
||||
selectSingleLayer(sourceLayer.id);
|
||||
const nextSelectionState = createUiAssetExtractionState(sourceLayer.id, {
|
||||
initialTool: null,
|
||||
model: DEFAULT_IMAGE_MODEL,
|
||||
@@ -1087,22 +1048,20 @@ export function useImageCanvasGenerationWorkflow({
|
||||
quickEditSelectionStateRef.current = nextSelectionState;
|
||||
setQuickEditSelectionState(nextSelectionState);
|
||||
quickEditSelectionMarkCounterRef.current = 0;
|
||||
selectSingleLayer(sourceLayer.id);
|
||||
setViewport(resolveQuickEditFocusViewport({ sourceLayer, canvasSize }));
|
||||
setActiveTool('generate');
|
||||
},
|
||||
[
|
||||
archiveActiveCanvasGenerationDialog,
|
||||
canvasSize,
|
||||
createQuickEditModeDraft,
|
||||
canvasGenerationDialogs,
|
||||
layers,
|
||||
openPlacedCanvasGenerationDialog,
|
||||
selectSingleLayer,
|
||||
setActiveTool,
|
||||
setGenerateDialog,
|
||||
setImageContextMenu,
|
||||
setMetadataLayer,
|
||||
setQuickEditPanel,
|
||||
setViewport,
|
||||
viewport,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -2012,6 +1971,23 @@ export function useImageCanvasGenerationWorkflow({
|
||||
draftMark: null,
|
||||
marks: [...currentSelectionState.marks, normalizedMark],
|
||||
}));
|
||||
setQuickEditPanel((currentPanel) =>
|
||||
currentPanel?.sourceLayerId === currentSelectionState.sourceLayerId
|
||||
? {
|
||||
...currentPanel,
|
||||
status:
|
||||
currentPanel.status === 'failed' ? 'idle' : currentPanel.status,
|
||||
errorMessage:
|
||||
currentPanel.status === 'failed'
|
||||
? undefined
|
||||
: currentPanel.errorMessage,
|
||||
prompt: appendQuickEditSelectionPrompt(
|
||||
currentPanel.prompt,
|
||||
nextMarkNumber,
|
||||
),
|
||||
}
|
||||
: currentPanel,
|
||||
);
|
||||
setGenerateDialog((currentDialog) =>
|
||||
currentDialog?.mode === 'quick-edit' &&
|
||||
currentDialog.sourceLayerId === currentSelectionState.sourceLayerId
|
||||
@@ -2031,7 +2007,7 @@ export function useImageCanvasGenerationWorkflow({
|
||||
}
|
||||
: currentDialog,
|
||||
);
|
||||
}, [setGenerateDialog, updateQuickEditSelectionState]);
|
||||
}, [setGenerateDialog, setQuickEditPanel, updateQuickEditSelectionState]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!quickEditSelectionState) {
|
||||
|
||||
@@ -1398,6 +1398,7 @@ describe('editorProjectClient', () => {
|
||||
assetFolderId: 'project',
|
||||
assetLabel: '角色形象 修改结果',
|
||||
sourceResourceId: 'resource-character-1',
|
||||
targetLayerId: 'layer-character-1',
|
||||
generationInputs: {
|
||||
fields: [{ title: '修改提示词', value: '把画面改成黄昏光线' }],
|
||||
references: [],
|
||||
@@ -1425,6 +1426,7 @@ describe('editorProjectClient', () => {
|
||||
assetFolderId: 'project',
|
||||
assetLabel: '角色形象 修改结果',
|
||||
sourceResourceId: 'resource-character-1',
|
||||
targetLayerId: 'layer-character-1',
|
||||
}),
|
||||
}),
|
||||
'修改图片失败',
|
||||
|
||||
@@ -196,6 +196,7 @@ export type EditorImageEditInput = {
|
||||
assetFolderId?: string | null;
|
||||
assetLabel?: string | null;
|
||||
sourceResourceId?: string | null;
|
||||
targetLayerId?: string | null;
|
||||
canvasCompletion?: EditorCanvasGenerationCompletionInput | null;
|
||||
};
|
||||
|
||||
@@ -910,6 +911,7 @@ export async function editEditorImage(input: EditorImageEditInput) {
|
||||
...(input.sourceResourceId
|
||||
? { sourceResourceId: input.sourceResourceId }
|
||||
: {}),
|
||||
...(input.targetLayerId ? { targetLayerId: input.targetLayerId } : {}),
|
||||
...(input.canvasCompletion
|
||||
? { canvasCompletion: input.canvasCompletion }
|
||||
: {}),
|
||||
|
||||
Reference in New Issue
Block a user