Chatón
Una paleta de chat para crear una interfaz de chatbot dentro de una superposición.
xph0000xUso
El componente ChatPalette es un envoltorio de diseño estructurado que organiza ChatMessages en un área de contenido desplazable y ChatPrompt en una sección inferior fija, creando interfaces cohesivas de chatbot para modales, diapositivas o cajones.
<template>
<UChatPalette>
<UChatMessages />
<template #prompt>
<UChatPrompt />
</template>
</UChatPalette>
</template>
Ejemplos
Consulte la página de descripción general de Chat para obtener instrucciones de instalación, configuración del servidor y ejemplos de uso.
Dentro de un Modal
Puede utilizar el componente ChatPalette dentro del contenido de un Modal.
<script setup lang="ts">
import { isTextUIPart } from 'ai'
import type { UIMessage } from 'ai'
import { useChat } from '@ai-sdk/vue'
import { isPartStreaming } from '@nuxt/ui/utils/ai'
import { Markdown } from '@comark/vue'
import shiki from '@comark/vue/plugins/shiki'
import security from '@comark/vue/plugins/security'
const plugins = [shiki(), security({ blockedTags: ['script', 'style', 'iframe', 'object', 'embed', 'form'] })]
const initialMessages: UIMessage[] = [{
id: '1',
role: 'user',
parts: [{ type: 'text', text: 'What is Nuxt UI?' }]
}, {
id: '2',
role: 'assistant',
parts: [{ type: 'text', text: 'Nuxt UI is a Vue component library built on Reka UI, Tailwind CSS, and Tailwind Variants. It provides 125+ accessible components for building modern web apps.' }]
}]
const input = ref('')
const { messages, status, error, sendMessage } = useChat({
messages: initialMessages
})
function onSubmit() {
if (!input.value.trim()) return
sendMessage({ text: input.value })
input.value = ''
}
const ui = {
prose: {
p: { base: 'my-2 leading-6' },
li: { base: 'my-0.5 leading-6' },
ul: { base: 'my-2' },
ol: { base: 'my-2' },
h1: { base: 'text-xl my-2' },
h2: { base: 'text-lg my-2' },
h3: { base: 'text-base my-2' },
h4: { base: 'text-sm my-2' },
pre: { root: 'my-2' },
table: { root: 'my-2' },
hr: { base: 'my-2' }
}
}
</script>
<template>
<UModal open :ui="{ content: 'sm:max-w-3xl sm:h-[28rem]' }">
<template #content>
<UTheme :ui="ui">
<UChatPalette>
<UChatMessages
:messages="messages"
:status="status"
:user="{ side: 'left', variant: 'naked', avatar: { src: 'https://github.com/benjamincanac.png', loading: 'lazy' as const } }"
:assistant="{ icon: 'i-lucide-bot' }"
>
<template #content="{ message }">
<template v-for="(part, index) in message.parts" :key="`${message.id}-${part.type}-${index}`">
<template v-if="isTextUIPart(part)">
<Markdown
v-if="message.role === 'assistant'"
:value="part.text"
:streaming="isPartStreaming(part)"
:plugins="plugins"
class="*:first:mt-0 *:last:mb-0"
/>
<p v-else-if="message.role === 'user'" class="whitespace-pre-wrap leading-6">
{{ part.text }}
</p>
</template>
</template>
</template>
</UChatMessages>
<template #prompt>
<UChatPrompt
v-model="input"
icon="i-lucide-search"
variant="naked"
:error="error"
@submit="onSubmit"
/>
</template>
</UChatPalette>
</UTheme>
</template>
</UModal>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { isTextUIPart } from 'ai'
import type { UIMessage } from 'ai'
import { useChat } from '@ai-sdk/vue'
import { isPartStreaming } from '@nuxt/ui/utils/ai'
import { Markdown } from '@comark/vue'
import shiki from '@comark/vue/plugins/shiki'
import security from '@comark/vue/plugins/security'
const plugins = [shiki(), security({ blockedTags: ['script', 'style', 'iframe', 'object', 'embed', 'form'] })]
const initialMessages: UIMessage[] = [{
id: '1',
role: 'user',
parts: [{ type: 'text', text: 'What is Nuxt UI?' }]
}, {
id: '2',
role: 'assistant',
parts: [{ type: 'text', text: 'Nuxt UI is a Vue component library built on Reka UI, Tailwind CSS, and Tailwind Variants. It provides 125+ accessible components for building modern web apps.' }]
}]
const input = ref('')
const { messages, status, error, sendMessage } = useChat({
messages: initialMessages
})
function onSubmit() {
if (!input.value.trim()) return
sendMessage({ text: input.value })
input.value = ''
}
const ui = {
prose: {
p: { base: 'my-2 leading-6' },
li: { base: 'my-0.5 leading-6' },
ul: { base: 'my-2' },
ol: { base: 'my-2' },
h1: { base: 'text-xl my-2' },
h2: { base: 'text-lg my-2' },
h3: { base: 'text-base my-2' },
h4: { base: 'text-sm my-2' },
pre: { root: 'my-2' },
table: { root: 'my-2' },
hr: { base: 'my-2' }
}
}
</script>
<template>
<UModal open :ui="{ content: 'sm:max-w-3xl sm:h-[28rem]' }">
<template #content>
<UTheme :ui="ui">
<UChatPalette>
<UChatMessages
:messages="messages"
:status="status"
:user="{ side: 'left', variant: 'naked', avatar: { src: 'https://github.com/benjamincanac.png', loading: 'lazy' as const } }"
:assistant="{ icon: 'i-lucide-bot' }"
>
<template #content="{ message }">
<template v-for="(part, index) in message.parts" :key="`${message.id}-${part.type}-${index}`">
<template v-if="isTextUIPart(part)">
<Markdown
v-if="message.role === 'assistant'"
:value="part.text"
:streaming="isPartStreaming(part)"
:plugins="plugins"
class="*:first:mt-0 *:last:mb-0"
/>
<p v-else-if="message.role === 'user'" class="whitespace-pre-wrap leading-6">
{{ part.text }}
</p>
</template>
</template>
</template>
</UChatMessages>
<template #prompt>
<UChatPrompt
v-model="input"
icon="i-lucide-search"
variant="naked"
:error="error"
@submit="onSubmit"
/>
</template>
</UChatPalette>
</UTheme>
</template>
</UModal>
</template>
Dentro de ContentSearch
Puede utilizar el componente ChatPalette condicionalmente dentro del contenido de ContentSearch para mostrar una interfaz de chatbot cuando un usuario selecciona un elemento.
<script setup lang="ts">
import { isTextUIPart } from 'ai'
import { useChat } from '@ai-sdk/vue'
import { isPartStreaming } from '@nuxt/ui/utils/ai'
import { Markdown } from '@comark/vue'
import shiki from '@comark/vue/plugins/shiki'
import security from '@comark/vue/plugins/security'
const plugins = [shiki(), security({ blockedTags: ['script', 'style', 'iframe', 'object', 'embed', 'form'] })]
const input = ref('')
const { messages, status, error, sendMessage, regenerate } = useChat()
const groups = computed(() => [{
id: 'ai',
ignoreFilter: true,
items: [{
label: searchTerm.value ? `Ask AI for “${searchTerm.value}”` : 'Ask AI',
icon: 'i-lucide-bot',
onSelect: (e: any) => {
e.preventDefault()
ai.value = true
if (searchTerm.value) {
messages.value = [...messages.value, {
id: '1',
role: 'user',
parts: [{ type: 'text', text: searchTerm.value }]
}]
regenerate()
}
}
}]
}])
const ai = ref(false)
const searchTerm = ref('')
function onSubmit() {
if (!input.value.trim()) return
sendMessage({ text: input.value })
input.value = ''
}
function onClose(e: Event) {
e.preventDefault()
ai.value = false
}
const ui = {
prose: {
p: { base: 'my-2 leading-6' },
li: { base: 'my-0.5 leading-6' },
ul: { base: 'my-2' },
ol: { base: 'my-2' },
h1: { base: 'text-xl my-2' },
h2: { base: 'text-lg my-2' },
h3: { base: 'text-base my-2' },
h4: { base: 'text-sm my-2' },
pre: { root: 'my-2' },
table: { root: 'my-2' },
hr: { base: 'my-2' }
}
}
</script>
<template>
<UContentSearch v-model:search-term="searchTerm" open :groups="groups">
<template v-if="ai" #content>
<UTheme :ui="ui">
<UChatPalette>
<UChatMessages
:messages="messages"
:status="status"
:user="{ side: 'left', variant: 'naked', avatar: { src: 'https://github.com/benjamincanac.png', loading: 'lazy' as const } }"
:assistant="{ icon: 'i-lucide-bot' }"
>
<template #content="{ message }">
<template v-for="(part, index) in message.parts" :key="`${message.id}-${part.type}-${index}`">
<template v-if="isTextUIPart(part)">
<Markdown
v-if="message.role === 'assistant'"
:value="part.text"
:streaming="isPartStreaming(part)"
:plugins="plugins"
class="*:first:mt-0 *:last:mb-0"
/>
<p v-else-if="message.role === 'user'" class="whitespace-pre-wrap leading-6">
{{ part.text }}
</p>
</template>
</template>
</template>
</UChatMessages>
<template #prompt>
<UChatPrompt
v-model="input"
icon="i-lucide-search"
variant="naked"
:error="error"
@submit="onSubmit"
@close="onClose"
/>
</template>
</UChatPalette>
</UTheme>
</template>
</UContentSearch>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { isTextUIPart } from 'ai'
import { useChat } from '@ai-sdk/vue'
import { isPartStreaming } from '@nuxt/ui/utils/ai'
import { Markdown } from '@comark/vue'
import shiki from '@comark/vue/plugins/shiki'
import security from '@comark/vue/plugins/security'
const plugins = [shiki(), security({ blockedTags: ['script', 'style', 'iframe', 'object', 'embed', 'form'] })]
const input = ref('')
const { messages, status, error, sendMessage, regenerate } = useChat()
const groups = computed(() => [{
id: 'ai',
ignoreFilter: true,
items: [{
label: searchTerm.value ? `Ask AI for “${searchTerm.value}”` : 'Ask AI',
icon: 'i-lucide-bot',
onSelect: (e: any) => {
e.preventDefault()
ai.value = true
if (searchTerm.value) {
messages.value = [...messages.value, {
id: '1',
role: 'user',
parts: [{ type: 'text', text: searchTerm.value }]
}]
regenerate()
}
}
}]
}])
const ai = ref(false)
const searchTerm = ref('')
function onSubmit() {
if (!input.value.trim()) return
sendMessage({ text: input.value })
input.value = ''
}
function onClose(e: Event) {
e.preventDefault()
ai.value = false
}
const ui = {
prose: {
p: { base: 'my-2 leading-6' },
li: { base: 'my-0.5 leading-6' },
ul: { base: 'my-2' },
ol: { base: 'my-2' },
h1: { base: 'text-xl my-2' },
h2: { base: 'text-lg my-2' },
h3: { base: 'text-base my-2' },
h4: { base: 'text-sm my-2' },
pre: { root: 'my-2' },
table: { root: 'my-2' },
hr: { base: 'my-2' }
}
}
</script>
<template>
<UContentSearch v-model:search-term="searchTerm" open :groups="groups">
<template v-if="ai" #content>
<UTheme :ui="ui">
<UChatPalette>
<UChatMessages
:messages="messages"
:status="status"
:user="{ side: 'left', variant: 'naked', avatar: { src: 'https://github.com/benjamincanac.png', loading: 'lazy' as const } }"
:assistant="{ icon: 'i-lucide-bot' }"
>
<template #content="{ message }">
<template v-for="(part, index) in message.parts" :key="`${message.id}-${part.type}-${index}`">
<template v-if="isTextUIPart(part)">
<Markdown
v-if="message.role === 'assistant'"
:value="part.text"
:streaming="isPartStreaming(part)"
:plugins="plugins"
class="*:first:mt-0 *:last:mb-0"
/>
<p v-else-if="message.role === 'user'" class="whitespace-pre-wrap leading-6">
{{ part.text }}
</p>
</template>
</template>
</template>
</UChatMessages>
<template #prompt>
<UChatPrompt
v-model="input"
icon="i-lucide-search"
variant="naked"
:error="error"
@submit="onSubmit"
@close="onClose"
/>
</template>
</UChatPalette>
</UTheme>
</template>
</UContentSearch>
</template>
API (en inglés)
Propciones
| Prop | Default | Type |
|---|---|---|
as | 'div' | anyThe element or component this component should render as. |
ui | { root?: SlotClass; prompt?: SlotClass; close?: SlotClass; content?: SlotClass; } |
Slots
| Slot | Type |
|---|---|
default | {} |
prompt | {} |
Temas
app.config.ts
export default defineAppConfig({
ui: {
chatPalette: {
slots: {
root: 'relative flex-1 flex flex-col min-h-0 min-w-0',
prompt: 'px-0 rounded-t-none border-t border-default',
close: '',
content: 'overflow-y-auto flex-1 flex flex-col py-3'
}
}
}
})vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
chatPalette: {
slots: {
root: 'relative flex-1 flex flex-col min-h-0 min-w-0',
prompt: 'px-0 rounded-t-none border-t border-default',
close: '',
content: 'overflow-y-auto flex-1 flex flex-col py-3'
}
}
}
})
]
})Changelog (Edición española)
No recent changes