Add YuE2 to the music engine picker and generate API.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+41
-23
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
|
||||
<GenerationSettings :advanced="advanced">
|
||||
<label class="block space-y-2 text-sm"><span>Model</span><select :value="engineFamily" class="w-full rounded-xl border border-white/15 bg-zinc-900 p-3" @change="selectEngineFamily(($event.target as HTMLSelectElement).value as 'ace' | 'yue')"><option value="ace">ACE-Step</option><option value="yue">YuE</option></select></label>
|
||||
<label class="block space-y-2 text-sm"><span>Model</span><select :value="engineFamily" class="w-full rounded-xl border border-white/15 bg-zinc-900 p-3" @change="selectEngineFamily(($event.target as HTMLSelectElement).value as 'ace' | 'yue' | 'yue2')"><option value="ace">ACE-Step</option><option value="yue">YuE</option><option value="yue2">YuE2</option></select></label>
|
||||
<label v-if="engineFamily === 'yue'" class="block space-y-2 text-sm">
|
||||
<span>YuEGP profile</span>
|
||||
<select v-model.number="yueProfile" class="w-full rounded-xl border border-white/15 bg-zinc-900 p-3">
|
||||
@@ -34,8 +34,9 @@
|
||||
</select>
|
||||
<span class="block text-xs text-zinc-400">One lyric section. Compile off. Profile 3 is never selected automatically.</span>
|
||||
</label>
|
||||
<p v-else-if="engineFamily === 'yue2'" class="text-xs text-zinc-400">YuE2-3B · one lyric section · plan → synthesize → decode · no score editor</p>
|
||||
<div v-if="!advanced" class="space-y-4">
|
||||
<label v-if="engineFamily !== 'yue'" class="block text-sm">Steps<input v-model.number="steps" type="number" :min="stepsMin" :max="stepsMax" class="mt-2 w-full rounded-xl border border-white/15 bg-zinc-900 p-3"></label>
|
||||
<label v-if="engineFamily === 'ace'" class="block text-sm">Steps<input v-model.number="steps" type="number" :min="stepsMin" :max="stepsMax" class="mt-2 w-full rounded-xl border border-white/15 bg-zinc-900 p-3"></label>
|
||||
<label class="block text-sm">Seed<input v-model="seed" placeholder="random" class="mt-2 w-full rounded-xl border border-white/15 bg-zinc-900 p-3"></label>
|
||||
</div>
|
||||
<div v-if="engineFamily === 'ace'" class="flex items-center justify-between gap-3 rounded-2xl border border-white/10 bg-zinc-950/40 px-4 py-3 text-sm">
|
||||
@@ -73,7 +74,7 @@
|
||||
<div class="mb-1 flex flex-wrap items-center justify-between gap-2">
|
||||
<span class="text-sm font-medium text-zinc-300">Lyrics</span>
|
||||
<label class="flex cursor-pointer items-center gap-2 text-xs text-zinc-400">
|
||||
<input v-model="instrumental" :disabled="engineFamily === 'yue'" type="checkbox" class="accent-amber-400">
|
||||
<input v-model="instrumental" :disabled="engineFamily === 'yue' || engineFamily === 'yue2'" type="checkbox" class="accent-amber-400">
|
||||
Instrumental
|
||||
</label>
|
||||
</div>
|
||||
@@ -108,9 +109,9 @@
|
||||
:max="durationMax"
|
||||
step="5"
|
||||
>
|
||||
<span class="mt-1 block text-[11px] text-zinc-500">{{ engineFamily === 'yue' ? 'Approximate length · one lyric section' : `${durationMin}–${durationMax} seconds` }}</span>
|
||||
<span class="mt-1 block text-[11px] text-zinc-500">{{ engineFamily === 'yue' || engineFamily === 'yue2' ? 'Approximate length · one lyric section' : `${durationMin}–${durationMax} seconds` }}</span>
|
||||
</label>
|
||||
<label v-if="advanced && engineFamily !== 'yue'" class="block text-sm">
|
||||
<label v-if="advanced && engineFamily === 'ace'" class="block text-sm">
|
||||
<span class="mb-1 block font-medium text-zinc-300">Steps · {{ steps }}</span>
|
||||
<input
|
||||
v-model.number="steps"
|
||||
@@ -208,7 +209,7 @@
|
||||
</div>
|
||||
<p v-if="busy && !queued && jobId" class="text-xs text-zinc-400">
|
||||
{{ musicElapsed }} elapsed · {{ musicActivityText }}
|
||||
<span v-if="activeMusicEngine === 'yue' && !indeterminateMusic" class="mt-1 block">Progress is for the current stage.</span>
|
||||
<span v-if="(activeMusicEngine === 'yue' || activeMusicEngine === 'yue2') && !indeterminateMusic" class="mt-1 block">Progress is for the current stage.</span>
|
||||
<span v-if="indeterminateMusic" class="mt-1 block">Waiting for a progress report.</span>
|
||||
</p>
|
||||
<AudioPlayer
|
||||
@@ -258,7 +259,7 @@ const tagsPlaceholder = DEFAULT_MUSIC_TAGS
|
||||
const lyricsPlaceholder = DEFAULT_MUSIC_LYRICS
|
||||
const chips = MUSIC_STRUCTURE_CHIPS
|
||||
const durationMin = MUSIC_DURATION_MIN
|
||||
const durationMax = computed(() => engineFamily.value === 'yue' ? 150 : MUSIC_DURATION_MAX)
|
||||
const durationMax = computed(() => (engineFamily.value === 'yue' || engineFamily.value === 'yue2') ? 150 : MUSIC_DURATION_MAX)
|
||||
const stepsMin = MUSIC_STEPS_MIN
|
||||
const stepsMax = MUSIC_STEPS_MAX
|
||||
|
||||
@@ -275,7 +276,7 @@ const folderId = ref('')
|
||||
const folders = ref<LibraryFolder[]>([])
|
||||
const lyricsEl = ref<HTMLTextAreaElement | null>(null)
|
||||
const ace15 = ref(false)
|
||||
const engineFamily = ref<'ace' | 'yue'>('ace')
|
||||
const engineFamily = ref<'ace' | 'yue' | 'yue2'>('ace')
|
||||
const yueProfile = ref<1 | 3>(1)
|
||||
const busy = ref(false)
|
||||
const forceClearing = ref(false)
|
||||
@@ -287,7 +288,7 @@ const musicStartedAt = ref(0)
|
||||
const activeMusicEngine = ref('')
|
||||
const musicActivity = ref<{ checkedAt: number; running: boolean } | null>(null)
|
||||
let musicClockTimer: ReturnType<typeof setInterval> | null = null
|
||||
const indeterminateMusic = computed(() => !queued.value && activeMusicEngine.value === 'yue' && progress.value < 98 && !musicActivity.value?.running)
|
||||
const indeterminateMusic = computed(() => !queued.value && (activeMusicEngine.value === 'yue' || activeMusicEngine.value === 'yue2') && progress.value < 98 && !musicActivity.value?.running)
|
||||
const recentMusicCheck = computed(() => Boolean(musicActivity.value?.running && musicClock.value - musicActivity.value.checkedAt < 15000))
|
||||
const musicElapsed = computed(() => {
|
||||
const seconds = musicStartedAt.value ? Math.max(0, Math.floor((musicClock.value - musicStartedAt.value) / 1000)) : 0
|
||||
@@ -318,27 +319,29 @@ let recoverPoll: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
const blocked = computed(() => Boolean(blockReason.value))
|
||||
const headerSubtitle = computed(() => {
|
||||
if (engineFamily.value === 'yue2') return 'YuE2 · lyrics-to-song'
|
||||
if (engineFamily.value === 'yue') return 'YuE · lyrics-to-song'
|
||||
return ace15.value ? 'ACE-Step 1.5 · vocals' : 'ACE-Step · vocals'
|
||||
})
|
||||
const selectedEngine = computed<MusicEngine>(() => {
|
||||
if (engineFamily.value === 'yue2') return 'yue2'
|
||||
if (engineFamily.value === 'yue') return 'yue'
|
||||
return ace15.value ? 'ace-step-1.5' : 'ace-step'
|
||||
})
|
||||
const blockReason = computed(() => {
|
||||
if (!folderId.value) return 'Choose a library folder before generating.'
|
||||
if (engineFamily.value === 'yue' && instrumental.value) return 'YuEGP requires lyrics. Use ACE for instrumental music.'
|
||||
if (engineFamily.value === 'yue' && duration.value > 150) return 'YuEGP supports up to 150 seconds per section.'
|
||||
if ((engineFamily.value === 'yue' || engineFamily.value === 'yue2') && instrumental.value) return 'YuE requires lyrics. Use ACE for instrumental music.'
|
||||
if ((engineFamily.value === 'yue' || engineFamily.value === 'yue2') && duration.value > 150) return 'YuE supports up to 150 seconds per section.'
|
||||
if (!tags.value.trim()) return 'Add genre and style tags.'
|
||||
if (engineFamily.value === 'yue') { const problem = yueLyricsProblem(lyrics.value); if (problem) return problem }
|
||||
if (engineFamily.value === 'yue' || engineFamily.value === 'yue2') { const problem = yueLyricsProblem(lyrics.value); if (problem) return problem }
|
||||
if (!instrumental.value && !lyrics.value.trim()) return 'Write lyrics, or turn on Instrumental.'
|
||||
return ''
|
||||
})
|
||||
|
||||
function selectEngineFamily(family: 'ace' | 'yue') {
|
||||
function selectEngineFamily(family: 'ace' | 'yue' | 'yue2') {
|
||||
engineFamily.value = family
|
||||
if (family === 'yue') { instrumental.value = false; duration.value = Math.min(150, duration.value) }
|
||||
if (family === 'yue' && lyrics.value === DEFAULT_MUSIC_LYRICS) lyrics.value = '[Verse 1]\n'
|
||||
if (family === 'yue' || family === 'yue2') { instrumental.value = false; duration.value = Math.min(150, duration.value) }
|
||||
if ((family === 'yue' || family === 'yue2') && lyrics.value === DEFAULT_MUSIC_LYRICS) lyrics.value = '[Verse 1]\n'
|
||||
if (family === 'ace' && ace15.value && steps.value === MUSIC_STEPS_DEFAULT) {
|
||||
steps.value = MUSIC_STEPS_DEFAULT_15
|
||||
}
|
||||
@@ -352,6 +355,11 @@ function goStudio(kind: 'video' | 'image') {
|
||||
}
|
||||
|
||||
function applyEngine(engine: MusicEngine) {
|
||||
if (engine === 'yue2') {
|
||||
engineFamily.value = 'yue2'
|
||||
ace15.value = false
|
||||
return
|
||||
}
|
||||
if (engine === 'yue') {
|
||||
engineFamily.value = 'yue'
|
||||
ace15.value = false
|
||||
@@ -454,7 +462,9 @@ async function attachLiveMusic(opts: {
|
||||
else if (!status.value || /queueing|waiting for a generate|waiting in the job queue|waiting for gpu/i.test(status.value)) {
|
||||
status.value = selectedEngine.value === 'yue'
|
||||
? 'YuEGP is generating your song'
|
||||
: 'Generating…'
|
||||
: selectedEngine.value === 'yue2'
|
||||
? 'YuE2 is generating your song'
|
||||
: 'Generating…'
|
||||
}
|
||||
if (jobId.value !== liveId) {
|
||||
musicActivity.value = null
|
||||
@@ -499,7 +509,9 @@ async function refreshStudioQueue() {
|
||||
? 'Paused in the job queue'
|
||||
: (selectedEngine.value === 'yue'
|
||||
? 'Waiting for the GPU to run YuEGP'
|
||||
: 'Waiting in the job queue…')
|
||||
: selectedEngine.value === 'yue2'
|
||||
? 'Waiting for the GPU to run YuE2'
|
||||
: 'Waiting in the job queue…')
|
||||
}
|
||||
|
||||
if (!studioJobId.value) return
|
||||
@@ -511,7 +523,9 @@ async function refreshStudioQueue() {
|
||||
if (jobId.value && /waiting in the job queue/i.test(status.value)) {
|
||||
status.value = selectedEngine.value === 'yue'
|
||||
? 'YuEGP is generating your song'
|
||||
: 'Generating…'
|
||||
: selectedEngine.value === 'yue2'
|
||||
? 'YuE2 is generating your song'
|
||||
: 'Generating…'
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -522,7 +536,9 @@ async function refreshStudioQueue() {
|
||||
? 'Paused in the job queue'
|
||||
: (selectedEngine.value === 'yue'
|
||||
? 'Waiting for the GPU to run YuEGP'
|
||||
: 'Waiting in the job queue…')
|
||||
: selectedEngine.value === 'yue2'
|
||||
? 'Waiting for the GPU to run YuE2'
|
||||
: 'Waiting in the job queue…')
|
||||
return
|
||||
}
|
||||
if (row.status === 'running' && row.liveJobId) {
|
||||
@@ -667,9 +683,11 @@ async function generate() {
|
||||
studioJobId.value = ''
|
||||
status.value = engineFamily.value === 'yue'
|
||||
? 'Queueing YuE…'
|
||||
: ace15.value
|
||||
? 'Queueing ACE-Step 1.5…'
|
||||
: 'Queueing ACE-Step…'
|
||||
: engineFamily.value === 'yue2'
|
||||
? 'Queueing YuE2…'
|
||||
: ace15.value
|
||||
? 'Queueing ACE-Step 1.5…'
|
||||
: 'Queueing ACE-Step…'
|
||||
try {
|
||||
const started = await $fetch<{ jobId: string; studioJobId?: string; queued?: boolean; name?: string }>(
|
||||
'/api/generate/music',
|
||||
@@ -796,7 +814,7 @@ onMounted(async () => {
|
||||
await loadMusicPresets()
|
||||
const route = useRoute()
|
||||
const engineQ = String(route.query.engine || '').toLowerCase()
|
||||
if (engineQ === 'yue' || engineQ === 'ace') selectEngineFamily(engineQ)
|
||||
if (engineQ === 'yue' || engineQ === 'yue2' || engineQ === 'ace') selectEngineFamily(engineQ as 'ace' | 'yue' | 'yue2')
|
||||
applyIncomingMusic()
|
||||
await resumeActiveMusic()
|
||||
await refreshStudioQueue()
|
||||
|
||||
Reference in New Issue
Block a user