Split prompts into pre/prompt/post and tidy the AIGen video form.
Keep wrappers off the library mid-prompt so Use as input and Extend restore the same split, send stills to xAIGen as hidden inputs, and stop the two-column library from growing with the list. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<details class="rounded-2xl border border-white/10 bg-zinc-950/40 px-4 py-3">
|
||||
<details v-if="!embedded" class="rounded-2xl border border-white/10 bg-zinc-950/40 px-4 py-3">
|
||||
<summary class="cursor-pointer text-sm font-medium text-zinc-200">
|
||||
Global locks
|
||||
<span class="ml-2 font-normal text-xs text-zinc-500">Every shot inherits these. Each clip adds one beat.</span>
|
||||
@@ -7,31 +7,45 @@
|
||||
<p class="mt-2 text-xs text-zinc-500">
|
||||
Shared standards for last-frame I2V. Later shots get a compact <span class="text-zinc-300">[INHERITS]</span> line, not this whole block again.
|
||||
</p>
|
||||
<textarea
|
||||
:value="modelValue"
|
||||
rows="10"
|
||||
class="mt-3 max-h-64 w-full overflow-y-auto rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm outline-none ring-amber-300/40 placeholder:text-zinc-600 [overflow-wrap:anywhere] focus:ring-2"
|
||||
:placeholder="placeholder"
|
||||
@input="emit('update:modelValue', ($event.target as HTMLTextAreaElement).value)"
|
||||
/>
|
||||
<div class="mt-2 flex flex-wrap gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-white/15 px-2.5 py-1 text-xs text-zinc-300 hover:border-amber-300/40 hover:text-amber-100"
|
||||
@click="insertTemplate"
|
||||
>
|
||||
Insert template
|
||||
</button>
|
||||
<button
|
||||
v-if="modelValue.trim()"
|
||||
type="button"
|
||||
class="rounded-lg border border-white/15 px-2.5 py-1 text-xs text-zinc-400 hover:text-white"
|
||||
@click="emit('update:modelValue', '')"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
<div class="mt-3">
|
||||
<PromptField
|
||||
:model-value="modelValue"
|
||||
:rows="10"
|
||||
title="Global locks"
|
||||
write-label="Write locks"
|
||||
edit-label="View / edit locks"
|
||||
textarea-class="max-h-64"
|
||||
:placeholder="placeholder"
|
||||
@update:model-value="emit('update:modelValue', $event)"
|
||||
/>
|
||||
<div class="mt-2 flex flex-wrap gap-2">
|
||||
<button type="button" class="rounded-lg border border-white/15 px-2.5 py-1 text-xs text-zinc-300 hover:border-amber-300/40 hover:text-amber-100" @click="insertTemplate">Insert template</button>
|
||||
<button v-if="modelValue.trim()" type="button" class="rounded-lg border border-white/15 px-2.5 py-1 text-xs text-zinc-400 hover:text-white" @click="emit('update:modelValue', '')">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<div v-else>
|
||||
<p class="text-sm font-medium text-zinc-200">Global locks</p>
|
||||
<p class="mt-0.5 text-xs text-zinc-500">
|
||||
Shared standards for last-frame I2V. Later shots get a compact <span class="text-zinc-300">[INHERITS]</span> line, not this whole block again.
|
||||
</p>
|
||||
<div class="mt-3">
|
||||
<PromptField
|
||||
:model-value="modelValue"
|
||||
:rows="10"
|
||||
title="Global locks"
|
||||
write-label="Write locks"
|
||||
edit-label="View / edit locks"
|
||||
textarea-class="max-h-64"
|
||||
:placeholder="placeholder"
|
||||
@update:model-value="emit('update:modelValue', $event)"
|
||||
/>
|
||||
<div class="mt-2 flex flex-wrap gap-2">
|
||||
<button type="button" class="rounded-lg border border-white/15 px-2.5 py-1 text-xs text-zinc-300 hover:border-amber-300/40 hover:text-amber-100" @click="insertTemplate">Insert template</button>
|
||||
<button v-if="modelValue.trim()" type="button" class="rounded-lg border border-white/15 px-2.5 py-1 text-xs text-zinc-400 hover:text-white" @click="emit('update:modelValue', '')">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -39,6 +53,7 @@ import { DEFAULT_GLOBAL_LOCKS } from '~/utils/globalLocks'
|
||||
|
||||
defineProps<{
|
||||
modelValue: string
|
||||
embedded?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div>
|
||||
<textarea
|
||||
:value="modelValue"
|
||||
:rows="rows"
|
||||
class="hidden w-full overflow-y-auto rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm outline-none ring-amber-300/40 placeholder:text-zinc-600 [overflow-wrap:anywhere] focus:ring-2 md:block"
|
||||
:class="textareaClass"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
@input="onInput"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="w-full rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-left md:hidden"
|
||||
:disabled="disabled"
|
||||
@click="drawerOpen = true"
|
||||
>
|
||||
<p v-if="modelValue.trim()" class="line-clamp-4 whitespace-pre-wrap text-sm text-zinc-200 [overflow-wrap:anywhere]">{{ modelValue }}</p>
|
||||
<p v-else class="text-sm text-zinc-600">{{ placeholder }}</p>
|
||||
<span class="mt-1 inline-block text-xs text-amber-200">{{ modelValue.trim() ? editLabel : writeLabel }}</span>
|
||||
</button>
|
||||
<PromptDrawer
|
||||
:open="drawerOpen"
|
||||
:title="title"
|
||||
:text="modelValue"
|
||||
:placeholder="placeholder"
|
||||
:keep-items="keepItems"
|
||||
:keep-heading="keepHeading"
|
||||
@update:open="drawerOpen = $event"
|
||||
@update:text="emit('update:modelValue', $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { KeepPrompt } from '~/utils/keepPrompts'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue?: string
|
||||
rows?: number
|
||||
placeholder?: string
|
||||
title?: string
|
||||
editLabel?: string
|
||||
writeLabel?: string
|
||||
textareaClass?: string
|
||||
disabled?: boolean
|
||||
keepItems?: KeepPrompt[]
|
||||
keepHeading?: string
|
||||
}>(), {
|
||||
modelValue: '',
|
||||
rows: 3,
|
||||
placeholder: '',
|
||||
title: 'Prompt',
|
||||
editLabel: 'View / edit',
|
||||
writeLabel: 'Write',
|
||||
textareaClass: 'max-h-32 md:max-h-none',
|
||||
disabled: false,
|
||||
keepItems: () => [],
|
||||
keepHeading: 'Saved prompts'
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
}>()
|
||||
|
||||
const drawerOpen = ref(false)
|
||||
|
||||
function onInput(event: Event) {
|
||||
emit('update:modelValue', (event.target as HTMLTextAreaElement).value)
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<div class="space-y-3">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<KeepPromptMenu
|
||||
:items="keepItems"
|
||||
:heading="keepHeading"
|
||||
:disabled="disabled"
|
||||
@insert="insertSnippet"
|
||||
/>
|
||||
<div class="inline-flex rounded-full border border-white/10 p-0.5">
|
||||
<button
|
||||
v-for="option in targets"
|
||||
:key="option.id"
|
||||
type="button"
|
||||
class="rounded-full px-2.5 py-1 text-[11px]"
|
||||
:class="snippetTarget === option.id ? 'bg-amber-400/15 text-amber-100' : 'text-zinc-500 hover:text-zinc-300'"
|
||||
:disabled="disabled"
|
||||
@click="snippetTarget = option.id"
|
||||
>
|
||||
{{ option.label }}
|
||||
</button>
|
||||
</div>
|
||||
<span class="text-[11px] text-zinc-500">Snippets go into {{ targetLabel }}.</span>
|
||||
</div>
|
||||
|
||||
<label class="block text-sm">
|
||||
<span class="mb-1 block text-xs font-medium text-zinc-400">Pre-text</span>
|
||||
<textarea
|
||||
:value="pre"
|
||||
rows="2"
|
||||
class="hidden max-h-40 w-full overflow-y-auto rounded-2xl border border-white/10 bg-zinc-950/70 px-4 py-2.5 text-sm outline-none ring-amber-300/40 placeholder:text-zinc-600 [overflow-wrap:anywhere] focus:ring-2 md:block"
|
||||
:placeholder="prePlaceholder"
|
||||
:disabled="disabled"
|
||||
@focus="snippetTarget = 'pre'"
|
||||
@input="onInput('pre', $event)"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="w-full rounded-2xl border border-white/10 bg-zinc-950/70 px-4 py-2.5 text-left md:hidden"
|
||||
@click="openDrawer('pre')"
|
||||
>
|
||||
<p v-if="pre.trim()" class="line-clamp-3 whitespace-pre-wrap text-sm text-zinc-200 [overflow-wrap:anywhere]">{{ pre }}</p>
|
||||
<p v-else class="text-sm text-zinc-600">{{ prePlaceholder }}</p>
|
||||
<span class="mt-1 inline-block text-xs text-amber-200">{{ pre.trim() ? 'View / edit pre-text' : 'Write pre-text' }}</span>
|
||||
</button>
|
||||
</label>
|
||||
|
||||
<label class="block text-sm">
|
||||
<span class="mb-1 block text-xs font-medium text-zinc-400">Prompt</span>
|
||||
<textarea
|
||||
:value="prompt"
|
||||
:rows="promptRows"
|
||||
class="hidden max-h-[min(28rem,70vh)] w-full overflow-y-auto rounded-2xl border border-white/10 bg-zinc-950/70 px-4 py-3 text-sm outline-none ring-amber-300/40 placeholder:text-zinc-600 [overflow-wrap:anywhere] focus:ring-2 md:block"
|
||||
:placeholder="promptPlaceholder"
|
||||
:disabled="disabled"
|
||||
@focus="snippetTarget = 'prompt'"
|
||||
@input="onInput('prompt', $event)"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="w-full rounded-2xl border border-white/10 bg-zinc-950/70 px-4 py-3 text-left md:hidden"
|
||||
@click="openDrawer('prompt')"
|
||||
>
|
||||
<p v-if="prompt.trim()" class="line-clamp-4 whitespace-pre-wrap text-sm text-zinc-200 [overflow-wrap:anywhere]">{{ prompt }}</p>
|
||||
<p v-else class="text-sm text-zinc-600">{{ promptPlaceholder }}</p>
|
||||
<span class="mt-2 inline-block text-xs text-amber-200">{{ prompt.trim() ? 'View / edit prompt' : 'Write prompt' }}</span>
|
||||
</button>
|
||||
</label>
|
||||
|
||||
<label class="block text-sm">
|
||||
<span class="mb-1 block text-xs font-medium text-zinc-400">Post-text</span>
|
||||
<textarea
|
||||
:value="post"
|
||||
rows="2"
|
||||
class="hidden max-h-40 w-full overflow-y-auto rounded-2xl border border-white/10 bg-zinc-950/70 px-4 py-2.5 text-sm outline-none ring-amber-300/40 placeholder:text-zinc-600 [overflow-wrap:anywhere] focus:ring-2 md:block"
|
||||
:placeholder="postPlaceholder"
|
||||
:disabled="disabled"
|
||||
@focus="snippetTarget = 'post'"
|
||||
@input="onInput('post', $event)"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="w-full rounded-2xl border border-white/10 bg-zinc-950/70 px-4 py-2.5 text-left md:hidden"
|
||||
@click="openDrawer('post')"
|
||||
>
|
||||
<p v-if="post.trim()" class="line-clamp-3 whitespace-pre-wrap text-sm text-zinc-200 [overflow-wrap:anywhere]">{{ post }}</p>
|
||||
<p v-else class="text-sm text-zinc-600">{{ postPlaceholder }}</p>
|
||||
<span class="mt-1 inline-block text-xs text-amber-200">{{ post.trim() ? 'View / edit post-text' : 'Write post-text' }}</span>
|
||||
</button>
|
||||
</label>
|
||||
|
||||
<div v-if="keepItems.length" class="flex flex-wrap gap-1.5">
|
||||
<button
|
||||
v-for="item in keepItems"
|
||||
:key="item.id"
|
||||
type="button"
|
||||
class="max-w-full truncate rounded-full border border-white/10 px-2.5 py-1 text-xs text-zinc-300 hover:border-amber-300/40 hover:text-amber-100"
|
||||
:title="item.text"
|
||||
:disabled="disabled"
|
||||
@click="insertSnippet(item)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<PromptDrawer
|
||||
:open="drawerOpen"
|
||||
:title="drawerTitle"
|
||||
:text="drawerText"
|
||||
:placeholder="drawerPlaceholder"
|
||||
:keep-items="keepItems"
|
||||
:keep-heading="keepHeading"
|
||||
@update:open="drawerOpen = $event"
|
||||
@update:text="onDrawerText"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { insertKeepSnippet, type KeepPrompt } from '~/utils/keepPrompts'
|
||||
|
||||
const targets = [
|
||||
{ id: 'pre' as const, label: 'Pre' },
|
||||
{ id: 'prompt' as const, label: 'Prompt' },
|
||||
{ id: 'post' as const, label: 'Post' }
|
||||
]
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
pre?: string
|
||||
prompt?: string
|
||||
post?: string
|
||||
keepItems?: KeepPrompt[]
|
||||
keepHeading?: string
|
||||
promptPlaceholder?: string
|
||||
prePlaceholder?: string
|
||||
postPlaceholder?: string
|
||||
promptRows?: number
|
||||
disabled?: boolean
|
||||
}>(), {
|
||||
pre: '',
|
||||
prompt: '',
|
||||
post: '',
|
||||
keepItems: () => [],
|
||||
keepHeading: 'Saved prompts',
|
||||
promptPlaceholder: '',
|
||||
prePlaceholder: 'Optional text before the prompt…',
|
||||
postPlaceholder: 'Optional text after the prompt…',
|
||||
promptRows: 6,
|
||||
disabled: false
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:pre': [value: string]
|
||||
'update:prompt': [value: string]
|
||||
'update:post': [value: string]
|
||||
}>()
|
||||
|
||||
const snippetTarget = ref<'pre' | 'prompt' | 'post'>('prompt')
|
||||
const drawerField = ref<'pre' | 'prompt' | 'post'>('prompt')
|
||||
const drawerOpen = ref(false)
|
||||
|
||||
const targetLabel = computed(() => targets.find(item => item.id === snippetTarget.value)?.label.toLowerCase() || 'prompt')
|
||||
const drawerTitle = computed(() => {
|
||||
if (drawerField.value === 'pre') return 'Pre-text'
|
||||
if (drawerField.value === 'post') return 'Post-text'
|
||||
return 'Prompt'
|
||||
})
|
||||
const drawerPlaceholder = computed(() => {
|
||||
if (drawerField.value === 'pre') return props.prePlaceholder
|
||||
if (drawerField.value === 'post') return props.postPlaceholder
|
||||
return props.promptPlaceholder
|
||||
})
|
||||
const drawerText = computed(() => {
|
||||
if (drawerField.value === 'pre') return props.pre
|
||||
if (drawerField.value === 'post') return props.post
|
||||
return props.prompt
|
||||
})
|
||||
|
||||
function currentValue(target: 'pre' | 'prompt' | 'post') {
|
||||
if (target === 'pre') return props.pre
|
||||
if (target === 'post') return props.post
|
||||
return props.prompt
|
||||
}
|
||||
|
||||
function emitField(target: 'pre' | 'prompt' | 'post', value: string) {
|
||||
if (target === 'pre') emit('update:pre', value)
|
||||
else if (target === 'post') emit('update:post', value)
|
||||
else emit('update:prompt', value)
|
||||
}
|
||||
|
||||
function onInput(target: 'pre' | 'prompt' | 'post', event: Event) {
|
||||
emitField(target, (event.target as HTMLTextAreaElement).value)
|
||||
}
|
||||
|
||||
function insertSnippet(item: KeepPrompt) {
|
||||
emitField(snippetTarget.value, insertKeepSnippet(currentValue(snippetTarget.value), item.text))
|
||||
}
|
||||
|
||||
function openDrawer(target: 'pre' | 'prompt' | 'post') {
|
||||
snippetTarget.value = target
|
||||
drawerField.value = target
|
||||
drawerOpen.value = true
|
||||
}
|
||||
|
||||
function onDrawerText(value: string) {
|
||||
emitField(drawerField.value, value)
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user