修复音频生成面板布局

对齐生成游戏音效面板底部参数、模型与生成按钮布局。

固定展示音效 Vidu 模型和背景音乐 Suno 模型。

补充音频生成面板布局回归测试并更新设计文档。
This commit is contained in:
2026-06-19 13:30:40 +08:00
parent fdcccec385
commit 23a6662c62
4 changed files with 104 additions and 63 deletions
@@ -517,8 +517,25 @@ describe('ImageCanvasGenerationComposerView', () => {
render(<AudioHarness />);
const panel = screen.getByRole('dialog', { name: '生成游戏音效' });
const footer = panel.querySelector(
'.image-canvas-editor__generation-composer-footer',
);
expect(footer).toBeTruthy();
expect(footer?.children[0]?.className).toContain(
'image-canvas-editor__option-popover-anchor--dimensions',
);
expect(footer?.children[1]?.className).toContain(
'image-canvas-editor__option-popover-anchor--model',
);
expect(footer?.children[2]?.className).toContain(
'image-canvas-editor__generation-submit',
);
expect(within(panel).getByRole('textbox', { name: 'prompt' })).toBeTruthy();
expect(screen.getByLabelText('当前音效模型').textContent).toBe('audio1.0');
const soundModelButton = within(panel).getByRole('button', {
name: '音效模型 Vidu',
});
expect((soundModelButton as HTMLButtonElement).disabled).toBe(true);
expect(within(panel).getByText('Vidu')).toBeTruthy();
expect(within(panel).queryByText(/Suno/u)).toBeNull();
expect(
@@ -570,6 +587,23 @@ describe('ImageCanvasGenerationComposerView', () => {
expect(
within(panel).getByRole('textbox', { name: 'gpt_description_prompt' }),
).toBeTruthy();
const footer = panel.querySelector(
'.image-canvas-editor__generation-composer-footer',
);
expect(footer).toBeTruthy();
expect(footer?.children[0]?.className).toContain(
'image-canvas-editor__option-popover-anchor--model',
);
expect(footer?.children[1]?.className).toContain(
'image-canvas-editor__generation-submit',
);
const backgroundMusicModelButton = within(panel).getByRole('button', {
name: '背景音乐模型 Suno',
});
expect((backgroundMusicModelButton as HTMLButtonElement).disabled).toBe(
true,
);
expect(within(panel).getByText('Suno')).toBeTruthy();
expect(within(panel).queryByText('make_instrumental')).toBeNull();
expect(within(panel).getByRole('button', { name: '生成游戏背景音乐' }).textContent).toBe(
'生成5泥点',
@@ -670,6 +670,10 @@ function ImageCanvasAudioGenerationComposerView({
const soundDuration = normalizeSoundDuration(dialog.soundDurationSeconds);
const soundOptionLabel = `${soundDuration}秒`;
const dialogLabel = isSoundEffect ? '生成游戏音效' : '生成游戏背景音乐';
const fixedModelLabel = isSoundEffect ? currentSoundModel.label : 'Suno';
const fixedModelAriaLabel = isSoundEffect
? `音效模型 ${fixedModelLabel}`
: `背景音乐模型 ${fixedModelLabel}`;
const cost = isSoundEffect
? calculateEditorSoundEffectPrice(soundModel)
: calculateEditorBackgroundMusicPrice(DEFAULT_BACKGROUND_MUSIC_MODEL);
@@ -737,65 +741,65 @@ function ImageCanvasAudioGenerationComposerView({
) : null}
<div className="image-canvas-editor__generation-composer-footer">
{isSoundEffect ? (
<>
<div className="image-canvas-editor__option-popover-anchor image-canvas-editor__option-popover-anchor--dimensions">
<PlatformInlineOptionButton
ref={soundModelButtonRef}
className="image-canvas-editor__option-cluster image-canvas-editor__option-cluster--model"
aria-label={`音效模型 ${currentSoundModel.label}`}
ref={soundOptionsButtonRef}
className="image-canvas-editor__option-cluster image-canvas-editor__option-cluster--dimensions"
aria-label={`音效时长 ${soundOptionLabel}`}
aria-expanded={isSoundOptionsOpen}
disabled={isGenerating}
trailingIcon={<ChevronDown className="h-3 w-3" />}
onClick={() => setIsSoundOptionsOpen((isOpen) => !isOpen)}
>
<span className="image-canvas-editor__model-trigger-label">
<span
className="image-canvas-editor__model-icon"
aria-hidden="true"
>
<Music />
</span>
<span>{currentSoundModel.label}</span>
</span>
{soundOptionLabel}
</PlatformInlineOptionButton>
<div className="image-canvas-editor__option-popover-anchor image-canvas-editor__option-popover-anchor--dimensions">
<PlatformInlineOptionButton
ref={soundOptionsButtonRef}
className="image-canvas-editor__option-cluster image-canvas-editor__option-cluster--dimensions"
aria-label={`音效时长 ${soundOptionLabel}`}
aria-expanded={isSoundOptionsOpen}
disabled={isGenerating}
trailingIcon={<ChevronDown className="h-3 w-3" />}
onClick={() => setIsSoundOptionsOpen((isOpen) => !isOpen)}
>
{soundOptionLabel}
</PlatformInlineOptionButton>
{isSoundOptionsOpen
? renderEditorPortal(
<PlatformFloatingMenu
className="image-canvas-editor__option-popover image-canvas-editor__option-popover--audio-options image-canvas-editor__portal-menu"
label="音效时长选项"
placement="top-start"
style={buildPortalMenuStyle(
soundOptionsButtonRef.current,
'above',
)}
>
<div className="image-canvas-editor__option-popover-items image-canvas-editor__audio-type-options">
{SOUND_EFFECT_DURATION_OPTIONS.map((option) => (
<VideoOptionChoice
key={option}
selected={soundDuration === option}
disabled={isGenerating}
ariaLabel={`时长 ${option}秒`}
onClick={() => updateSoundDuration(option)}
>
{option}秒
</VideoOptionChoice>
))}
</div>
</PlatformFloatingMenu>,
)
: null}
</div>
</>
{isSoundOptionsOpen
? renderEditorPortal(
<PlatformFloatingMenu
className="image-canvas-editor__option-popover image-canvas-editor__option-popover--audio-options image-canvas-editor__portal-menu"
label="音效时长选项"
placement="top-start"
style={buildPortalMenuStyle(
soundOptionsButtonRef.current,
'above',
)}
>
<div className="image-canvas-editor__option-popover-items image-canvas-editor__audio-type-options">
{SOUND_EFFECT_DURATION_OPTIONS.map((option) => (
<VideoOptionChoice
key={option}
selected={soundDuration === option}
disabled={isGenerating}
ariaLabel={`时长 ${option}秒`}
onClick={() => updateSoundDuration(option)}
>
{option}秒
</VideoOptionChoice>
))}
</div>
</PlatformFloatingMenu>,
)
: null}
</div>
) : null}
<div className="image-canvas-editor__option-popover-anchor image-canvas-editor__option-popover-anchor--model image-canvas-editor__readonly-generation-option">
<PlatformInlineOptionButton
ref={soundModelButtonRef}
className="image-canvas-editor__option-cluster image-canvas-editor__option-cluster--model"
aria-label={fixedModelAriaLabel}
disabled
>
<span className="image-canvas-editor__model-trigger-label">
<span
className="image-canvas-editor__model-icon"
aria-hidden="true"
>
<Music />
</span>
<span>{fixedModelLabel}</span>
</span>
</PlatformInlineOptionButton>
</div>
<PlatformActionButton
type="submit"
tone="secondary"