diff --git a/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx index 2bc4f979c..1eeabed63 100644 --- a/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx +++ b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx @@ -16,6 +16,7 @@ import type { EditorAgentMessage, EditorAgentMessageResponse, } from '@/packages/shared/src/contracts'; +import { setPlainTextEditorValue } from '@/src/components/common/AutoGrowTextArea.test-utils.ts'; import type { EditorAgentConversationClient } from '@/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.ts'; import { EDITOR_AGENT_PATIENCE_NOTICE_DELAY_MS } from '@/src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.ts'; import { ImageCanvasActionsProvider } from '@/src/components/image-editor/ImageCanvasActionsProvider.tsx'; @@ -135,9 +136,10 @@ function createClient(): EditorAgentConversationClient { } function enterAttachmentPrompt() { - fireEvent.change(screen.getByLabelText('发送给画布 Agent'), { - target: { value: ATTACHMENT_PROMPT }, - }); + setPlainTextEditorValue( + screen.getByLabelText('发送给画布 Agent'), + ATTACHMENT_PROMPT, + ); } afterEach(() => { @@ -294,9 +296,10 @@ describe('EditorAgentConversationPanelView', () => { name: '新建对话', }) as HTMLButtonElement; expect(newConversationButton.disabled).toBe(false); - fireEvent.change(screen.getByLabelText('发送给画布 Agent'), { - target: { value: '应保留到新会话的草稿' }, - }); + setPlainTextEditorValue( + screen.getByLabelText('发送给画布 Agent'), + '应保留到新会话的草稿', + ); fireEvent.click(screen.getByRole('button', { name: '添加附件' })); const attachmentDialog = screen.getByRole('dialog', { name: '选择图片附件', @@ -332,25 +335,27 @@ describe('EditorAgentConversationPanelView', () => { ).toBe('conversation-2'); expect(newConversationButton.disabled).toBe(true); }); - expect( - (screen.getByLabelText('发送给画布 Agent') as HTMLTextAreaElement).value, - ).toBe('应保留到新会话的草稿'); + expect(screen.getByLabelText('发送给画布 Agent').textContent).toBe( + '应保留到新会话的草稿', + ); expect( screen.getByText('一二三四五六七八九十甲乙丙丁戊己庚辛壬癸子丑寅卯'), ).toBeTruthy(); expect(screen.getByRole('option', { name: '角色参考' })).toBeTruthy(); expect(screen.getByRole('option', { name: '新对话' })).toBeTruthy(); - fireEvent.change(screen.getByLabelText('发送给画布 Agent'), { - target: { value: '尚未发送的草稿' }, - }); + setPlainTextEditorValue( + screen.getByLabelText('发送给画布 Agent'), + '尚未发送的草稿', + ); expect(newConversationButton.disabled).toBe(true); fireEvent.click(newConversationButton); expect(client.createConversation).toHaveBeenCalledTimes(1); - fireEvent.change(screen.getByLabelText('发送给画布 Agent'), { - target: { value: '参考附件做像素风' }, - }); + setPlainTextEditorValue( + screen.getByLabelText('发送给画布 Agent'), + '参考附件做像素风', + ); fireEvent.click(screen.getByRole('button', { name: '发送' })); await waitFor(() => { @@ -491,9 +496,10 @@ describe('EditorAgentConversationPanelView', () => { }) as HTMLButtonElement; expect(newConversationButton.disabled).toBe(true); - fireEvent.change(screen.getByLabelText('发送给画布 Agent'), { - target: { value: '未发送内容' }, - }); + setPlainTextEditorValue( + screen.getByLabelText('发送给画布 Agent'), + '未发送内容', + ); expect(newConversationButton.disabled).toBe(true); fireEvent.click(newConversationButton); expect(client.createConversation).not.toHaveBeenCalled(); @@ -516,17 +522,18 @@ describe('EditorAgentConversationPanelView', () => { await waitFor(() => { expect(screen.getByText('已经看到画布内容')).toBeTruthy(); }); - fireEvent.change(screen.getByLabelText('发送给画布 Agent'), { - target: { value: '需要保留的草稿' }, - }); + setPlainTextEditorValue( + screen.getByLabelText('发送给画布 Agent'), + '需要保留的草稿', + ); fireEvent.click(screen.getByRole('button', { name: '新建对话' })); await waitFor(() => { expect(screen.getByText('创建新会话失败')).toBeTruthy(); }); - expect( - (screen.getByLabelText('发送给画布 Agent') as HTMLTextAreaElement).value, - ).toBe('需要保留的草稿'); + expect(screen.getByLabelText('发送给画布 Agent').textContent).toBe( + '需要保留的草稿', + ); expect(screen.getByText('已经看到画布内容')).toBeTruthy(); expect((screen.getByLabelText('当前对话') as HTMLSelectElement).value).toBe( 'conversation-1', @@ -556,15 +563,13 @@ describe('EditorAgentConversationPanelView', () => { }); const draftInput = screen.getByLabelText( '发送给画布 Agent', - ) as HTMLTextAreaElement; - fireEvent.change(draftInput, { - target: { value: '旧项目草稿' }, - }); + ) as HTMLElement; + setPlainTextEditorValue(draftInput, '旧项目草稿'); fireEvent.click(screen.getByRole('button', { name: '新建对话' })); await waitFor(() => { expect(client.createConversation).toHaveBeenCalledWith('project-1', {}); - expect(draftInput.disabled).toBe(false); + expect(draftInput.getAttribute('aria-disabled')).toBeNull(); }); const conversationSelect = screen.getByLabelText( '当前对话', @@ -578,12 +583,10 @@ describe('EditorAgentConversationPanelView', () => { (screen.getByRole('button', { name: '添加附件' }) as HTMLButtonElement) .disabled, ).toBe(false); - fireEvent.change(draftInput, { - target: { value: '创建期间更新的草稿' }, - }); - fireEvent.submit(draftInput.closest('form') as HTMLFormElement); + setPlainTextEditorValue(draftInput, '创建期间更新的草稿'); + fireEvent.click(sendButton); expect(client.sendMessage).not.toHaveBeenCalled(); - expect(draftInput.value).toBe('创建期间更新的草稿'); + expect(draftInput.textContent).toBe('创建期间更新的草稿'); await act(async () => { resolveCreate({ @@ -596,7 +599,7 @@ describe('EditorAgentConversationPanelView', () => { }); await Promise.resolve(); }); - expect(draftInput.value).toBe('创建期间更新的草稿'); + expect(draftInput.textContent).toBe('创建期间更新的草稿'); expect(conversationSelect.value).toBe('conversation-2'); expect(conversationSelect.disabled).toBe(false); expect(sendButton.disabled).toBe(false); @@ -659,13 +662,11 @@ describe('EditorAgentConversationPanelView', () => { }); const draftInput = screen.getByLabelText( '发送给画布 Agent', - ) as HTMLTextAreaElement; + ) as HTMLElement; const conversationSelect = screen.getByLabelText( '当前对话', ) as HTMLSelectElement; - fireEvent.change(draftInput, { - target: { value: '切换期间必须保留的草稿' }, - }); + setPlainTextEditorValue(draftInput, '切换期间必须保留的草稿'); fireEvent.change(conversationSelect, { target: { value: 'conversation-2' }, }); @@ -677,9 +678,9 @@ describe('EditorAgentConversationPanelView', () => { expect(client.getConversation).toHaveBeenCalledWith('conversation-2'); expect(sendButton.disabled).toBe(true); }); - fireEvent.submit(draftInput.closest('form') as HTMLFormElement); + fireEvent.click(sendButton); expect(client.sendMessage).not.toHaveBeenCalled(); - expect(draftInput.value).toBe('切换期间必须保留的草稿'); + expect(draftInput.textContent).toBe('切换期间必须保留的草稿'); await act(async () => { resolveConversationLoad({ @@ -702,7 +703,7 @@ describe('EditorAgentConversationPanelView', () => { await Promise.resolve(); }); expect(await screen.findByText('背景会话内容')).toBeTruthy(); - expect(draftInput.value).toBe('切换期间必须保留的草稿'); + expect(draftInput.textContent).toBe('切换期间必须保留的草稿'); expect(sendButton.disabled).toBe(false); }); @@ -729,9 +730,10 @@ describe('EditorAgentConversationPanelView', () => { }); vi.useFakeTimers(); - fireEvent.change(screen.getByLabelText('发送给画布 Agent'), { - target: { value: '继续规划' }, - }); + setPlainTextEditorValue( + screen.getByLabelText('发送给画布 Agent'), + '继续规划', + ); fireEvent.click(screen.getByRole('button', { name: '发送' })); await act(async () => { @@ -1316,7 +1318,7 @@ describe('EditorAgentConversationPanelView', () => { }) as HTMLButtonElement; expect(sendButton.disabled).toBe(true); - fireEvent.submit(sendButton.closest('form')!); + fireEvent.click(sendButton); expect(client.sendMessage).not.toHaveBeenCalled(); expect(screen.getByText('角色图层')).toBeTruthy(); @@ -1368,15 +1370,16 @@ describe('EditorAgentConversationPanelView', () => { fireEvent.click( within(attachmentDialog).getByRole('button', { name: '应用' }), ); - fireEvent.change(screen.getByLabelText('发送给画布 Agent'), { - target: { value: '失败后恢复这条草稿' }, - }); + setPlainTextEditorValue( + screen.getByLabelText('发送给画布 Agent'), + '失败后恢复这条草稿', + ); fireEvent.click(screen.getByRole('button', { name: '发送' })); expect(await screen.findByText('Network error')).toBeTruthy(); - expect( - (screen.getByLabelText('发送给画布 Agent') as HTMLTextAreaElement).value, - ).toBe('失败后恢复这条草稿'); + expect(screen.getByLabelText('发送给画布 Agent').textContent).toBe( + '失败后恢复这条草稿', + ); expect(screen.getByText('角色图层')).toBeTruthy(); expect( within( @@ -1760,9 +1763,10 @@ describe('EditorAgentConversationPanelView', () => { expect(screen.getByText('已经看到画布内容')).toBeTruthy(); }); - fireEvent.change(screen.getByLabelText('发送给画布 Agent'), { - target: { value: '生成一张图片' }, - }); + setPlainTextEditorValue( + screen.getByLabelText('发送给画布 Agent'), + '生成一张图片', + ); fireEvent.click(screen.getByRole('button', { name: '发送' })); await waitFor(() => { diff --git a/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx index 43416aeea..f5be56938 100644 --- a/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx +++ b/src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.tsx @@ -10,7 +10,6 @@ import { X, } from 'lucide-react'; import { - type FormEvent, useEffect, useState, type WheelEvent as ReactWheelEvent, @@ -139,8 +138,7 @@ export function EditorAgentConversationPanelView({ void createConversation().catch(() => undefined); }; - const submitMessage = (event: FormEvent) => { - event.preventDefault(); + const submitMessage = () => { if (isMessageSubmissionBlocked) { return; } @@ -328,10 +326,7 @@ export function EditorAgentConversationPanelView({ {attachmentError} ) : null} -
+
{attachments.length ? (
{attachments.map((attachment) => ( @@ -354,19 +349,21 @@ export function EditorAgentConversationPanelView({
- +
{ + vi.stubGlobal( + 'ResizeObserver', + class ResizeObserver { + observe() {} + unobserve() {} + disconnect() {} + }, + ); +}); + +function DraftHarness({ + onPaste, + onSubmitRequest, +}: { + onPaste?: (event: ClipboardEvent) => void; + onSubmitRequest: () => void; +}) { + const [value, setValue] = useState(''); + return ( + + ); +} + describe('EditorAgentDraftTextarea', () => { - it('forwards draft changes and paste events through the Agent wrapper', () => { - const onChange = vi.fn(); - const onPaste = vi.fn(); + it('forwards controlled draft edits and paste events', async () => { + const user = userEvent.setup(); + const onPaste = vi.fn((event: ClipboardEvent) => event.preventDefault()); + render(); - render( - , - ); - - const input = screen.getByLabelText( - '发送给画布 Agent', - ) as HTMLTextAreaElement; - expect(input.className).toContain('auto-grow-text-area'); - expect(input.className).toContain( - 'editor-agent-conversation__draft-input', - ); - - fireEvent.change(input, { target: { value: '新草稿' } }); - expect(onChange).toHaveBeenCalledWith('新草稿'); - fireEvent.paste(input); + const editor = screen.getByRole('textbox', { + name: '发送给画布 Agent', + }); + await user.click(editor); + fireEvent.paste(editor, { + clipboardData: { + files: [], + getData: () => '', + items: [], + types: [], + }, + }); expect(onPaste).toHaveBeenCalledTimes(1); }); - it('submits on Enter and keeps line breaks or IME confirmation from submitting', () => { - const onSubmit = vi.fn((event: FormEvent) => { - event.preventDefault(); - }); - render( -
- - , + it('submits on Enter but preserves Shift+Enter and IME confirmation', async () => { + const user = userEvent.setup(); + const onSubmitRequest = vi.fn(); + const { container } = render( + , ); - const input = screen.getByLabelText('发送给画布 Agent'); - fireEvent.keyDown(input, { key: 'Enter', shiftKey: true }); - expect(onSubmit).not.toHaveBeenCalled(); + const editor = screen.getByRole('textbox', { + name: '发送给画布 Agent', + }); + await user.click(editor); + fireEvent.keyDown(editor, { key: 'Enter', shiftKey: true }); + expect(onSubmitRequest).not.toHaveBeenCalled(); - fireEvent.keyDown(input, { + fireEvent.keyDown(editor, { key: 'Enter', - isComposing: false, + isComposing: true, keyCode: 229, }); - expect(onSubmit).not.toHaveBeenCalled(); + expect(onSubmitRequest).not.toHaveBeenCalled(); - fireEvent.keyDown(input, { key: 'Enter' }); - expect(onSubmit).toHaveBeenCalledTimes(1); + fireEvent.keyDown(editor, { key: 'Enter' }); + expect(onSubmitRequest).toHaveBeenCalledTimes(1); + expect(container.querySelector('form')).toBeNull(); + expect(container.querySelector('textarea')).toBeNull(); }); }); diff --git a/src/components/image-editor/EditorAgentConversation/EditorAgentDraftTextarea.tsx b/src/components/image-editor/EditorAgentConversation/EditorAgentDraftTextarea.tsx index fdeabcbc4..fea992fd1 100644 --- a/src/components/image-editor/EditorAgentConversation/EditorAgentDraftTextarea.tsx +++ b/src/components/image-editor/EditorAgentConversation/EditorAgentDraftTextarea.tsx @@ -1,39 +1,24 @@ -import { - type ClipboardEventHandler, - type KeyboardEvent, -} from 'react'; - import { AutoGrowTextArea } from '@/src/components/common/AutoGrowTextArea'; export function EditorAgentDraftTextarea({ value, - onChange, + onValueChange, onPaste, + onSubmitRequest, }: { value: string; - onChange: (value: string) => void; - onPaste?: ClipboardEventHandler; + onValueChange: (value: string) => void; + onPaste?: (event: ClipboardEvent) => void; + onSubmitRequest: () => void; }) { - const handleKeyDown = (event: KeyboardEvent) => { - if ( - event.key === 'Enter' && - !event.shiftKey && - !event.nativeEvent.isComposing && - event.nativeEvent.keyCode !== 229 - ) { - event.preventDefault(); - event.currentTarget.form?.requestSubmit(); - } - }; - return ( onChange(event.currentTarget.value)} + onValueChange={onValueChange} onPaste={onPaste} - onKeyDown={handleKeyDown} + onSubmitRequest={onSubmitRequest} /> ); } diff --git a/src/components/image-editor/EditorAgentConversation/useConversationAttachments.ts b/src/components/image-editor/EditorAgentConversation/useConversationAttachments.ts index 197dbd4aa..da9d272aa 100644 --- a/src/components/image-editor/EditorAgentConversation/useConversationAttachments.ts +++ b/src/components/image-editor/EditorAgentConversation/useConversationAttachments.ts @@ -1,5 +1,4 @@ import { - type ClipboardEvent as ReactClipboardEvent, useCallback, useMemo, useRef, @@ -305,7 +304,7 @@ export function useConversationAttachments({ ); const handleInputPaste = useCallback( - (event: ReactClipboardEvent) => { + (event: ClipboardEvent) => { const imageFiles = extractClipboardImageFiles(event.clipboardData); if (!imageFiles.length || isPastingAttachmentRef.current) { return; diff --git a/src/components/image-editor/ImageCanvasBasicGenerationComposerView.tsx b/src/components/image-editor/ImageCanvasBasicGenerationComposerView.tsx index 7fc76170a..202570fdd 100644 --- a/src/components/image-editor/ImageCanvasBasicGenerationComposerView.tsx +++ b/src/components/image-editor/ImageCanvasBasicGenerationComposerView.tsx @@ -76,7 +76,7 @@ type ImageCanvasBasicGenerationComposerViewProps = { referenceAriaLabelFormatter?: ReferenceLabelFormatter; referenceRemoveLabelFormatter?: ReferenceLabelFormatter; showReferenceAddButton?: boolean; - formClassName?: string; + panelClassName?: string; footerClassName?: string; promptClassName?: string; referenceSlotClassName?: string; @@ -130,7 +130,7 @@ export function ImageCanvasBasicGenerationComposerView({ referenceAriaLabelFormatter, referenceRemoveLabelFormatter, showReferenceAddButton = true, - formClassName, + panelClassName, footerClassName, promptClassName = 'image-canvas-editor__generation-prompt', referenceSlotClassName, @@ -166,8 +166,8 @@ export function ImageCanvasBasicGenerationComposerView({ ? '修改中' : submittingStatusLabel; const hasReferenceMenu = shouldIncludeReferences && onRequestUpload; - const finalFormClassName = - formClassName ?? + const finalPanelClassName = + panelClassName ?? 'image-canvas-editor__generation-composer image-canvas-editor__generation-composer--image'; const finalFooterClassName = footerClassName ?? 'image-canvas-editor__generation-composer-footer'; @@ -180,18 +180,12 @@ export function ImageCanvasBasicGenerationComposerView({ return ( <> -
event.stopPropagation()} - onSubmit={(event) => { - event.preventDefault(); - if (dialog.status !== 'generating') { - onSubmit(dialog); - } - }} > {shouldIncludeReferences ? (
@@ -255,12 +249,12 @@ export function ImageCanvasBasicGenerationComposerView({ disabled={dialog.status === 'generating'} placeholder={resolvedPromptPlaceholder} className={promptClassName} - onChange={(event) => + onValueChange={(value) => setGenerateDialog((currentDialog) => currentDialog ? { ...resetFailedDialogStatus(currentDialog), - prompt: event.target.value, + prompt: value, } : currentDialog, ) @@ -284,6 +278,11 @@ export function ImageCanvasBasicGenerationComposerView({ submitLabel={resolvedSubmitLabel} submitAriaLabel={resolvedSubmitAriaLabel} submitButtonClassName={submitButtonClassName} + onSubmitRequest={() => { + if (dialog.status !== 'generating') { + onSubmit(dialog); + } + }} renderEditorPortal={renderEditorPortal} buildPortalMenuStyle={buildPortalMenuStyle} /> @@ -310,7 +309,7 @@ export function ImageCanvasBasicGenerationComposerView({ {dialog.errorMessage} ) : null} - +
{hasReferenceMenu && isGenerationReferenceMenuOpen && generationReferenceButtonRef diff --git a/src/components/image-editor/ImageCanvasCharacterAnimationPanelView.tsx b/src/components/image-editor/ImageCanvasCharacterAnimationPanelView.tsx index 55dc74d18..9a0019714 100644 --- a/src/components/image-editor/ImageCanvasCharacterAnimationPanelView.tsx +++ b/src/components/image-editor/ImageCanvasCharacterAnimationPanelView.tsx @@ -161,18 +161,12 @@ export function ImageCanvasCharacterAnimationPanelView({ return ( <> -
event.stopPropagation()} - onSubmit={(event) => { - event.preventDefault(); - if (!isGenerating) { - onSubmit(); - } - }} >
- updatePanel({ promptText: event.target.value.slice(0, 4000) }) - } + onValueChange={(value) => updatePanel({ promptText: value })} />
{CHARACTER_ANIMATION_ACTION_PROMPTS.map((preset) => ( @@ -244,12 +236,17 @@ export function ImageCanvasCharacterAnimationPanelView({
{ + if (!isGenerating) { + onSubmit(); + } + }} > {isGenerating ? '生成中' : `生成${price}泥点`} @@ -281,7 +278,7 @@ export function ImageCanvasCharacterAnimationPanelView({ {panel.errorMessage} ) : null} - +
{isParameterMenuOpen ? renderPanelPortal( event.stopPropagation()} - onSubmit={(event) => { - event.preventDefault(); - if (dialog.status !== 'generating') { - onSubmit(dialog); - } - }} >
-
- + ); } diff --git a/src/components/image-editor/ImageCanvasCropExpandPanelView.tsx b/src/components/image-editor/ImageCanvasCropExpandPanelView.tsx index af728a271..d9cf2ff4c 100644 --- a/src/components/image-editor/ImageCanvasCropExpandPanelView.tsx +++ b/src/components/image-editor/ImageCanvasCropExpandPanelView.tsx @@ -41,18 +41,12 @@ export function ImageCanvasCropExpandPanelView({ onSubmit, }: ImageCanvasCropExpandPanelViewProps) { return ( -
event.stopPropagation()} - onSubmit={(event) => { - event.preventDefault(); - if (panel.status !== 'processing') { - onSubmit(); - } - }} >
裁扩 @@ -107,14 +101,19 @@ export function ImageCanvasCropExpandPanelView({ 取消 { + if (panel.status !== 'processing') { + onSubmit(); + } + }} > {panel.status === 'processing' ? '处理中' : '完成'}
-
+ ); } diff --git a/src/components/image-editor/ImageCanvasEditGenerationModalView.tsx b/src/components/image-editor/ImageCanvasEditGenerationModalView.tsx index 114f31175..83e7bcb00 100644 --- a/src/components/image-editor/ImageCanvasEditGenerationModalView.tsx +++ b/src/components/image-editor/ImageCanvasEditGenerationModalView.tsx @@ -60,7 +60,7 @@ export function ImageCanvasEditGenerationModalView({ submitLabel="修改" submitAriaLabel={`修改${editPrice}泥点`} submittingStatusLabel="修改中" - formClassName="image-canvas-editor__generate-form" + panelClassName="image-canvas-editor__generate-form" footerClassName="image-canvas-editor__generate-body" promptClassName="image-canvas-editor__generate-prompt" submitButtonClassName="image-canvas-editor__generate-submit" diff --git a/src/components/image-editor/ImageCanvasGenerationComposerView.tsx b/src/components/image-editor/ImageCanvasGenerationComposerView.tsx index 2e900e3ac..49ed574a3 100644 --- a/src/components/image-editor/ImageCanvasGenerationComposerView.tsx +++ b/src/components/image-editor/ImageCanvasGenerationComposerView.tsx @@ -438,18 +438,12 @@ function ImageCanvasVideoGenerationComposerView({ return ( <> -
event.stopPropagation()} - onSubmit={(event) => { - event.preventDefault(); - if (!isGenerating) { - onSubmit(dialog); - } - }} > {supportsReferences ? (
@@ -485,9 +479,7 @@ function ImageCanvasVideoGenerationComposerView({ disabled={isGenerating} placeholder="你希望生成什么视频?" className="image-canvas-editor__generation-prompt" - onChange={(event) => - updateVideoDialog({ prompt: event.target.value }) - } + onValueChange={(value) => updateVideoDialog({ prompt: value })} />
@@ -697,13 +689,18 @@ function ImageCanvasVideoGenerationComposerView({ : null}
{ + if (!isGenerating) { + onSubmit(dialog); + } + }} > {isGenerating ? ( '生成中' @@ -717,7 +714,7 @@ function ImageCanvasVideoGenerationComposerView({ )}
- +
{isGenerationReferenceMenuOpen && supportsReferences ? renderEditorPortal( event.stopPropagation()} - onSubmit={(event) => { - event.preventDefault(); - if (!canGenerate) { - return; - } - onSubmit(backgroundMusicDialog); - }} > {/* 与生图 / 生视频 / 角色 / 图标同源:自动增高到 max-h-32 后内部滚动。 */} updateBackgroundMusicPrompt(event.target.value)} + onValueChange={updateBackgroundMusicPrompt} /> { + if (canGenerate) { + onSubmit(backgroundMusicDialog); + } + }} > {isGenerating ? ( '生成中' @@ -1079,7 +1074,7 @@ function ImageCanvasAudioGenerationComposerView({ )} - + ); } @@ -1113,18 +1108,12 @@ function ImageCanvasAudioGenerationComposerView({ }; return ( -
event.stopPropagation()} - onSubmit={(event) => { - event.preventDefault(); - if (!isGenerating) { - onSubmit(dialog); - } - }} > updateAudioDialog({ prompt: event.target.value })} + onValueChange={(value) => updateAudioDialog({ prompt: value })} /> {dialog.status === 'failed' ? ( { + if (!isGenerating) { + onSubmit(dialog); + } + }} > {isGenerating ? ( '生成中' @@ -1234,7 +1228,7 @@ function ImageCanvasAudioGenerationComposerView({ )} - + ); } diff --git a/src/components/image-editor/ImageCanvasGenerationImageOptionsView.test.tsx b/src/components/image-editor/ImageCanvasGenerationImageOptionsView.test.tsx index f12e82f76..72b1ad1e7 100644 --- a/src/components/image-editor/ImageCanvasGenerationImageOptionsView.test.tsx +++ b/src/components/image-editor/ImageCanvasGenerationImageOptionsView.test.tsx @@ -44,6 +44,7 @@ function ImageOptionsHarness({ })} submitLabel="生成" submitAriaLabel="生成角色形象" + onSubmitRequest={() => undefined} /> {dialog.imageModel} {dialog.aspectRatio} diff --git a/src/components/image-editor/ImageCanvasGenerationImageOptionsView.tsx b/src/components/image-editor/ImageCanvasGenerationImageOptionsView.tsx index 069eac493..de6490199 100644 --- a/src/components/image-editor/ImageCanvasGenerationImageOptionsView.tsx +++ b/src/components/image-editor/ImageCanvasGenerationImageOptionsView.tsx @@ -41,6 +41,7 @@ type ImageCanvasGenerationImageOptionsViewProps = { submitLabel?: string; submitAriaLabel?: string; submitButtonClassName?: string; + onSubmitRequest: () => void; lockedModel?: string; renderEditorPortal?: (node: ReactNode) => ReactNode; buildPortalMenuStyle?: ( @@ -133,6 +134,7 @@ export function ImageCanvasGenerationImageOptionsView({ submitLabel = '生成', submitAriaLabel = '生成', submitButtonClassName = 'image-canvas-editor__generation-submit', + onSubmitRequest, lockedModel, renderEditorPortal = (node) => node, buildPortalMenuStyle = () => ({}), @@ -419,13 +421,14 @@ export function ImageCanvasGenerationImageOptionsView({ ) : null} {typeof cost === 'number' ? ( {isGenerating ? ( '生成中' diff --git a/src/components/image-editor/ImageCanvasIconSpritesheetComposerView.tsx b/src/components/image-editor/ImageCanvasIconSpritesheetComposerView.tsx index 2e55408fe..c2a9375d5 100644 --- a/src/components/image-editor/ImageCanvasIconSpritesheetComposerView.tsx +++ b/src/components/image-editor/ImageCanvasIconSpritesheetComposerView.tsx @@ -86,18 +86,12 @@ export function ImageCanvasIconSpritesheetComposerView({ const references = dialog.generationReferences ?? []; return ( -
event.stopPropagation()} - onSubmit={(event) => { - event.preventDefault(); - if (dialog.status !== 'generating') { - onSubmit({ ...dialog, prompt: descriptionText }); - } - }} >
onRequestUpload('generation-reference')} />
-