Replace Comfy YuE with standalone YuEGP profile 1

This commit is contained in:
Towsty
2026-09-07 20:24:47 -05:00
parent eeff12cdab
commit c2b6032e49
33 changed files with 976 additions and 294 deletions
+36 -15
View File
@@ -26,6 +26,14 @@
<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 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">
<option :value="1">1 · Full quality · 16GB (default)</option>
<option :value="3">3 · Lower memory · Manual fallback</option>
</select>
<span class="block text-xs text-zinc-400">One lyric section. Compile off. Profile 3 is never selected automatically.</span>
</label>
<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 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>
@@ -65,7 +73,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" type="checkbox" class="accent-amber-400">
<input v-model="instrumental" :disabled="engineFamily === 'yue'" type="checkbox" class="accent-amber-400">
Instrumental
</label>
</div>
@@ -100,7 +108,7 @@
:max="durationMax"
step="5"
>
<span class="mt-1 block text-[11px] text-zinc-500">{{ engineFamily === 'yue' ? 'YuE uses this as stage-A length' : `${durationMin}–${durationMax} seconds` }}</span>
<span class="mt-1 block text-[11px] text-zinc-500">{{ engineFamily === 'yue' ? 'Approximate length · one lyric section' : `${durationMin}–${durationMax} seconds` }}</span>
</label>
<label v-if="advanced && engineFamily !== 'yue'" class="block text-sm">
<span class="mb-1 block font-medium text-zinc-300">Steps · {{ steps }}</span>
@@ -200,7 +208,8 @@
</div>
<p v-if="busy && !queued && jobId" class="text-xs text-zinc-400">
{{ musicElapsed }} elapsed · {{ musicActivityText }}
<span v-if="indeterminateMusic" class="mt-1 block">YuE does not report a completion percentage during this stage.</span>
<span v-if="activeMusicEngine === 'yue' && !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
v-if="audioUrl"
@@ -237,7 +246,8 @@ import {
MUSIC_STEPS_MIN,
MUSIC_STRUCTURE_CHIPS,
parseMusicEngine,
type MusicEngine
type MusicEngine,
yueLyricsProblem,
} from '~/utils/music'
import { ensureGenerationNotifyPermission, notifyGenerationComplete } from '~/utils/generationNotify'
import type { LibraryFolder } from '~/utils/libraryBrowse'
@@ -248,7 +258,7 @@ const tagsPlaceholder = DEFAULT_MUSIC_TAGS
const lyricsPlaceholder = DEFAULT_MUSIC_LYRICS
const chips = MUSIC_STRUCTURE_CHIPS
const durationMin = MUSIC_DURATION_MIN
const durationMax = MUSIC_DURATION_MAX
const durationMax = computed(() => engineFamily.value === 'yue' ? 150 : MUSIC_DURATION_MAX)
const stepsMin = MUSIC_STEPS_MIN
const stepsMax = MUSIC_STEPS_MAX
@@ -266,6 +276,7 @@ const folders = ref<LibraryFolder[]>([])
const lyricsEl = ref<HTMLTextAreaElement | null>(null)
const ace15 = ref(false)
const engineFamily = ref<'ace' | 'yue'>('ace')
const yueProfile = ref<1 | 3>(1)
const busy = ref(false)
const forceClearing = ref(false)
const error = ref('')
@@ -276,17 +287,17 @@ 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)
const indeterminateMusic = computed(() => !queued.value && activeMusicEngine.value === 'yue' && 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
return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`
})
const musicActivityText = computed(() => {
if (!musicActivity.value) return 'Waiting for a Comfy activity check…'
if (!musicActivity.value) return 'Waiting for a generator activity check…'
const age = Math.max(0, Math.floor((musicClock.value - musicActivity.value.checkedAt) / 1000))
if (age >= 15) return `Activity check delayed · last response ${age}s ago`
return musicActivity.value.running ? `Comfy confirms this job is running · checked ${age}s ago` : 'Comfy responded · checking for the result…'
return musicActivity.value.running ? `Generator confirms this job is running · checked ${age}s ago` : 'Generator responded · checking for the result…'
})
const audioUrl = ref('')
const trackId = ref('')
@@ -305,7 +316,7 @@ let settled = false
let queuePoll: ReturnType<typeof setInterval> | null = null
let recoverPoll: ReturnType<typeof setInterval> | null = null
const blocked = computed(() => !tags.value.trim() || (!instrumental.value && !lyrics.value.trim()) || !folderId.value)
const blocked = computed(() => Boolean(blockReason.value))
const headerSubtitle = computed(() => {
if (engineFamily.value === 'yue') return 'YuE · lyrics-to-song'
return ace15.value ? 'ACE-Step 1.5 · vocals' : 'ACE-Step · vocals'
@@ -316,13 +327,18 @@ const selectedEngine = computed<MusicEngine>(() => {
})
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 (!tags.value.trim()) return 'Add genre and style tags.'
if (engineFamily.value === 'yue') { 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') {
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 === 'ace' && ace15.value && steps.value === MUSIC_STEPS_DEFAULT) {
steps.value = MUSIC_STEPS_DEFAULT_15
}
@@ -437,7 +453,7 @@ async function attachLiveMusic(opts: {
if (opts.message) status.value = opts.message
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'
? 'YuE running on Comfy — Stage A can take 10–20+ minutes'
? 'YuEGP is generating your song'
: 'Generating…'
}
if (jobId.value !== liveId) {
@@ -482,7 +498,7 @@ async function refreshStudioQueue() {
status.value = musicRow.status === 'held'
? 'Paused in the job queue'
: (selectedEngine.value === 'yue'
? 'Waiting for GPU… YuE is heavy; do not spam Generate'
? 'Waiting for the GPU to run YuEGP'
: 'Waiting in the job queue…')
}
@@ -494,7 +510,7 @@ async function refreshStudioQueue() {
}
if (jobId.value && /waiting in the job queue/i.test(status.value)) {
status.value = selectedEngine.value === 'yue'
? 'YuE running on Comfy — Stage A can take 10–20+ minutes'
? 'YuEGP is generating your song'
: 'Generating…'
}
return
@@ -505,7 +521,7 @@ async function refreshStudioQueue() {
status.value = row.status === 'held'
? 'Paused in the job queue'
: (selectedEngine.value === 'yue'
? 'Waiting for GPU… YuE is heavy; do not spam Generate'
? 'Waiting for the GPU to run YuEGP'
: 'Waiting in the job queue…')
return
}
@@ -553,7 +569,7 @@ function applyEvent(payload: Record<string, any>) {
if (payload.trackId) {
trackId.value = payload.trackId
audioUrl.value = `/api/library/tracks/${payload.trackId}/audio`
downloadName.value = `${(name.value || 'track').replace(/[^\w.-]+/g, '-')}.flac`
downloadName.value = `${(name.value || 'track').replace(/[^\w.-]+/g, '-')}.${payload.audioExt === 'wav' ? 'wav' : 'flac'}`
} else if (payload.filename) {
const params = new URLSearchParams({
filename: payload.filename,
@@ -669,7 +685,8 @@ async function generate() {
instrumental: instrumental.value,
folderId: folderId.value,
name: name.value.trim(),
engine: selectedEngine.value
engine: selectedEngine.value,
...(engineFamily.value === 'yue' ? { yueProfile: yueProfile.value } : {})
}
}
)
@@ -715,6 +732,7 @@ function applyIncomingMusic() {
if (payload.seed != null) seed.value = String(payload.seed)
if (typeof payload.name === 'string') name.value = payload.name
if (payload.folderId && folders.value.some(folder => folder.id === payload.folderId)) folderId.value = payload.folderId
if (payload.yueProfile === 1 || payload.yueProfile === 3) yueProfile.value = payload.yueProfile
if (payload.musicEngine || payload.engine) applyEngine(parseMusicEngine(payload.musicEngine || payload.engine))
status.value = 'Loaded from generation log'
return
@@ -736,6 +754,7 @@ function applyIncomingMusic() {
seed?: string
folderId?: string
engine?: string
yueProfile?: 1 | 3
}
if (typeof loaded.tags === 'string') tags.value = loaded.tags
if (typeof loaded.lyrics === 'string') lyrics.value = loaded.lyrics
@@ -746,6 +765,7 @@ function applyIncomingMusic() {
if (typeof loaded.name === 'string') name.value = loaded.name
if (loaded.folderId && folders.value.some(folder => folder.id === loaded.folderId)) folderId.value = loaded.folderId
if (loaded.engine) applyEngine(parseMusicEngine(loaded.engine))
yueProfile.value = loaded.yueProfile === 3 ? 3 : 1
status.value = 'Loaded from the queue'
} catch { /* ignore */ }
}
@@ -766,6 +786,7 @@ function applyPreset() {
if (typeof settings.steps === 'number') steps.value = settings.steps
if (typeof settings.seed === 'number') seed.value = String(settings.seed)
if (settings.engine) applyEngine(settings.engine)
yueProfile.value = settings.yueProfile === 3 ? 3 : 1
name.value = preset.name
}