Stop Use as input from trapping you on the Image tab.
Loading a library still as input now only fills the start-still slot and stays on Video. Video/Image cards switch mode directly instead of relying on fragile query links. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mb-4 grid grid-cols-3 gap-3">
|
<div class="mb-4 grid grid-cols-3 gap-3">
|
||||||
<NuxtLink
|
<button
|
||||||
to="/?studio=video"
|
type="button"
|
||||||
class="rounded-3xl border px-4 py-4 text-left 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 hover:border-white/20 hover:text-zinc-200'"
|
: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">
|
<svg class="h-10 w-10" viewBox="0 0 40 40" fill="none" aria-hidden="true">
|
||||||
<rect x="4" y="10" width="24" height="20" rx="3" stroke="currentColor" stroke-width="2" />
|
<rect x="4" y="10" width="24" height="20" rx="3" stroke="currentColor" stroke-width="2" />
|
||||||
@@ -12,11 +13,12 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<span class="mt-3 block font-display text-lg font-bold text-zinc-100">Video</span>
|
<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>
|
<span class="mt-0.5 block text-xs text-zinc-500">MiniMax H3 · LTX</span>
|
||||||
</NuxtLink>
|
</button>
|
||||||
<NuxtLink
|
<button
|
||||||
to="/?studio=image"
|
type="button"
|
||||||
class="rounded-3xl border px-4 py-4 text-left transition"
|
class="rounded-3xl border px-4 py-4 text-left transition"
|
||||||
: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'"
|
: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'"
|
||||||
|
@click="emit('select', 'image')"
|
||||||
>
|
>
|
||||||
<svg class="h-10 w-10" viewBox="0 0 40 40" fill="none" aria-hidden="true">
|
<svg class="h-10 w-10" viewBox="0 0 40 40" fill="none" aria-hidden="true">
|
||||||
<rect x="6" y="8" width="28" height="24" rx="3" stroke="currentColor" stroke-width="2" />
|
<rect x="6" y="8" width="28" height="24" rx="3" stroke="currentColor" stroke-width="2" />
|
||||||
@@ -25,7 +27,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<span class="mt-3 block font-display text-lg font-bold text-zinc-100">Image</span>
|
<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>
|
<span class="mt-0.5 block text-xs text-zinc-500">Flux · Krea</span>
|
||||||
</NuxtLink>
|
</button>
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/music"
|
to="/music"
|
||||||
class="rounded-3xl border px-4 py-4 text-left transition"
|
class="rounded-3xl border px-4 py-4 text-left transition"
|
||||||
@@ -45,4 +47,8 @@
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
current: 'video' | 'image' | 'music'
|
current: 'video' | 'image' | 'music'
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
select: [kind: 'video' | 'image']
|
||||||
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+41
-44
@@ -8,7 +8,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<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'" />
|
<StudioKindCards :current="imageStudio ? 'image' : 'video'" @select="selectStudioKind" />
|
||||||
<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>
|
||||||
@@ -2594,11 +2594,15 @@ function selectStudioKind(kind: 'video' | 'image') {
|
|||||||
if (kind === 'video') {
|
if (kind === 'video') {
|
||||||
studioMode.value = 'video'
|
studioMode.value = 'video'
|
||||||
outputFocus.value = 'video'
|
outputFocus.value = 'video'
|
||||||
return
|
} else {
|
||||||
}
|
|
||||||
studioMode.value = 'editv2'
|
studioMode.value = 'editv2'
|
||||||
outputFocus.value = 'edit'
|
outputFocus.value = 'edit'
|
||||||
}
|
}
|
||||||
|
const studio = kind === 'video' ? 'video' : 'image'
|
||||||
|
if (String(route.query.studio || '') !== studio) {
|
||||||
|
void router.replace({ path: '/', query: { ...route.query, studio } })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function applyStudioQueryFromRoute() {
|
function applyStudioQueryFromRoute() {
|
||||||
const studio = String(route.query.studio || '').toLowerCase()
|
const studio = String(route.query.studio || '').toLowerCase()
|
||||||
@@ -3974,7 +3978,7 @@ onMounted(async () => {
|
|||||||
await refreshStudioQueue()
|
await refreshStudioQueue()
|
||||||
await refreshLiveQueue()
|
await refreshLiveQueue()
|
||||||
await loadPromptPresets()
|
await loadPromptPresets()
|
||||||
if (route.query.clip || route.query.still) applyIncomingLibraryItem()
|
if (route.query.clip || route.query.still) await applyIncomingLibraryItem()
|
||||||
else applyIncomingDirectorScript()
|
else applyIncomingDirectorScript()
|
||||||
await applyIncomingQueuedJob()
|
await applyIncomingQueuedJob()
|
||||||
applyStudioQueryFromRoute()
|
applyStudioQueryFromRoute()
|
||||||
@@ -4386,21 +4390,28 @@ async function deleteDirectorScript() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyIncomingLibraryItem() {
|
async function applyIncomingLibraryItem() {
|
||||||
const clipId = String(route.query.clip || '').trim()
|
const clipId = String(route.query.clip || '').trim()
|
||||||
const stillId = String(route.query.still || '').trim()
|
const stillId = String(route.query.still || '').trim()
|
||||||
const use = String(route.query.use || '') === '1'
|
const use = String(route.query.use || '') === '1'
|
||||||
|
const studio = String(route.query.studio || '').toLowerCase()
|
||||||
if (clipId) {
|
if (clipId) {
|
||||||
const clip = clips.value.find(item => item.id === clipId)
|
const clip = clips.value.find(item => item.id === clipId)
|
||||||
if (clip) {
|
if (clip) {
|
||||||
if (use) void useClipAsInput(clip)
|
if (use) await useClipAsInput(clip)
|
||||||
else openClip(clip)
|
else openClip(clip)
|
||||||
}
|
}
|
||||||
} else if (stillId) {
|
} else if (stillId) {
|
||||||
const still = stills.value.find(item => item.id === stillId)
|
const still = stills.value.find(item => item.id === stillId)
|
||||||
if (still) {
|
if (still) {
|
||||||
if (use) void useStillAsInput(still)
|
if (use) {
|
||||||
else openStill(still)
|
await useStillAsInput(still)
|
||||||
|
// Prefer Video for "use as input" unless the link explicitly asked for Image.
|
||||||
|
if (studio === 'image') selectStudioKind('image')
|
||||||
|
else selectStudioKind('video')
|
||||||
|
} else {
|
||||||
|
openStill(still)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void router.replace({ path: '/', query: {} })
|
void router.replace({ path: '/', query: {} })
|
||||||
@@ -6267,19 +6278,29 @@ async function loadStillAsEditRef(stillId: string) {
|
|||||||
setEditRef(new File([blob], stillFileName(still) || 'still-b.png', { type: blob.type || 'image/png' }))
|
setEditRef(new File([blob], stillFileName(still) || 'still-b.png', { type: blob.type || 'image/png' }))
|
||||||
}
|
}
|
||||||
|
|
||||||
function stillUseAsInputMessage(still: LibraryStill) {
|
function stillUseAsInputMessage(_still: LibraryStill) {
|
||||||
const dest = inferStillPipeline(still)
|
return 'Loaded as input. Switch to Image only if you want to edit it there.'
|
||||||
const mode = still.settings?.mode
|
}
|
||||||
if (dest === 'imagev2' && mode === 'generate') return 'Generate settings loaded. Tweak and generate.'
|
|
||||||
if (dest === 'imagev2' && mode === 'compose') {
|
async function useStillAsInput(still: LibraryStill) {
|
||||||
return still.settings?.referenceStillId
|
try {
|
||||||
? 'Compose settings loaded. Tweak and generate.'
|
// Use as input = put pixels in the start-still slot. Do NOT jump to Image studio
|
||||||
: 'Compose settings loaded. Re-add still B, then generate.'
|
// or overwrite video settings — that trapped people on the Image tab.
|
||||||
|
folderId.value = still.folderId
|
||||||
|
browseFolderId.value = still.folderId
|
||||||
|
const generateOnly = inferStillPipeline(still) === 'imagev2' && still.settings?.mode === 'generate'
|
||||||
|
if (generateOnly) {
|
||||||
|
toast('That still is text-to-image only (no canvas to reuse). Pick a different image.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const blob = await $fetch<Blob>(`/api/library/stills/${still.id}`, { responseType: 'blob' })
|
||||||
|
if (still.hideInput && showPrivacyToggles.value) hideInputPreview.value = true
|
||||||
|
readFile(new File([blob], stillFileName(still), { type: blob.type || 'image/png' }), { stillId: still.id })
|
||||||
|
scrollToStudioInput()
|
||||||
|
toast(stillUseAsInputMessage(still))
|
||||||
|
} catch (error: any) {
|
||||||
|
toast(error?.data?.statusMessage || 'Could not load that still')
|
||||||
}
|
}
|
||||||
if (dest === 'imagev2' && mode === 'refine') return 'Refine settings loaded. Paint a mask, then generate.'
|
|
||||||
if (dest === 'imagev2') return 'Image v2 settings loaded. Tweak and generate.'
|
|
||||||
if (dest === 'image' && still.settings) return 'Image edit settings loaded. Tweak and generate.'
|
|
||||||
return 'Loaded as input. It stays in Output, not the upload library.'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendOutputStillToBackup() {
|
async function sendOutputStillToBackup() {
|
||||||
@@ -6342,30 +6363,6 @@ async function saveEditAsInput() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function useStillAsInput(still: LibraryStill) {
|
|
||||||
try {
|
|
||||||
applyStillSettings(still)
|
|
||||||
const dest = inferStillPipeline(still)
|
|
||||||
const generate = dest === 'imagev2' && still.settings?.mode === 'generate'
|
|
||||||
if (!generate) {
|
|
||||||
const blob = await $fetch<Blob>(`/api/library/stills/${still.id}`, { responseType: 'blob' })
|
|
||||||
if (still.hideInput && showPrivacyToggles.value) hideInputPreview.value = true
|
|
||||||
readFile(new File([blob], stillFileName(still), { type: blob.type || 'image/png' }), { stillId: still.id })
|
|
||||||
}
|
|
||||||
if (dest === 'imagev2' && still.settings?.mode === 'compose' && still.settings.referenceStillId) {
|
|
||||||
try {
|
|
||||||
await loadStillAsEditRef(still.settings.referenceStillId)
|
|
||||||
} catch {
|
|
||||||
toast('Compose settings loaded, but still B is missing. Re-add it.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scrollToStudioInput()
|
|
||||||
toast(stillUseAsInputMessage(still))
|
|
||||||
} catch (error: any) {
|
|
||||||
toast(error?.data?.statusMessage || 'Could not load that still')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function useClipAsInput(item: LibraryClip, collection = false) {
|
async function useClipAsInput(item: LibraryClip, collection = false) {
|
||||||
await rerun(item, collection)
|
await rerun(item, collection)
|
||||||
scrollToStudioInput()
|
scrollToStudioInput()
|
||||||
|
|||||||
+2
-2
@@ -1054,11 +1054,11 @@ function openInStudio(entry: LibraryEntry) {
|
|||||||
|
|
||||||
function useInStudio(entry: LibraryEntry) {
|
function useInStudio(entry: LibraryEntry) {
|
||||||
if (entry.kind === 'clip' && entry.group) {
|
if (entry.kind === 'clip' && entry.group) {
|
||||||
navigateTo({ path: '/', query: { clip: focusedPartId.value || entry.group.head.id, use: '1' } })
|
navigateTo({ path: '/', query: { clip: focusedPartId.value || entry.group.head.id, use: '1', studio: 'video' } })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (entry.stillGroup) {
|
if (entry.stillGroup) {
|
||||||
navigateTo({ path: '/', query: { still: focusedPartId.value || entry.stillGroup.head.id, use: '1' } })
|
navigateTo({ path: '/', query: { still: focusedPartId.value || entry.stillGroup.head.id, use: '1', studio: 'video' } })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -7,7 +7,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<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" />
|
<StudioKindCards current="music" @select="goStudio" />
|
||||||
<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>
|
<div>
|
||||||
@@ -327,6 +327,10 @@ function selectEngineFamily(family: 'ace' | 'yue') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goStudio(kind: 'video' | 'image') {
|
||||||
|
void navigateTo({ path: '/', query: { studio: kind } })
|
||||||
|
}
|
||||||
|
|
||||||
function applyEngine(engine: MusicEngine) {
|
function applyEngine(engine: MusicEngine) {
|
||||||
if (engine === 'yue') {
|
if (engine === 'yue') {
|
||||||
engineFamily.value = 'yue'
|
engineFamily.value = 'yue'
|
||||||
|
|||||||
Reference in New Issue
Block a user