Chat
Nuxt UI biedt een set componenten die zijn ontworpen om door AI aangedreven chatinterfaces te bouwen. Ze integreren naadloos met de Vercel AI SDK voor het streamen van reacties, redeneren, aanroepen van tools en meer.
Componenten
| Component | Omschrijving |
|---|---|
| ChatMessages | Doorschuifbare berichtenlijst met auto-scroll en laadindicator. |
| ChatMessage | Individuele berichtenballon met avatar, acties en slots. |
| ChatPrompt | Verbeterd tekstgebied voor het indienen van prompts. |
| ChatPromptSubmit | Indienknop met automatische statusafhandeling. |
| ChatReasoning | Inklapbaar blok voor AI-redeneer- / denkproces. |
| ChatTool | Inklapbaar blok voor aanroepstatus AI-tool. |
| ChatShimmer | Tekst glinsterende animatie voor streaming-staten. |
| ChatPalette | Layout wrapper voor het insluiten van chat in modals of lades. |
Installatie
De Chat-componenten zijn ontworpen om te worden gebruikt met de Vercel AI SDK, met name de Chat klasse voor het beheren van de chatstatus en het streamen van reacties.
De voorbeelden op deze pagina zijn gericht op AI SDK v7.
Installeer de vereiste afhankelijkheden:
pnpm add ai @ai-sdk/gateway @ai-sdk/vue @comark/nuxt
yarn add ai @ai-sdk/gateway @ai-sdk/vue @comark/nuxt
npm install ai @ai-sdk/gateway @ai-sdk/vue @comark/nuxt
bun add ai @ai-sdk/gateway @ai-sdk/vue @comark/nuxt
Voeg @comark/nuxt toe aan uw modules:
export default defineNuxtConfig({
modules: [
'@nuxt/ui',
'@comark/nuxt'
]
})
@comark/nuxt biedt de Markdown-component die wordt gebruikt om AI-reacties weer te geven als Markdown, het geeft stapsgewijs tokens weer wanneer ze aankomen, waardoor de flikkering en herparsering wordt vermeden die traditionele Markdown renderers veroorzaken.
Het schakelt ook automatisch de prose- components van Nuxt UI in, zodat uw inhoud is afgestemd op uw thema.pnpm add ai @ai-sdk/gateway @ai-sdk/vue @comark/vue
yarn add ai @ai-sdk/gateway @ai-sdk/vue @comark/vue
npm install ai @ai-sdk/gateway @ai-sdk/vue @comark/vue
bun add ai @ai-sdk/gateway @ai-sdk/vue @comark/vue
@comark/vue biedt de Markdown-component die wordt gebruikt om AI-reacties weer te geven als Markdown, het geeft stapsgewijs tokens weer wanneer ze aankomen, waardoor de flikkering en herparsering wordt vermeden die traditionele Markdown renderers veroorzaken.
Als u de prose components van Nuxt UI met Comark wilt gebruiken, schakelt u de optie
prose in uw vite.config.ts in:import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
prose: true
})
]
})
Server instellen
Maak een server-API-eindpunt om chatverzoeken af te handelen met streamText. U kunt de Vercel AI- Gateway gebruiken om toegang te krijgen tot AI-modellen via een gecentraliseerd eindpunt:
import { streamText, convertToModelMessages, toUIMessageStream, createUIMessageStreamResponse } from 'ai'
import { gateway } from '@ai-sdk/gateway'
export default defineEventHandler(async (event) => {
const { messages } = await readBody(event)
const result = streamText({
model: gateway('anthropic/claude-sonnet-5'),
maxOutputTokens: 10000,
instructions: 'You are a helpful assistant.',
messages: await convertToModelMessages(messages)
})
const stream = toUIMessageStream({ stream: result.stream })
return createUIMessageStreamResponse({ stream })
})
Redeneren
Als u reasoning wilt inschakelen, configureert u providerOptions voor uw provider (Anthropic, Google, OpenAI):
import { streamText, convertToModelMessages, toUIMessageStream, createUIMessageStreamResponse } from 'ai'
import { gateway } from '@ai-sdk/gateway'
export default defineEventHandler(async (event) => {
const { messages } = await readBody(event)
const result = streamText({
model: gateway('anthropic/claude-sonnet-5'),
maxOutputTokens: 10000,
instructions: 'You are a helpful assistant.',
messages: await convertToModelMessages(messages),
providerOptions: {
anthropic: {
thinking: {
type: 'adaptive'
},
effort: 'low'
},
google: {
thinkingConfig: {
includeThoughts: true,
thinkingLevel: 'low'
}
},
openai: {
reasoningEffort: 'low',
reasoningSummary: 'detailed'
}
}
})
const stream = toUIMessageStream({ stream: result.stream })
return createUIMessageStreamResponse({ stream })
})
Web Zoeken
Sommige providers bieden ingebouwde webzoekhulpmiddelen: Anthropic, Google, OpenAI.
import { streamText, convertToModelMessages, toUIMessageStream, createUIMessageStreamResponse } from 'ai'
import { anthropic } from '@ai-sdk/anthropic'
import { gateway } from '@ai-sdk/gateway'
export default defineEventHandler(async (event) => {
const { messages } = await readBody(event)
const result = streamText({
model: gateway('anthropic/claude-sonnet-5'),
instructions: 'You are a helpful assistant.',
messages: await convertToModelMessages(messages),
tools: {
web_search: anthropic.tools.webSearch_20260209({})
}
})
const stream = toUIMessageStream({ stream: result.stream })
return createUIMessageStreamResponse({ stream })
})
import { streamText, convertToModelMessages, toUIMessageStream, createUIMessageStreamResponse } from 'ai'
import { google } from '@ai-sdk/google'
import { gateway } from '@ai-sdk/gateway'
export default defineEventHandler(async (event) => {
const { messages } = await readBody(event)
const result = streamText({
model: gateway('google/gemini-3-flash'),
instructions: 'You are a helpful assistant.',
messages: await convertToModelMessages(messages),
tools: {
google_search: google.tools.googleSearch({})
}
})
const stream = toUIMessageStream({ stream: result.stream })
return createUIMessageStreamResponse({ stream })
})
import { streamText, convertToModelMessages, toUIMessageStream, createUIMessageStreamResponse } from 'ai'
import { openai } from '@ai-sdk/openai'
import { gateway } from '@ai-sdk/gateway'
export default defineEventHandler(async (event) => {
const { messages } = await readBody(event)
const result = streamText({
model: gateway('openai/gpt-5-nano'),
instructions: 'You are a helpful assistant.',
messages: await convertToModelMessages(messages),
tools: {
web_search: openai.tools.webSearch({})
}
})
const stream = toUIMessageStream({ stream: result.stream })
return createUIMessageStreamResponse({ stream })
})
MCP Opdrachtgever
Geef uw chatbot geavanceerde functies voor het aanroepen van gereedschappen met behulp van het Model Context Protocol (MCP) van @ai-sdk/mcp.
MCP stelt uw AI in staat dynamische acties uit te voeren, zoals het doorzoeken van uw documentatie of het uitvoeren van aangepaste taken, om relevantere en nauwkeurigere antwoorden te geven.
Installeer om te beginnen het MCP-pakket:
npm install @ai-sdk/mcp
pnpm add @ai-sdk/mcp
yarn add @ai-sdk/mcp
Configureer vervolgens uw serverendpoint om MCP-tools te gebruiken:
import { streamText, convertToModelMessages, isStepCount, toUIMessageStream, createUIMessageStreamResponse } from 'ai'
import { createMCPClient } from '@ai-sdk/mcp'
import { gateway } from '@ai-sdk/gateway'
export default defineEventHandler(async (event) => {
const { messages } = await readBody(event)
const httpClient = await createMCPClient({
transport: { type: 'http', url: 'https://your-app.com/mcp' }
})
try {
const tools = await httpClient.tools()
const result = streamText({
model: gateway('anthropic/claude-sonnet-5'),
maxOutputTokens: 10000,
instructions: 'You are a helpful assistant. Use your tools to search for relevant information before answering questions.',
messages: await convertToModelMessages(messages),
stopWhen: isStepCount(6),
tools,
onEnd: async () => {
await httpClient.close()
},
onError: async (error) => {
console.error(error)
await httpClient.close()
}
})
const stream = toUIMessageStream({ stream: result.stream })
return createUIMessageStreamResponse({ stream })
} catch (error) {
// Close the MCP client if setup fails before streaming starts
await httpClient.close()
throw error
}
})
Tool Goedkeuring
Vereist een gebruikersbevestiging voordat een tool wordt uitgevoerd met de toolApproval optie. Het gereedschapsonderdeel pauzeert in de approval-requested-status totdat de gebruiker reageert:
import { streamText, convertToModelMessages, toUIMessageStream, createUIMessageStreamResponse, tool } from 'ai'
import { gateway } from '@ai-sdk/gateway'
import { z } from 'zod'
export default defineEventHandler(async (event) => {
const { messages } = await readBody(event)
const result = streamText({
model: gateway('anthropic/claude-sonnet-5'),
maxOutputTokens: 10000,
instructions: 'You are a helpful assistant.',
messages: await convertToModelMessages(messages),
tools: {
deleteFile: tool({
description: 'Delete a file from the project',
inputSchema: z.object({ path: z.string() }),
execute: async ({ path }) => ({ deleted: path })
})
},
toolApproval: {
deleteFile: 'user-approval'
}
})
const stream = toUIMessageStream({ stream: result.stream })
return createUIMessageStreamResponse({ stream })
})
Client instellen
Gebruik de useChat composable van @ai-sdk/vue om de chatstatus te beheren en verbinding te maken met het servereindpunt:
<script setup lang="ts">
import { isReasoningUIPart, isTextUIPart, isToolUIPart, getToolName, lastAssistantMessageIsCompleteWithApprovalResponses } from 'ai'
import { useChat } from '@ai-sdk/vue'
import { isPartStreaming, isToolStreaming } from '@nuxt/ui/utils/ai'
import shiki from '@comark/nuxt/plugins/shiki'
const plugins = [shiki()]
const input = ref('')
const { messages, status, error, sendMessage, regenerate, stop, addToolApprovalResponse } = useChat({
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
onError(error) {
console.error(error)
}
})
function onSubmit() {
sendMessage({ text: input.value })
input.value = ''
}
</script>
<template>
<UChatMessages
:messages="messages"
:status="status"
>
<template #content="{ message }">
<template
v-for="(part, index) in message.parts"
:key="`${message.id}-${part.type}-${index}`"
>
<UChatReasoning
v-if="isReasoningUIPart(part)"
:text="part.text"
:streaming="isPartStreaming(part)"
>
<Markdown
:value="part.text"
:streaming="isPartStreaming(part)"
:plugins="plugins"
class="*:first:mt-0 *:last:mb-0"
/>
</UChatReasoning>
<UChatTool
v-else-if="isToolUIPart(part)"
:text="getToolName(part)"
:streaming="isToolStreaming(part)"
:actions="part.state === 'approval-requested' ? [
{ label: 'Approve', onClick: () => addToolApprovalResponse({ id: part.approval.id, approved: true }) },
{ label: 'Deny', color: 'neutral', variant: 'ghost', onClick: () => addToolApprovalResponse({ id: part.approval.id, approved: false }) }
] : undefined"
/>
<template v-else-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">
{{ part.text }}
</p>
</template>
</template>
</template>
</UChatMessages>
<UChatPrompt
v-model="input"
:error="error"
@submit="onSubmit"
>
<UChatPromptSubmit
:status="status"
@stop="stop()"
@reload="regenerate()"
/>
</UChatPrompt>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { isReasoningUIPart, isTextUIPart, isToolUIPart, getToolName, lastAssistantMessageIsCompleteWithApprovalResponses } from 'ai'
import { useChat } from '@ai-sdk/vue'
import { isPartStreaming, isToolStreaming } from '@nuxt/ui/utils/ai'
import { Markdown } from '@comark/vue'
import shiki from '@comark/vue/plugins/shiki'
const plugins = [shiki()]
const input = ref('')
const { messages, status, error, sendMessage, regenerate, stop, addToolApprovalResponse } = useChat({
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
onError(error) {
console.error(error)
}
})
function onSubmit() {
sendMessage({ text: input.value })
input.value = ''
}
</script>
<template>
<UChatMessages
:messages="messages"
:status="status"
>
<template #content="{ message }">
<template
v-for="(part, index) in message.parts"
:key="`${message.id}-${part.type}-${index}`"
>
<UChatReasoning
v-if="isReasoningUIPart(part)"
:text="part.text"
:streaming="isPartStreaming(part)"
>
<Markdown
:value="part.text"
:streaming="isPartStreaming(part)"
:plugins="plugins"
class="*:first:mt-0 *:last:mb-0"
/>
</UChatReasoning>
<UChatTool
v-else-if="isToolUIPart(part)"
:text="getToolName(part)"
:streaming="isToolStreaming(part)"
:actions="part.state === 'approval-requested' ? [
{ label: 'Approve', onClick: () => addToolApprovalResponse({ id: part.approval.id, approved: true }) },
{ label: 'Deny', color: 'neutral', variant: 'ghost', onClick: () => addToolApprovalResponse({ id: part.approval.id, approved: false }) }
] : undefined"
/>
<template v-else-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">
{{ part.text }}
</p>
</template>
</template>
</template>
</UChatMessages>
<UChatPrompt
v-model="input"
:error="error"
@submit="onSubmit"
>
<UChatPromptSubmit
:status="status"
@stop="stop()"
@reload="regenerate()"
/>
</UChatPrompt>
</template>
defineMarkdownComponent om een aangepast onderdeel te maken in plaats van rekwisieten elke keer inline door te geven.import shiki from '@comark/nuxt/plugins/shiki'
export default defineMarkdownComponent({
name: 'ChatMarkdown',
plugins: [shiki()],
class: '*:first:mt-0 *:last:mb-0'
})
import { defineMarkdownComponent } from '@comark/vue'
import shiki from '@comark/vue/plugins/shiki'
export default defineMarkdownComponent({
name: 'ChatMarkdown',
plugins: [shiki()],
class: '*:first:mt-0 *:last:mb-0'
})
shiki-plug-in gebruikt, voegt u de volgende CSS toe aan uw stylesheet om de donkere modus te ondersteunen:html.dark .shiki span {
color: var(--shiki-dark) !important;
background-color: var(--shiki-dark-bg) !important;
font-style: var(--shiki-dark-font-style) !important;
font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important;
}