Repository navigation
Replies: 1 comment
|
Hey @LegendZDY! The cleanest way to do this with Nuxt UI + the AI SDK is a client-side tool. Don't ask the model to output special text that you then parse. Give it a tool like 1. Define the tool (shared by server and client)import { tool } from 'ai'
import type { InferUITools, UIDataTypes, UIMessage } from 'ai'
import { z } from 'zod'
export const tools = {
askUser: tool({
description: 'Ask the user to pick one of a few options. Use it whenever you need the user to make a choice instead of listing options in plain text.',
inputSchema: z.object({
question: z.string(),
options: z.array(z.string()).min(2).max(6)
}),
outputSchema: z.object({ choice: z.string() })
// no `execute`: the output comes from the user
})
}
export type ChatMessage = UIMessage<unknown, UIDataTypes, InferUITools<typeof tools>>2. Serverimport { streamText, convertToModelMessages, toUIMessageStream, createUIMessageStreamResponse } from 'ai'
import { tools } from '#shared/utils/tools'
export default defineEventHandler(async (event) => {
const { messages } = await readBody(event)
const result = streamText({
model: 'anthropic/claude-sonnet-5.5',
instructions: 'You are a helpful assistant.',
messages: await convertToModelMessages(messages),
tools
})
return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }) })
})3. Client<script setup lang="ts">
import { isTextUIPart, lastAssistantMessageIsCompleteWithToolCalls } from 'ai'
import { useChat } from '@ai-sdk/vue'
import { isPartStreaming } from '@nuxt/ui/utils/ai'
import type { ChatMessage } from '#shared/utils/tools'
const input = ref('')
const { messages, status, sendMessage, addToolOutput } = useChat<ChatMessage>({
// automatically send the request again once every tool call in the last message has an output
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls
})
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}`">
<div
v-if="part.type === 'tool-askUser' && (part.state === 'input-available' || part.state === 'output-available')"
class="flex flex-col gap-2"
>
<p>{{ part.input.question }}</p>
<div class="flex flex-wrap gap-2">
<UButton
v-for="option in part.input.options"
:key="option"
:label="option"
:variant="part.output?.choice === option ? 'solid' : 'outline'"
:disabled="part.state === 'output-available'"
@click="addToolOutput({ tool: 'askUser', toolCallId: part.toolCallId, output: { choice: option } })"
/>
</div>
</div>
<template v-else-if="isTextUIPart(part)">
<Markdown v-if="message.role === 'assistant'" :value="part.text" :streaming="isPartStreaming(part)" />
<p v-else class="whitespace-pre-wrap">{{ part.text }}</p>
</template>
</template>
</template>
</UChatMessages>
<UChatPrompt v-model="input" @submit="onSubmit">
<UChatPromptSubmit :status="status" />
</UChatPrompt>
</template>After the click, the part moves to For multi-choice, add |
Uh oh!
There was an error while loading. Please reload this page.
Package
v4.x
Description
Hello, I have been trying to implement an interactive UI pattern for an AI chat module. Specifically, I would like the large language model to output text along with several clickable options (e.g., option cards, multi‑choice buttons, etc.) that can be rendered directly within the chat interface and support user click interactions. Could you suggest any feasible implementation approaches or reference examples?
Additional context
No response
All reactions