diff --git a/src/components/image-editor/ImageCanvasBackgroundMusicPresetMarquee.tsx b/src/components/image-editor/ImageCanvasBackgroundMusicPresetMarquee.tsx
index ff4af6791..750b46669 100644
--- a/src/components/image-editor/ImageCanvasBackgroundMusicPresetMarquee.tsx
+++ b/src/components/image-editor/ImageCanvasBackgroundMusicPresetMarquee.tsx
@@ -1,6 +1,12 @@
-import { ChevronLeft, ChevronRight } from 'lucide-react';
+import {
+ ChevronDown,
+ ChevronLeft,
+ ChevronRight,
+ ChevronUp,
+} from 'lucide-react';
import {
type PointerEvent as ReactPointerEvent,
+ type ReactNode,
useCallback,
useEffect,
useId,
@@ -77,10 +83,13 @@ export function ImageCanvasBackgroundMusicPresetMarquee({
presets,
isLocked,
onSelectPreset,
+ headerTrailing,
}: {
presets: readonly BackgroundMusicPromptPreset[];
isLocked: boolean;
onSelectPreset: (preset: BackgroundMusicPromptPreset) => void;
+ /** 标题行右端的展示位。字数计数要和「预设」同排,但它属于 composer 的状态。 */
+ headerTrailing?: ReactNode;
}) {
const trackId = useId();
const [isExpanded, setIsExpanded] = useState(false);
@@ -325,13 +334,20 @@ export function ImageCanvasBackgroundMusicPresetMarquee({
size="xs"
shape="pill"
className="image-canvas-editor__background-music-presets-toggle"
+ // 图标化后文案不再进入可访问名,必须由 aria-label 承担。
+ aria-label={isExpanded ? '收起' : '展开'}
aria-expanded={isExpanded}
aria-controls={trackId}
disabled={isLocked}
onClick={() => setIsExpanded((expanded) => !expanded)}
>
- {isExpanded ? '收起' : '展开'}
+ {isExpanded ? (
+
+ ) : (
+
+ )}
+ {headerTrailing}
{isExpanded ? (
{
'.image-canvas-editor__generation-composer-footer',
);
expect(footer).toBeTruthy();
+ // 助手动作与模型、生成同处底部一行:左侧动作,右侧「Suno + 生成」。
expect(footer?.children[0]?.className).toContain(
- 'image-canvas-editor__option-popover-anchor--model',
+ 'image-canvas-editor__background-music-prompt-actions',
);
expect(footer?.children[1]?.className).toContain(
+ 'image-canvas-editor__option-popover-anchor--model',
+ );
+ expect(footer?.children[2]?.className).toContain(
'image-canvas-editor__generation-submit',
);
+ // 字数计数和「预设」同排,收起预设时也必须还在。
+ const presetsHeader = panel.querySelector(
+ '.image-canvas-editor__background-music-presets-header',
+ );
+ expect(
+ presetsHeader?.querySelector(
+ '.image-canvas-editor__background-music-prompt-count',
+ )?.textContent,
+ ).toBe('0 / 200');
const backgroundMusicModelButton = within(panel).getByRole('button', {
name: '背景音乐模型 Suno',
});
diff --git a/src/components/image-editor/ImageCanvasGenerationComposerView.tsx b/src/components/image-editor/ImageCanvasGenerationComposerView.tsx
index b13979dcd..2e900e3ac 100644
--- a/src/components/image-editor/ImageCanvasGenerationComposerView.tsx
+++ b/src/components/image-editor/ImageCanvasGenerationComposerView.tsx
@@ -1,4 +1,14 @@
-import { Check, ChevronDown, Cpu, Film, ImageIcon, Music } from 'lucide-react';
+import {
+ Check,
+ ChevronDown,
+ Cpu,
+ Film,
+ ImageIcon,
+ Music,
+ Sparkles,
+ Undo2,
+ WandSparkles,
+} from 'lucide-react';
import {
type CSSProperties,
type Dispatch,
@@ -19,7 +29,6 @@ import {
} from '../common/PlatformFloatingMenu';
import { PlatformInlineOptionButton } from '../common/PlatformInlineOptionButton';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
-import { PlatformTextField } from '../common/PlatformTextField';
import { ImageCanvasBackgroundMusicPresetMarquee } from './ImageCanvasBackgroundMusicPresetMarquee';
import {
appendBackgroundMusicPromptPreset,
@@ -908,84 +917,33 @@ function ImageCanvasAudioGenerationComposerView({
onSubmit(backgroundMusicDialog);
}}
>
-
updateBackgroundMusicPrompt(event.target.value)}
/>
-
-
- {`${promptCodePointCount} / ${BACKGROUND_MUSIC_PROMPT_MAX_CODE_POINTS}`}
-
-
{
- void backgroundMusicPromptAssist.completePrompt(
- backgroundMusicDialog.id,
- );
- }}
- >
- {assistState.status === 'completing' ? '补全中' : 'AI 补全'}
-
-
{
- void backgroundMusicPromptAssist.simplifyPrompt(
- backgroundMusicDialog.id,
- );
- }}
- >
- {assistState.status === 'simplifying' ? '简化中' : '一键简化'}
-
- {isUndoVisible ? (
-
{
- backgroundMusicPromptAssist.undoPrompt(
- backgroundMusicDialog.id,
- );
- }}
- >
- 撤销
-
- ) : null}
-
+ {`${promptCodePointCount} / ${BACKGROUND_MUSIC_PROMPT_MAX_CODE_POINTS}`}
+
+ }
/>
{assistState.errorMessage ? (
) : null}
+
+
{
+ void backgroundMusicPromptAssist.completePrompt(
+ backgroundMusicDialog.id,
+ );
+ }}
+ >
+
+
+
+
+ {assistState.status === 'completing' ? '补全中' : 'AI 补全'}
+
+
+
{
+ void backgroundMusicPromptAssist.simplifyPrompt(
+ backgroundMusicDialog.id,
+ );
+ }}
+ >
+
+
+
+
+ {assistState.status === 'simplifying' ? '简化中' : '一键简化'}
+
+
+ {isUndoVisible ? (
+
{
+ backgroundMusicPromptAssist.undoPrompt(
+ backgroundMusicDialog.id,
+ );
+ }}
+ >
+
+
+
+ 撤销
+
+ ) : null}
+