@@ -0,0 +1,119 @@
1+<script lang="ts">
2+import type { VNode } from 'vue'
3+import type { AppConfig } from '@nuxt/schema'
4+import theme from '#build/ui/prose/prompt'
5+import type { IconProps } from '../../types'
6+import type { ComponentConfig } from '../../types/tv'
7+8+type ProsePrompt = ComponentConfig<typeof theme, AppConfig, 'prompt', 'ui.prose'>
9+10+export interface ProsePromptProps {
11+ description?: string
12+ /**
13+ * @IconifyIcon
14+ */
15+ icon?: IconProps['name']
16+ /**
17+ * @defaultValue ['copy']
18+ */
19+ actions?: ('copy' | 'cursor' | 'windsurf')[]
20+ class?: any
21+ ui?: ProsePrompt['slots']
22+}
23+24+export interface ProsePromptSlots {
25+ default(props?: {}): VNode[]
26+}
27+</script>
28+29+<script setup lang="ts">
30+import { computed } from 'vue'
31+import { useClipboard } from '@vueuse/core'
32+import { useAppConfig } from '#imports'
33+import { useComponentUI } from '../../composables/useComponentUI'
34+import { useLocale } from '../../composables/useLocale'
35+import { getSlotChildrenText } from '../../utils'
36+import { tv } from '../../utils/tv'
37+import UIcon from '../Icon.vue'
38+import UButton from '../Button.vue'
39+40+defineOptions({ inheritAttrs: false })
41+42+const props = withDefaults(defineProps<ProsePromptProps>(), {
43+ actions: () => ['copy']
44+})
45+const slots = defineSlots<ProsePromptSlots>()
46+47+const { t } = useLocale()
48+const { copy, copied } = useClipboard()
49+const appConfig = useAppConfig() as ProsePrompt['AppConfig']
50+const uiProp = useComponentUI('prose.prompt', props)
51+52+// eslint-disable-next-line vue/no-dupe-keys
53+const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.prose?.prompt || {}) })())
54+55+function getPromptText() {
56+ const children = slots.default?.()
57+ return children ? getSlotChildrenText(children).trim() : ''
58+}
59+60+function copyPrompt() {
61+ copy(getPromptText())
62+}
63+64+function openInCursor() {
65+ const url = new URL('cursor://anysphere.cursor-deeplink/prompt')
66+ url.searchParams.set('text', getPromptText())
67+68+ window.open(url.toString(), '_self')
69+}
70+71+function openInWindsurf() {
72+ const url = new URL('windsurf://cascade/newChat')
73+ url.searchParams.set('prompt', getPromptText())
74+75+ window.open(url.toString(), '_self')
76+}
77+</script>
78+79+<template>
80+<div :class="ui.root({ class: [uiProp?.root, props.class] })" v-bind="$attrs">
81+<UIcon v-if="icon" :name="icon" :class="ui.icon({ class: uiProp?.icon })" />
82+83+<div :class="ui.content({ class: uiProp?.content })">
84+<p v-if="description" :class="ui.description({ class: uiProp?.description })">
85+ {{ description }}
86+</p>
87+</div>
88+89+<div :class="ui.actions({ class: uiProp?.actions })">
90+<UButton
91+v-if="actions.includes('copy')"
92+ :icon="copied ? appConfig.ui.icons.copyCheck : appConfig.ui.icons.copy"
93+ size="sm"
94+ :label="t('prose.prompt.copy')"
95+ @click="copyPrompt"
96+ />
97+98+<UButton
99+v-if="actions.includes('cursor')"
100+ icon="i-simple-icons-cursor"
101+ color="neutral"
102+ variant="outline"
103+ size="sm"
104+ :label="t('prose.prompt.openIn', { name: 'Cursor' })"
105+ @click="openInCursor"
106+ />
107+108+<UButton
109+v-if="actions.includes('windsurf')"
110+ icon="i-simple-icons-windsurf"
111+ color="neutral"
112+ variant="outline"
113+ size="sm"
114+ :label="t('prose.prompt.openIn', { name: 'Windsurf' })"
115+ @click="openInWindsurf"
116+ />
117+</div>
118+</div>
119+</template>