Add clickable keep-prompt snippets for video and image.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-08-31 20:39:08 -05:00
co-authored by Cursor
parent 529598cef2
commit a3380870ad
7 changed files with 407 additions and 12 deletions
+54 -6
View File
@@ -190,7 +190,15 @@
<div>
<div class="mb-2 flex flex-wrap items-center justify-between gap-2">
<label class="text-sm font-medium text-zinc-300">{{ studioMode === 'editv2' && v2Mode === 'generate' ? 'Generate prompt' : studioMode === 'editv2' && v2Mode === 'refine' ? 'Refine prompt' : studioMode === 'edit' || studioMode === 'editv2' ? 'Edit prompt' : 'Motion & scene prompt' }}</label>
<div class="flex items-center gap-2">
<label class="text-sm font-medium text-zinc-300">{{ studioMode === 'editv2' && v2Mode === 'generate' ? 'Generate prompt' : studioMode === 'editv2' && v2Mode === 'refine' ? 'Refine prompt' : studioMode === 'edit' || studioMode === 'editv2' ? 'Edit prompt' : 'Motion & scene prompt' }}</label>
<KeepPromptMenu
:items="activeKeepPrompts"
:heading="keepPromptHeading"
align="left"
@insert="insertKeepPrompt"
/>
</div>
<div class="flex flex-wrap items-center gap-3">
<button
v-if="identityPrompting"
@@ -486,7 +494,16 @@
class="rounded-2xl border border-white/10 bg-zinc-950/50 p-4"
>
<div class="mb-3 flex items-center justify-between gap-2">
<p class="text-sm font-medium text-zinc-200">Extension #{{ index + 1 }}</p>
<div class="flex items-center gap-2">
<p class="text-sm font-medium text-zinc-200">Extension #{{ index + 1 }}</p>
<KeepPromptMenu
:items="keepPrompts.video"
heading="Video prompts"
align="left"
:disabled="videoBusy"
@insert="insertKeepIntoExtension(item.id, $event)"
/>
</div>
<button
type="button"
class="text-xs text-red-300 hover:text-red-200"
@@ -1360,7 +1377,16 @@
</p>
</div>
<label class="block text-sm">
<span class="mb-1 block text-zinc-400">Extension prompt</span>
<span class="mb-1 flex items-center gap-2 text-zinc-400">
Extension prompt
<KeepPromptMenu
:items="keepPrompts.video"
heading="Video prompts"
align="left"
:disabled="lastFrameLoading || videoBusy"
@insert="insertKeepExtend"
/>
</span>
<textarea
v-model="extendPrompt"
rows="4"
@@ -1965,6 +1991,8 @@
v-model:text="prompt"
:title="studioMode === 'edit' || studioMode === 'editv2' ? 'Edit prompt' : (shotScriptMode ? 'Shot script' : 'Prompt')"
:placeholder="promptPlaceholder"
:keep-items="activeKeepPrompts"
:keep-heading="keepPromptHeading"
@update:open="promptDrawerOpen = $event"
/>
<ImageBatchModal
@@ -2122,6 +2150,7 @@ import {
} from '~/utils/libraryGeneration'
import { parseImageAspect, generateSizeForAspect } from '~/utils/imageAspect'
import { buildOutputBaseName, nextUniqueName } from '~/utils/outputNames'
import { emptyKeepPrompts, insertKeepSnippet, normalizeKeepPrompts, type KeepPrompt, type KeepPromptLists } from '~/utils/keepPrompts'
import {
IMAGE_V2_CFG_DEFAULT,
IMAGE_V2_CONSISTENCY_LORA,
@@ -2567,6 +2596,9 @@ const stills = ref<LibraryStill[]>([])
const drafts = ref<RetryDraft[]>([])
const loraAliases = ref<Record<string, string>>({})
const loraTriggers = ref<Record<string, string>>({})
const keepPrompts = ref<KeepPromptLists>(emptyKeepPrompts())
const activeKeepPrompts = computed(() => studioMode.value === 'video' ? keepPrompts.value.video : keepPrompts.value.image)
const keepPromptHeading = computed(() => studioMode.value === 'video' ? 'Video prompts' : 'Image prompts')
const folderId = ref('')
const browseFolderId = ref('')
const renamingId = ref('')
@@ -3503,8 +3535,8 @@ function applyInstancePrivacy() {
hideThumbnail.value = readPrivacyToggle('aigen-hide-output-preview', true)
}
function applyLibrary(data: { folders?: LibraryFolder[]; clips?: LibraryClip[]; stills?: LibraryStill[]; drafts?: RetryDraft[]; loraAliases?: Record<string, string>; loraTriggers?: Record<string, string> } | unknown) {
const payload = (data || {}) as { folders?: LibraryFolder[]; clips?: LibraryClip[]; stills?: LibraryStill[]; drafts?: RetryDraft[]; loraAliases?: Record<string, string>; loraTriggers?: Record<string, string> }
function applyLibrary(data: { folders?: LibraryFolder[]; clips?: LibraryClip[]; stills?: LibraryStill[]; drafts?: RetryDraft[]; loraAliases?: Record<string, string>; loraTriggers?: Record<string, string>; keepPrompts?: KeepPromptLists } | unknown) {
const payload = (data || {}) as { folders?: LibraryFolder[]; clips?: LibraryClip[]; stills?: LibraryStill[]; drafts?: RetryDraft[]; loraAliases?: Record<string, string>; loraTriggers?: Record<string, string>; keepPrompts?: KeepPromptLists }
folders.value = payload.folders || []
clips.value = payload.clips || []
stills.value = payload.stills || []
@@ -3515,6 +3547,9 @@ function applyLibrary(data: { folders?: LibraryFolder[]; clips?: LibraryClip[];
if (payload.loraTriggers && typeof payload.loraTriggers === 'object') {
loraTriggers.value = payload.loraTriggers
}
if (payload.keepPrompts) {
keepPrompts.value = normalizeKeepPrompts(payload.keepPrompts)
}
if (!folderId.value || !folders.value.some(folder => folder.id === folderId.value)) {
folderId.value = folders.value[0]?.id || ''
}
@@ -3524,7 +3559,7 @@ function applyLibrary(data: { folders?: LibraryFolder[]; clips?: LibraryClip[];
}
async function loadLibrary(light = false) {
const data = await $fetch<{ folders?: LibraryFolder[]; clips?: LibraryClip[]; stills?: LibraryStill[]; drafts?: RetryDraft[]; loraAliases?: Record<string, string>; loraTriggers?: Record<string, string> }>(light ? '/api/library?light=1' : '/api/library')
const data = await $fetch<{ folders?: LibraryFolder[]; clips?: LibraryClip[]; stills?: LibraryStill[]; drafts?: RetryDraft[]; loraAliases?: Record<string, string>; loraTriggers?: Record<string, string>; keepPrompts?: KeepPromptLists }>(light ? '/api/library?light=1' : '/api/library')
applyLibrary(data)
if (light) return
const queued = await $fetch<{ pendingCount?: number }>('/api/queues').catch(() => ({ pendingCount: 0 }))
@@ -3996,6 +4031,19 @@ async function saveLoraTrigger(name: string, trigger: string) {
}
}
function insertKeepPrompt(item: KeepPrompt) {
prompt.value = insertKeepSnippet(prompt.value, item.text)
}
function insertKeepExtend(item: KeepPrompt) {
extendPrompt.value = insertKeepSnippet(extendPrompt.value, item.text)
}
function insertKeepIntoExtension(id: string, item: KeepPrompt) {
const row = extensionQueue.value.find(entry => entry.id === id)
if (row) row.prompt = insertKeepSnippet(row.prompt, item.text)
}
function insertLoraTrigger(phrase: string, target: 'main' | 'extend' | { shot: number } | { extensionId: string } = 'main') {
const token = String(phrase || '').trim()
if (!token) return