210 lines
7.5 KiB
Vue
210 lines
7.5 KiB
Vue
<template>
|
|
<div class="space-y-3">
|
|
<div v-if="!simple" 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 v-if="!simple || pre.trim()" 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 v-if="!simple || post.trim()" 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<{
|
|
simple?: boolean
|
|
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>
|