Move engine pickers into the Video / Image / Music cards.
Flux/Krea, MiniMax/LTX, and ACE/YuE sit on the top kind cards so you choose the engine before the prompt form.
This commit is contained in:
+149
-39
@@ -1,54 +1,164 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mb-4 grid grid-cols-3 gap-3">
|
<div class="mb-4 grid grid-cols-3 gap-3">
|
||||||
<button
|
<div
|
||||||
type="button"
|
class="overflow-hidden rounded-3xl border transition"
|
||||||
class="rounded-3xl border px-4 py-4 text-left transition"
|
:class="current === 'video' ? 'border-amber-300 bg-amber-400/10 text-amber-50' : 'border-white/10 text-zinc-400'"
|
||||||
:class="current === 'video' ? 'border-amber-300 bg-amber-400/10 text-amber-50' : 'border-white/10 text-zinc-400 hover:border-white/20 hover:text-zinc-200'"
|
|
||||||
@click="emit('select', 'video')"
|
|
||||||
>
|
>
|
||||||
<svg class="h-10 w-10" viewBox="0 0 40 40" fill="none" aria-hidden="true">
|
<button
|
||||||
<rect x="4" y="10" width="24" height="20" rx="3" stroke="currentColor" stroke-width="2" />
|
type="button"
|
||||||
<path d="M28 16 36 12v16l-8-4V16Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
class="w-full px-4 pb-3 pt-4 text-left transition"
|
||||||
<circle cx="12" cy="20" r="2" fill="currentColor" />
|
:class="current === 'video' ? '' : 'hover:text-zinc-200'"
|
||||||
</svg>
|
@click="emit('select', 'video')"
|
||||||
<span class="mt-3 block font-display text-lg font-bold text-zinc-100">Video</span>
|
>
|
||||||
<span class="mt-0.5 block text-xs text-zinc-500">MiniMax H3 · LTX</span>
|
<svg class="h-10 w-10" viewBox="0 0 40 40" fill="none" aria-hidden="true">
|
||||||
</button>
|
<rect x="4" y="10" width="24" height="20" rx="3" stroke="currentColor" stroke-width="2" />
|
||||||
<button
|
<path d="M28 16 36 12v16l-8-4V16Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
||||||
type="button"
|
<circle cx="12" cy="20" r="2" fill="currentColor" />
|
||||||
class="rounded-3xl border px-4 py-4 text-left transition"
|
</svg>
|
||||||
:class="current === 'image' ? 'border-amber-300 bg-amber-400/10 text-amber-50' : 'border-white/10 text-zinc-400 hover:border-white/20 hover:text-zinc-200'"
|
<span class="mt-3 block font-display text-lg font-bold text-zinc-100">Video</span>
|
||||||
@click="emit('select', 'image')"
|
</button>
|
||||||
|
<div v-if="ltxEnabled" class="grid grid-cols-2 border-t border-white/10">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="border-r border-white/10 px-3 py-2.5 text-left text-sm transition"
|
||||||
|
:class="engineBtnClass(current === 'video' && videoEngine === 'minimax')"
|
||||||
|
@click="pickVideo('minimax')"
|
||||||
|
>
|
||||||
|
<span class="block font-semibold">MiniMax</span>
|
||||||
|
<span class="text-xs opacity-80">H3</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="px-3 py-2.5 text-left text-sm transition"
|
||||||
|
:class="engineBtnClass(current === 'video' && videoEngine === 'ltx')"
|
||||||
|
@click="pickVideo('ltx')"
|
||||||
|
>
|
||||||
|
<span class="block font-semibold">LTX</span>
|
||||||
|
<span class="text-xs opacity-80">2.3</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p v-else class="border-t border-white/10 px-4 py-2 text-xs text-zinc-500">MiniMax H3</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="overflow-hidden rounded-3xl border transition"
|
||||||
|
:class="current === 'image' ? 'border-amber-300 bg-amber-400/10 text-amber-50' : 'border-white/10 text-zinc-400'"
|
||||||
>
|
>
|
||||||
<svg class="h-10 w-10" viewBox="0 0 40 40" fill="none" aria-hidden="true">
|
<button
|
||||||
<rect x="6" y="8" width="28" height="24" rx="3" stroke="currentColor" stroke-width="2" />
|
type="button"
|
||||||
<path d="m10 26 7-8 6 7 3-3 4 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
class="w-full px-4 pb-3 pt-4 text-left transition"
|
||||||
<circle cx="15" cy="15" r="2.5" fill="currentColor" />
|
:class="current === 'image' ? '' : 'hover:text-zinc-200'"
|
||||||
</svg>
|
@click="emit('select', 'image')"
|
||||||
<span class="mt-3 block font-display text-lg font-bold text-zinc-100">Image</span>
|
>
|
||||||
<span class="mt-0.5 block text-xs text-zinc-500">Flux · Krea</span>
|
<svg class="h-10 w-10" viewBox="0 0 40 40" fill="none" aria-hidden="true">
|
||||||
</button>
|
<rect x="6" y="8" width="28" height="24" rx="3" stroke="currentColor" stroke-width="2" />
|
||||||
<NuxtLink
|
<path d="m10 26 7-8 6 7 3-3 4 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||||
to="/music"
|
<circle cx="15" cy="15" r="2.5" fill="currentColor" />
|
||||||
class="rounded-3xl border px-4 py-4 text-left transition"
|
</svg>
|
||||||
:class="current === 'music' ? 'border-amber-300 bg-amber-400/10 text-amber-50' : 'border-white/10 text-zinc-400 hover:border-white/20 hover:text-zinc-200'"
|
<span class="mt-3 block font-display text-lg font-bold text-zinc-100">Image</span>
|
||||||
|
</button>
|
||||||
|
<div class="grid grid-cols-2 border-t border-white/10">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="border-r border-white/10 px-3 py-2.5 text-left text-sm transition"
|
||||||
|
:class="engineBtnClass(current === 'image' && imageEngine === 'flux')"
|
||||||
|
@click="pickImage('flux')"
|
||||||
|
>
|
||||||
|
<span class="block font-semibold">Flux</span>
|
||||||
|
<span class="text-xs opacity-80">Klein 9B</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="px-3 py-2.5 text-left text-sm transition"
|
||||||
|
:class="engineBtnClass(current === 'image' && imageEngine === 'krea')"
|
||||||
|
@click="pickImage('krea')"
|
||||||
|
>
|
||||||
|
<span class="block font-semibold">Krea</span>
|
||||||
|
<span class="text-xs opacity-80">2 Turbo</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="overflow-hidden rounded-3xl border transition"
|
||||||
|
:class="current === 'music' ? 'border-amber-300 bg-amber-400/10 text-amber-50' : 'border-white/10 text-zinc-400'"
|
||||||
>
|
>
|
||||||
<svg class="h-10 w-10" viewBox="0 0 40 40" fill="none" aria-hidden="true">
|
<NuxtLink
|
||||||
<circle cx="20" cy="20" r="12" stroke="currentColor" stroke-width="2" />
|
to="/music"
|
||||||
<path d="M18 14v12l10-4V12l-10 2Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
class="block w-full px-4 pb-3 pt-4 text-left transition"
|
||||||
</svg>
|
:class="current === 'music' ? '' : 'hover:text-zinc-200'"
|
||||||
<span class="mt-3 block font-display text-lg font-bold text-zinc-100">Music</span>
|
>
|
||||||
<span class="mt-0.5 block text-xs text-zinc-500">ACE-Step · YuE</span>
|
<svg class="h-10 w-10" viewBox="0 0 40 40" fill="none" aria-hidden="true">
|
||||||
</NuxtLink>
|
<circle cx="20" cy="20" r="12" stroke="currentColor" stroke-width="2" />
|
||||||
|
<path d="M18 14v12l10-4V12l-10 2Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
||||||
|
</svg>
|
||||||
|
<span class="mt-3 block font-display text-lg font-bold text-zinc-100">Music</span>
|
||||||
|
</NuxtLink>
|
||||||
|
<div class="grid grid-cols-2 border-t border-white/10">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="border-r border-white/10 px-3 py-2.5 text-left text-sm transition"
|
||||||
|
:class="engineBtnClass(current === 'music' && musicEngine === 'ace')"
|
||||||
|
@click="pickMusic('ace')"
|
||||||
|
>
|
||||||
|
<span class="block font-semibold">ACE</span>
|
||||||
|
<span class="text-xs opacity-80">Step</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="px-3 py-2.5 text-left text-sm transition"
|
||||||
|
:class="engineBtnClass(current === 'music' && musicEngine === 'yue')"
|
||||||
|
@click="pickMusic('yue')"
|
||||||
|
>
|
||||||
|
<span class="block font-semibold">YuE</span>
|
||||||
|
<span class="text-xs opacity-80">Lyrics</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
current: 'video' | 'image' | 'music'
|
current: 'video' | 'image' | 'music'
|
||||||
}>()
|
ltxEnabled?: boolean
|
||||||
|
videoEngine?: 'minimax' | 'ltx'
|
||||||
|
imageEngine?: 'flux' | 'krea'
|
||||||
|
musicEngine?: 'ace' | 'yue'
|
||||||
|
}>(), {
|
||||||
|
ltxEnabled: false,
|
||||||
|
videoEngine: 'minimax',
|
||||||
|
imageEngine: 'flux',
|
||||||
|
musicEngine: 'ace'
|
||||||
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
select: [kind: 'video' | 'image']
|
select: [kind: 'video' | 'image']
|
||||||
|
'video-engine': [engine: 'minimax' | 'ltx']
|
||||||
|
'image-engine': [engine: 'flux' | 'krea']
|
||||||
|
'music-engine': [engine: 'ace' | 'yue']
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
function engineBtnClass(active: boolean) {
|
||||||
|
if (active) return 'bg-amber-400/15 text-amber-100'
|
||||||
|
return 'text-zinc-400 hover:bg-white/5 hover:text-zinc-200'
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickVideo(engine: 'minimax' | 'ltx') {
|
||||||
|
emit('video-engine', engine)
|
||||||
|
if (props.current !== 'video') emit('select', 'video')
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickImage(engine: 'flux' | 'krea') {
|
||||||
|
emit('image-engine', engine)
|
||||||
|
if (props.current !== 'image') emit('select', 'image')
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickMusic(engine: 'ace' | 'yue') {
|
||||||
|
if (props.current === 'music') {
|
||||||
|
emit('music-engine', engine)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
void navigateTo({ path: '/music', query: { engine } })
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+40
-77
@@ -8,7 +8,15 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<main class="mx-auto min-w-0 max-w-7xl space-y-6 px-4 py-6 sm:px-6">
|
<main class="mx-auto min-w-0 max-w-7xl space-y-6 px-4 py-6 sm:px-6">
|
||||||
<StudioKindCards :current="imageStudio ? 'image' : 'video'" @select="selectStudioKind" />
|
<StudioKindCards
|
||||||
|
:current="imageStudio ? 'image' : 'video'"
|
||||||
|
:ltx-enabled="ltxEnabled"
|
||||||
|
:video-engine="videoEngine"
|
||||||
|
:image-engine="v2Engine"
|
||||||
|
@select="selectStudioKind"
|
||||||
|
@video-engine="selectVideoEngine"
|
||||||
|
@image-engine="setV2Engine"
|
||||||
|
/>
|
||||||
<div class="grid min-w-0 gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)] lg:items-stretch">
|
<div class="grid min-w-0 gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)] lg:items-stretch">
|
||||||
<section id="studio-input" class="glass min-w-0 max-w-full space-y-5 rounded-3xl p-5 sm:p-6">
|
<section id="studio-input" class="glass min-w-0 max-w-full space-y-5 rounded-3xl p-5 sm:p-6">
|
||||||
<div>
|
<div>
|
||||||
@@ -27,89 +35,44 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="overflow-hidden rounded-2xl border border-white/15 bg-zinc-950/50"
|
v-if="!(studioMode === 'editv2' && v2Mode === 'generate')"
|
||||||
|
class="relative cursor-pointer rounded-2xl border border-dashed border-white/15 bg-zinc-950/50 p-4 transition hover:border-amber-300/50"
|
||||||
:class="{ 'border-amber-300/70 bg-amber-400/5': dragging }"
|
:class="{ 'border-amber-300/70 bg-amber-400/5': dragging }"
|
||||||
|
@dragover.prevent="dragging = true"
|
||||||
|
@dragleave.prevent="dragging = false"
|
||||||
|
@drop.prevent="onDrop"
|
||||||
|
@click="openPicker('main')"
|
||||||
>
|
>
|
||||||
<div
|
<div v-if="studioMode === 'editv2' && v2Mode === 'refine' && preview" class="relative" @click.stop>
|
||||||
v-if="!(studioMode === 'editv2' && v2Mode === 'generate')"
|
<ImageV2MaskPainter
|
||||||
class="relative cursor-pointer border-dashed p-4 transition hover:border-amber-300/50"
|
ref="refinePainter"
|
||||||
:class="studioMode === 'editv2' || (studioMode === 'video' && ltxEnabled) ? 'border-b border-white/10' : ''"
|
:src="preview"
|
||||||
@dragover.prevent="dragging = true"
|
:hide-preview="hideInputPreview"
|
||||||
@dragleave.prevent="dragging = false"
|
@painted="refineMaskDirty = $event"
|
||||||
@drop.prevent="onDrop"
|
/>
|
||||||
@click="openPicker('main')"
|
<button type="button" class="absolute right-3 top-3 z-10 rounded-full bg-zinc-950/80 px-3 py-1 text-xs" @click.stop="resetImage">Reset</button>
|
||||||
>
|
|
||||||
<div v-if="studioMode === 'editv2' && v2Mode === 'refine' && preview" class="relative" @click.stop>
|
|
||||||
<ImageV2MaskPainter
|
|
||||||
ref="refinePainter"
|
|
||||||
:src="preview"
|
|
||||||
:hide-preview="hideInputPreview"
|
|
||||||
@painted="refineMaskDirty = $event"
|
|
||||||
/>
|
|
||||||
<button type="button" class="absolute right-3 top-3 z-10 rounded-full bg-zinc-950/80 px-3 py-1 text-xs" @click.stop="resetImage">Reset</button>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="preview && !hideInputPreview" class="relative">
|
|
||||||
<img :src="preview" alt="Source still" class="max-h-72 w-full rounded-xl object-contain bg-black/40">
|
|
||||||
<button type="button" class="absolute right-3 top-3 rounded-full bg-zinc-950/80 px-3 py-1 text-xs" @click.stop="resetImage">Reset</button>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="preview && hideInputPreview" class="relative flex min-h-44 flex-col items-center justify-center text-center">
|
|
||||||
<span class="sensitive-glow absolute inset-3 rounded-xl" />
|
|
||||||
<p class="relative font-medium">Still loaded · preview hidden</p>
|
|
||||||
<p class="relative mt-1 text-sm text-zinc-500">Toggle Hide input still off to show it</p>
|
|
||||||
<button type="button" class="relative mt-3 rounded-full bg-zinc-950/80 px-3 py-1 text-xs" @click.stop="resetImage">Reset</button>
|
|
||||||
</div>
|
|
||||||
<div v-else class="flex min-h-44 flex-col items-center justify-center text-center">
|
|
||||||
<p class="font-medium">{{ studioMode === 'edit' || studioMode === 'editv2' ? (studioMode === 'editv2' ? (v2Mode === 'refine' ? 'Still A · canvas' : 'Still A') : 'Image 1') : (textToVideo ? 'Start still · optional' : 'Choose an image') }}</p>
|
|
||||||
<p class="mt-1 text-sm text-zinc-500">{{ textToVideo ? 'Text-to-video does not need a still. Shot 2+ will still extend from the last frame.' : 'Browse the library or upload a new PNG, JPG, or WEBP' }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-else-if="preview && !hideInputPreview" class="relative">
|
||||||
v-else-if="studioMode === 'editv2' && v2Mode === 'generate' && (file || inputStillId)"
|
<img :src="preview" alt="Source still" class="max-h-72 w-full rounded-xl object-contain bg-black/40">
|
||||||
class="border-b border-white/10 px-4 py-3 text-sm text-zinc-400"
|
<button type="button" class="absolute right-3 top-3 rounded-full bg-zinc-950/80 px-3 py-1 text-xs" @click.stop="resetImage">Reset</button>
|
||||||
>
|
|
||||||
Still A is loaded for Edit / Compose / Refine. Generate ignores it and uses the prompt only.
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="studioMode === 'editv2'" class="grid grid-cols-2">
|
<div v-else-if="preview && hideInputPreview" class="relative flex min-h-44 flex-col items-center justify-center text-center">
|
||||||
<button
|
<span class="sensitive-glow absolute inset-3 rounded-xl" />
|
||||||
type="button"
|
<p class="relative font-medium">Still loaded · preview hidden</p>
|
||||||
class="border-r border-white/10 px-3 py-2.5 text-left text-sm transition"
|
<p class="relative mt-1 text-sm text-zinc-500">Toggle Hide input still off to show it</p>
|
||||||
:class="v2Engine === 'flux' ? 'bg-amber-400/10 text-amber-100' : 'text-zinc-400 hover:bg-white/5 hover:text-zinc-200'"
|
<button type="button" class="relative mt-3 rounded-full bg-zinc-950/80 px-3 py-1 text-xs" @click.stop="resetImage">Reset</button>
|
||||||
@click="setV2Engine('flux')"
|
|
||||||
>
|
|
||||||
<span class="block font-semibold">Flux</span>
|
|
||||||
<span class="text-xs opacity-80">Klein 9B</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="px-3 py-2.5 text-left text-sm transition"
|
|
||||||
:class="v2Engine === 'krea' ? 'bg-amber-400/10 text-amber-100' : 'text-zinc-400 hover:bg-white/5 hover:text-zinc-200'"
|
|
||||||
@click="setV2Engine('krea')"
|
|
||||||
>
|
|
||||||
<span class="block font-semibold">Krea</span>
|
|
||||||
<span class="text-xs opacity-80">Krea 2 Turbo</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="studioMode === 'video' && ltxEnabled" class="grid grid-cols-2">
|
<div v-else class="flex min-h-44 flex-col items-center justify-center text-center">
|
||||||
<button
|
<p class="font-medium">{{ studioMode === 'edit' || studioMode === 'editv2' ? (studioMode === 'editv2' ? (v2Mode === 'refine' ? 'Still A · canvas' : 'Still A') : 'Image 1') : (textToVideo ? 'Start still · optional' : 'Choose an image') }}</p>
|
||||||
type="button"
|
<p class="mt-1 text-sm text-zinc-500">{{ textToVideo ? 'Text-to-video does not need a still. Shot 2+ will still extend from the last frame.' : 'Browse the library or upload a new PNG, JPG, or WEBP' }}</p>
|
||||||
class="border-r border-white/10 px-3 py-2.5 text-left text-sm transition"
|
|
||||||
:class="videoEngine === 'minimax' ? 'bg-amber-400/10 text-amber-100' : 'text-zinc-400 hover:bg-white/5 hover:text-zinc-200'"
|
|
||||||
@click="selectVideoEngine('minimax')"
|
|
||||||
>
|
|
||||||
<span class="block font-semibold">MiniMax H3</span>
|
|
||||||
<span class="text-xs opacity-80">Stereo · identity refs</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="px-3 py-2.5 text-left text-sm transition"
|
|
||||||
:class="videoEngine === 'ltx' ? 'bg-amber-400/10 text-amber-100' : 'text-zinc-400 hover:bg-white/5 hover:text-zinc-200'"
|
|
||||||
@click="selectVideoEngine('ltx')"
|
|
||||||
>
|
|
||||||
<span class="block font-semibold">LTX-2.3</span>
|
|
||||||
<span class="text-xs opacity-80">PinkCherry</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="studioMode === 'editv2' && v2Mode === 'generate' && (file || inputStillId)"
|
||||||
|
class="rounded-2xl border border-white/10 bg-zinc-950/40 px-4 py-3 text-sm text-zinc-400"
|
||||||
|
>
|
||||||
|
Still A is loaded for Edit / Compose / Refine. Generate ignores it and uses the prompt only.
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="file || inputStillId" class="mt-2 flex min-w-0 items-center gap-2">
|
<div v-if="file || inputStillId" class="mt-2 flex min-w-0 items-center gap-2">
|
||||||
<input
|
<input
|
||||||
|
|||||||
+15
-35
@@ -7,42 +7,19 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<main class="mx-auto min-w-0 max-w-7xl space-y-6 px-4 py-6 sm:px-6">
|
<main class="mx-auto min-w-0 max-w-7xl space-y-6 px-4 py-6 sm:px-6">
|
||||||
<StudioKindCards current="music" @select="goStudio" />
|
<StudioKindCards
|
||||||
|
current="music"
|
||||||
|
:music-engine="engineFamily"
|
||||||
|
@select="goStudio"
|
||||||
|
@music-engine="selectEngineFamily"
|
||||||
|
/>
|
||||||
<div class="grid min-w-0 gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)] lg:items-stretch">
|
<div class="grid min-w-0 gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)] lg:items-stretch">
|
||||||
<section class="glass min-w-0 max-w-full space-y-5 rounded-3xl p-5 sm:p-6">
|
<section class="glass min-w-0 max-w-full space-y-5 rounded-3xl p-5 sm:p-6">
|
||||||
<div class="overflow-hidden rounded-2xl border border-white/15 bg-zinc-950/50">
|
<div>
|
||||||
<div class="border-b border-white/10 px-4 py-3">
|
<h2 class="font-display text-2xl font-bold">Music Generator</h2>
|
||||||
<h2 class="font-display text-2xl font-bold">Music Generator</h2>
|
<p class="mt-1 text-sm text-zinc-400">
|
||||||
<p class="mt-1 text-sm text-zinc-400">
|
Tags describe style and voice. Lyrics use structure chips. Instrumental skips the vocal line.
|
||||||
Tags describe style and voice. Lyrics use structure chips. Instrumental skips the vocal line.
|
</p>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="border-r border-white/10 px-3 py-2.5 text-left text-sm transition"
|
|
||||||
:class="engineFamily === 'ace' ? 'bg-amber-400/10 text-amber-100' : 'text-zinc-400 hover:bg-white/5 hover:text-zinc-200'"
|
|
||||||
@click="selectEngineFamily('ace')"
|
|
||||||
>
|
|
||||||
<span class="block font-semibold">ACE-Step</span>
|
|
||||||
<span class="text-xs opacity-80">Fast vocals</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="px-3 py-2.5 text-left text-sm transition"
|
|
||||||
:class="engineFamily === 'yue' ? 'bg-amber-400/10 text-amber-100' : 'text-zinc-400 hover:bg-white/5 hover:text-zinc-200'"
|
|
||||||
@click="selectEngineFamily('yue')"
|
|
||||||
>
|
|
||||||
<span class="flex items-center gap-2">
|
|
||||||
<span class="font-semibold">YuE</span>
|
|
||||||
<InfoTip
|
|
||||||
aria-label="What YuE does"
|
|
||||||
text="YuE is a separate lyrics-to-song engine (stage-1 7B + stage-2 1B + xcodec). It needs the ComfyUI_YuE custom node on Beast and is slower and heavier than ACE-Step. Use it when you want a full sung track from lyrics rather than ACE-Step’s tag-conditioned sampler."
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span class="text-xs opacity-80">Lyrics-to-song</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</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">
|
<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">
|
||||||
@@ -746,12 +723,15 @@ function applyPreset() {
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadLibrary()
|
await loadLibrary()
|
||||||
await loadMusicPresets()
|
await loadMusicPresets()
|
||||||
|
const route = useRoute()
|
||||||
|
const engineQ = String(route.query.engine || '').toLowerCase()
|
||||||
|
if (engineQ === 'yue' || engineQ === 'ace') selectEngineFamily(engineQ)
|
||||||
applyIncomingMusic()
|
applyIncomingMusic()
|
||||||
await resumeActiveMusic()
|
await resumeActiveMusic()
|
||||||
await refreshStudioQueue()
|
await refreshStudioQueue()
|
||||||
stopQueuePoll()
|
stopQueuePoll()
|
||||||
queuePoll = setInterval(() => { void refreshStudioQueue() }, 8000)
|
queuePoll = setInterval(() => { void refreshStudioQueue() }, 8000)
|
||||||
const query = useRoute().query
|
const query = route.query
|
||||||
if (typeof query.track === 'string' && query.track) {
|
if (typeof query.track === 'string' && query.track) {
|
||||||
audioUrl.value = `/api/library/tracks/${query.track}/audio`
|
audioUrl.value = `/api/library/tracks/${query.track}/audio`
|
||||||
trackId.value = query.track
|
trackId.value = query.track
|
||||||
|
|||||||
Reference in New Issue
Block a user