@@ -0,0 +1,132 @@
1+<script setup lang="ts">
2+import { Extension } from '@tiptap/core'
3+import type { EditorMentionMenuItem, SelectItem } from '@nuxt/ui'
4+5+const input = ref('')
6+const mode = ref('auto')
7+const attachments = ref<{ name: string, src?: string }[]>([])
8+const fileInputRef = useTemplateRef('fileInputRef')
9+10+const files: EditorMentionMenuItem[] = [
11+ { label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
12+ { label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' },
13+ { label: 'package.json', icon: 'i-vscode-icons-file-type-json' },
14+ { label: 'README.md', icon: 'i-vscode-icons-file-type-markdown' },
15+ { label: 'AuthForm.vue', icon: 'i-vscode-icons-file-type-vue' },
16+ { label: 'useChat.ts', icon: 'i-vscode-icons-file-type-typescript' }
17+]
18+19+const commands: EditorMentionMenuItem[] = [
20+ { label: 'init', icon: 'i-lucide-sparkles' },
21+ { label: 'review', icon: 'i-lucide-search-code' },
22+ { label: 'security-review', icon: 'i-lucide-shield-check' },
23+ { label: 'clear', icon: 'i-lucide-eraser' },
24+ { label: 'compact', icon: 'i-lucide-fold-vertical' },
25+ { label: 'config', icon: 'i-lucide-settings' }
26+]
27+28+const modes = [
29+ { label: 'Manual', value: 'manual', icon: 'i-lucide-hand' },
30+ { label: 'Edit automatically', value: 'edit', icon: 'i-lucide-code-xml' },
31+ { label: 'Plan mode', value: 'plan', icon: 'i-lucide-list-checks' },
32+ { label: 'Auto mode', value: 'auto', icon: 'i-lucide-zap' }
33+] satisfies SelectItem[]
34+35+// SSR-safe target so the menus aren't clipped by overflow
36+const appendToBody = import.meta.client ? () => document.body : undefined
37+38+function onFilesChange(event: Event) {
39+ const target = event.target as HTMLInputElement
40+ for (const file of Array.from(target.files ?? [])) {
41+ attachments.value.push({
42+ name: file.name,
43+ src: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined
44+ })
45+ }
46+ target.value = ''
47+}
48+49+function onSubmit() {
50+ console.log('submit', input.value)
51+52+ input.value = ''
53+ attachments.value = []
54+}
55+</script>
56+57+<template>
58+<UChatPrompt
59+v-model="input"
60+ class="w-full p-0 gap-0"
61+ placeholder="Press / to open the command menu"
62+ :ui="{
63+ header: 'px-2.5 py-2 border-b border-default',
64+ footer: 'px-2.5 py-2 border-t border-default'
65+ }"
66+ @submit="onSubmit"
67+>
68+<template v-if="attachments.length" #header>
69+<UButton
70+v-for="(file, index) in attachments"
71+ :key="index"
72+ :label="file.name"
73+ :avatar="file.src ? { src: file.src } : undefined"
74+ :icon="file.src ? undefined : 'i-lucide-file'"
75+ color="neutral"
76+ variant="soft"
77+ size="xs"
78+ square
79+ trailing-icon="i-lucide-x"
80+ @click="attachments.splice(index, 1)"
81+ />
82+</template>
83+84+<template #body="{ submit, placeholder }">
85+<UEditor
86+v-slot="{ editor }"
87+v-model="input"
88+ content-type="markdown"
89+ :starter-kit="false"
90+ :placeholder="placeholder"
91+ class="w-full min-h-12"
92+ :ui="{ base: 'p-2.5! [&_.mention]:bg-primary/5 [&_.mention]:rounded-sm [&_.mention]:px-0.5 [&_.mention]:py-0.25' }"
93+ :extensions="[Extension.create({
94+ name: 'chatPromptSubmit',
95+ priority: 1000,
96+ addKeyboardShortcuts: () => ({ Enter: () => (submit(), true) })
97+ })]"
98+>
99+<UEditorMentionMenu :editor="editor" char="@" plugin-key="mention" :items="files" :append-to="appendToBody" />
100+<UEditorMentionMenu :editor="editor" char="/" plugin-key="command" :items="commands" :append-to="appendToBody" />
101+</UEditor>
102+</template>
103+104+<template #footer>
105+<div class="flex items-center gap-0.5">
106+<UButton
107+ icon="i-lucide-plus"
108+ color="neutral"
109+ variant="ghost"
110+ aria-label="Attach files"
111+ size="sm"
112+ @click="fileInputRef?.click()"
113+ />
114+<input ref="fileInputRef" type="file" multiple class="hidden" @change="onFilesChange">
115+</div>
116+117+<div class="flex items-center gap-1">
118+<USelect
119+v-model="mode"
120+ :items="modes"
121+ :icon="modes.find(item => item.value === mode)?.icon"
122+ color="neutral"
123+ variant="ghost"
124+ size="sm"
125+ square
126+ />
127+128+<UChatPromptSubmit size="sm" :disabled="!input.trim()" />
129+</div>
130+</template>
131+</UChatPrompt>
132+</template>