diff --git a/src/components/image-editor/ImageCanvasGenerationComposerView.test.tsx b/src/components/image-editor/ImageCanvasGenerationComposerView.test.tsx index 93079c042..0e07f44d1 100644 --- a/src/components/image-editor/ImageCanvasGenerationComposerView.test.tsx +++ b/src/components/image-editor/ImageCanvasGenerationComposerView.test.tsx @@ -961,13 +961,20 @@ describe('ImageCanvasGenerationComposerView', () => { '.image-canvas-editor__generation-composer-footer', ); expect(footer).toBeTruthy(); + // 底部一行:左侧一键优化与时长、循环,右侧 ElevenLabs + 生成。 expect(footer?.children[0]?.className).toContain( + 'image-canvas-editor__sound-effect-prompt-actions', + ); + expect(footer?.children[1]?.className).toContain( 'image-canvas-editor__option-popover-anchor--dimensions', ); expect(footer?.children[2]?.className).toContain( - 'image-canvas-editor__option-popover-anchor--model', + 'image-canvas-editor__sound-effect-loop', ); expect(footer?.children[3]?.className).toContain( + 'image-canvas-editor__option-popover-anchor--model', + ); + expect(footer?.children[4]?.className).toContain( 'image-canvas-editor__generation-submit', ); const prompt = within(panel).getByRole('textbox', { name: 'prompt' }); @@ -988,7 +995,7 @@ describe('ImageCanvasGenerationComposerView', () => { expect(within(panel).queryByText('单次')).toBeNull(); expect( within(panel) - .getByRole('button', { name: 'Loop' }) + .getByRole('button', { name: '循环 关' }) .getAttribute('aria-pressed'), ).toBe('false'); expect(within(panel).queryByText(/BPM/u)).toBeNull(); @@ -1034,7 +1041,7 @@ describe('ImageCanvasGenerationComposerView', () => { expect(screen.getByLabelText('当前音效时长').textContent).toBe('8'); expect(screen.getByRole('button', { name: '音效时长 8秒' })).toBeTruthy(); - fireEvent.click(within(panel).getByRole('button', { name: 'Loop' })); + fireEvent.click(within(panel).getByRole('button', { name: '循环 关' })); fireEvent.submit(panel); expect(onSubmitImageGeneration).toHaveBeenCalledWith( @@ -1082,7 +1089,7 @@ describe('ImageCanvasGenerationComposerView', () => { '一键优化', '撤销', '音效时长 5秒', - 'Loop', + '循环 关', '生成游戏音效', ]) { expect( diff --git a/src/components/image-editor/ImageCanvasGenerationComposerView.tsx b/src/components/image-editor/ImageCanvasGenerationComposerView.tsx index 0086e4b35..a2ec9942e 100644 --- a/src/components/image-editor/ImageCanvasGenerationComposerView.tsx +++ b/src/components/image-editor/ImageCanvasGenerationComposerView.tsx @@ -1,10 +1,12 @@ import { Check, ChevronDown, + Clock, Cpu, Film, ImageIcon, Music, + Repeat, Sparkles, Undo2, WandSparkles, @@ -1183,7 +1185,7 @@ function ImageCanvasAudioGenerationComposerView({ return (
) : null} -
- { - void controller.optimizePrompt(soundEffectDialog.id); - }} - > - - {isAiProcessing ? '优化中' : '一键优化'} - - {isUndoVisible ? ( +
+
controller.undoPrompt(soundEffectDialog.id)} + className="image-canvas-editor__background-music-prompt-action image-canvas-editor__sound-effect-prompt-action--optimize" + aria-label="一键优化" + disabled={!canOptimize} + onClick={() => { + void controller.optimizePrompt(soundEffectDialog.id); + }} > - 撤销 + {isAiProcessing ? '优化中' : '一键优化'} - ) : null} -
-
+ {isUndoVisible ? ( + controller.undoPrompt(soundEffectDialog.id)} + > + + 撤销 + + ) : null} +
} onClick={() => setIsSoundOptionsOpen((isOpen) => !isOpen)} > - {soundOptionLabel} + + {soundOptionLabel} {isSoundOptionsOpen ? renderEditorPortal( @@ -1366,12 +1374,21 @@ function ImageCanvasAudioGenerationComposerView({
updateAudioDialog({ soundLoop: !soundLoop })} > - Loop + + 循环 + + {soundLoop ? '开' : '关'} +
{ status: 'idle', composerOpen: true, soundModel: 'eleven_text_to_sound_v2', - soundDurationMode: 'manual', + soundDurationMode: 'auto', soundDurationSeconds: 5, soundLoop: false, placeholder: { diff --git a/src/components/image-editor/ImageCanvasGenerationDialogModel.ts b/src/components/image-editor/ImageCanvasGenerationDialogModel.ts index 956b475c0..0e5beb228 100644 --- a/src/components/image-editor/ImageCanvasGenerationDialogModel.ts +++ b/src/components/image-editor/ImageCanvasGenerationDialogModel.ts @@ -404,7 +404,8 @@ export function createSoundEffectGenerationDialogDraft({ status: 'idle', composerOpen: true, soundModel: DEFAULT_SOUND_EFFECT_MODEL, - soundDurationMode: 'manual', + // 与设计稿一致:新建音效默认自动时长,由模型按描述决定长度。 + soundDurationMode: 'auto', soundDurationSeconds: DEFAULT_SOUND_EFFECT_DURATION_SECONDS, soundLoop: false, placeholder: { diff --git a/src/index.css b/src/index.css index ffdd1ce69..a524e12f4 100644 --- a/src/index.css +++ b/src/index.css @@ -7541,11 +7541,18 @@ button.image-canvas-editor__reference-chip:disabled { box-shadow: none; } +/* + * 开启态按设计稿取淡橙底 + 橙字 + 橙描边,不用实心橙底配近白文字:实底方案在通用 hover + * 把底色换成 #f8fafc 后会变成白底白字。hover 变体必须一并写死,否则通用 hover 的 + * `background` 简写仍会把淡橙底洗掉;阴影仍由通用规则提供,悬停反馈不丢。 + */ .image-canvas-editor__generation-composer - .platform-inline-option-button[aria-pressed='true'] { + .platform-inline-option-button[aria-pressed='true'], +.image-canvas-editor__generation-composer + .platform-inline-option-button[aria-pressed='true']:hover:not(:disabled) { border-color: var(--image-canvas-brand-border-strong); - background: var(--image-canvas-brand-fill); - color: var(--image-canvas-brand-text-on-fill); + background: var(--image-canvas-brand-soft); + color: var(--image-canvas-brand-accent); } .image-canvas-editor__generation-submit { @@ -16892,8 +16899,85 @@ button { min-width: 0; } +/* + * 助手动作与时长、循环、模型和生成同处底部一行:共享 footer 的 5 列 grid 在这里换成 + * flex,作用域限定在 SFX,图片 / 视频 composer 仍走原来的 grid。 + */ +.image-canvas-editor__generation-composer--sound-effect + .image-canvas-editor__generation-composer-footer { + display: flex; + align-items: center; + gap: 0.4rem; + padding-top: 0.5rem; +} + +/* + * `.image-canvas-editor__option-cluster` 的 `width: 100%` 是给 grid 单元格写的,在 flex + * 行里会把基准宽度撑成整行再收缩。循环按钮是 footer 的直接子元素,必须改回按内容取宽; + * 时长和模型包在 anchor 里,宽度由 anchor 收敛,不受影响。 + */ +.image-canvas-editor__generation-composer--sound-effect + .image-canvas-editor__generation-composer-footer + > .image-canvas-editor__option-cluster { + width: auto; +} + +/* anchor 是块级盒、内部按钮是 inline-block,行盒下沿会多出基线空隙导致高度错位。 */ +.image-canvas-editor__generation-composer--sound-effect + .image-canvas-editor__generation-composer-footer + > .image-canvas-editor__option-popover-anchor { + display: flex; + align-items: center; +} + +/* 模型 chip 起分组作用:它和生成按钮被顶到右侧,左侧留给动作与参数。 */ +.image-canvas-editor__generation-composer--sound-effect + .image-canvas-editor__option-popover-anchor--model { + margin-left: auto; +} + +/* flex 布局下提交按钮硬编码的 grid-column 自动失效,无需覆盖。 */ +.image-canvas-editor__generation-composer--sound-effect + .image-canvas-editor__generation-submit { + height: 2.25rem; + min-height: 0; +} + .image-canvas-editor__sound-effect-prompt-actions { - grid-column: 1 / -1; + display: flex; + align-items: center; + gap: 0.4rem; + min-width: 0; +} + +/* 共享的 label 是无 gap 的 inline-flex,加了图标和状态后必须自己撑开间距。 */ +.image-canvas-editor__generation-composer--sound-effect + .image-canvas-editor__option-cluster + .platform-inline-option-button__label { + gap: 0.3rem; +} + +/* 时长与循环的前置图标,尺寸对齐助手按钮里的图标。 */ +.image-canvas-editor__sound-effect-option-icon { + display: inline-flex; + align-items: center; + justify-content: center; +} + +.image-canvas-editor__sound-effect-option-icon svg { + width: 0.85rem; + height: 0.85rem; +} + +/* 开 / 关状态跟在「循环」后面,用弱化色,避免和标签抢读。 */ +.image-canvas-editor__sound-effect-loop-state { + color: #94a3b8; + font-weight: 700; +} + +.image-canvas-editor__sound-effect-loop[aria-pressed='true'] + .image-canvas-editor__sound-effect-loop-state { + color: inherit; } .image-canvas-editor__sound-effect-prompt-action--optimize:not(:disabled) { @@ -16920,12 +17004,6 @@ button { margin: 0; } -.image-canvas-editor__sound-effect-loop[aria-pressed='true'] { - border-color: var(--image-canvas-brand-border-strong); - background: var(--image-canvas-brand-soft); - color: var(--image-canvas-brand-accent); -} - /* * 助手动作按钮:`.platform-button` 给的是 2.9rem 高与 0.7rem/1rem 内边距,比同一面板里 * 2.25rem 的生成按钮高出一截。这里按两级选择器压回去,避免整排按钮比主操作还重。