Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/services/studio_music_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,8 @@ def _check_lora_names(cls, values):
@model_validator(mode="after")
def _check_semantics(self):
_non_blank(self.prompt, "input.params.prompt")
_non_blank(self.alt_prompt, "input.params.alt_prompt")
# Caption/style is optional. ACE-Step accepts an empty alt_prompt;
# MiniMax-Music3 still rejects a blank caption in its handler.
_non_blank(self.model_type, "input.params.model_type")
if self.model_type not in STUDIO_MUSIC_MODEL_TYPES:
raise ValueError("input.params.model_type is not a registered local music model")
Expand Down
13 changes: 13 additions & 0 deletions tests/test_studio_music_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,19 @@ def test_instrumental_marker_remains_literal_and_is_not_rewritten():
assert frozen["effective"]["input"]["params"]["alt_prompt"] == command["input"]["params"]["alt_prompt"]


def test_empty_or_omitted_alt_prompt_is_admitted():
blank = music_command()
blank["input"]["params"]["alt_prompt"] = ""
frozen_blank = freeze_studio_music_spec(blank)
assert frozen_blank["effective"]["input"]["params"]["alt_prompt"] == ""

omitted = music_command()
omitted["input"]["params"].pop("alt_prompt")
frozen_omitted = freeze_studio_music_spec(omitted)
assert "alt_prompt" not in frozen_omitted["original"]["input"]["params"]
assert frozen_omitted["effective"]["input"]["params"].get("alt_prompt", "") == ""


def test_fingerprint_excludes_intent_but_covers_workspace_collection_and_content():
first = freeze_studio_music_spec(music_command("first"))
second = freeze_studio_music_spec(music_command("second"))
Expand Down
4 changes: 3 additions & 1 deletion ui/src/features/studio/musicGenerationSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,9 @@ function assertMusicParams(value: unknown): asserts value is StudioMusicParams {
}
assertCatalogValue(value, paramsSchema, 'input.params')
requiredText(value.prompt, 'input.params.prompt', MAX_PROMPT_LENGTH)
requiredText(value.alt_prompt, 'input.params.alt_prompt', MAX_PROMPT_LENGTH)
// alt_prompt is optional in the generated catalog (default ""). Requiring a
// non-blank caption blocked Studio Generate and Wizard start_generation
// when the user wrote lyrics or [Instrumental] without filling Style.
requiredText(value.model_type, 'input.params.model_type', MAX_WORKSPACE_LENGTH)
assertMusicSelectors(value)
assertAudioReferences(value)
Expand Down
14 changes: 14 additions & 0 deletions ui/tests/musicGenerationCommands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,20 @@ test('music builder retains inactive sentinels but rejects active modes, TTS and
)
})

test('music builder admits empty or omitted style captions', { concurrency: false }, () => {
const blank = command('music-blank-caption', { alt_prompt: '' })
assert.equal(blank.input.params.alt_prompt, '')
assert.equal(blank.input.params.prompt, nativeParams().prompt)

const { alt_prompt: _omitted, ...withoutCaption } = nativeParams()
const omitted = createStudioMusicGenerationCommand({
...withoutCaption,
workspace: 'music-workspace',
}, 'music-omitted-caption')
assert.equal('alt_prompt' in omitted.input.params, false)
assert.equal(omitted.input.params.prompt, withoutCaption.prompt)
})

test('form projection preserves music text, language, refs and sentinels while rejecting active stale controls', { concurrency: false }, () => {
const source = {
...nativeParams(),
Expand Down