diff --git a/apps/www/content/3.components/1.chatbot/tool.md b/apps/www/content/3.components/1.chatbot/tool.md new file mode 100644 index 0000000..0ccfd2a --- /dev/null +++ b/apps/www/content/3.components/1.chatbot/tool.md @@ -0,0 +1,580 @@ +--- +title: Tool +description: A collapsible component for displaying tool invocation details in AI chatbot interfaces. +icon: lucide:tool-case +--- + +The `Tool` component isplays a collapsible interface for showing/hiding tool details. It is designed to take the `ToolUIPart` type from the AI SDK and display it in a collapsible interface. + +:::ComponentLoader{label="Preview" componentName="Tool"} +::: + +## Install using CLI + +::tabs{variant="card"} + ::div{label="ai-elements-vue"} + ```sh + npx ai-elements-vue@latest add tool + ``` + :: + ::div{label="shadcn-vue"} + + ```sh + npx shadcn-vue@latest add https://registry.ai-elements-vue.com/tool.json + ``` + :: +:: + +## Install Manually + +Copy and paste the following code in the same folder. + +:::code-group +```vue [Tool.vue] height=260 collapse + + + +``` + +```vue [ToolStatusBadge.vue] height=260 collapse + + + + +``` + +```vue [ToolHeader.vue] height=260 collapse + + + +``` + +```vue [ToolContent.vue] height=260 collapse + + + +``` + +```vue [ToolInput.vue] height=260 collapse + + + +``` + +```vue [ToolOutput.vue] height=260 collapse + + + +``` + +```ts [index.ts] height=260 collapse +export { default as Tool } from './Tool.vue' +export { default as ToolContent } from './ToolContent.vue' +export { default as ToolHeader } from './ToolHeader.vue' +export { default as ToolInput } from './ToolInput.vue' +export { default as ToolOutput } from './ToolOutput.vue' +``` +::: + +## Usage with AI SDK + +Build a simple stateful weather app that renders the last message in a tool using [useChat](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat). + +Add the following component to your frontend: + +```vue [pages/index.vue] height=260 collapse + + + +``` + +Add the following route to your backend: + +```ts [server/api/agent.ts] height=260 collapse +import { convertToModelMessages, streamText, UIMessage } from 'ai' +import { defineEventHandler, readBody } from 'h3' +import { z } from 'zod' + +// Allow streaming responses up to 30 seconds +export const maxDuration = 30 + +export default defineEventHandler(async (event) => { + const body = await readBody(event) as { messages: UIMessage[] } + const { messages } = body + + const result = streamText({ + model: 'openai/gpt-4o', + messages: convertToModelMessages(messages), + tools: { + fetch_weather_data: { + description: 'Fetch weather information for a specific location', + parameters: z.object({ + location: z.string().describe('The city or location to get weather for'), + units: z.enum(['celsius', 'fahrenheit']).default('celsius').describe('Temperature units'), + }), + inputSchema: z.object({ + location: z.string(), + units: z.enum(['celsius', 'fahrenheit']).default('celsius'), + }), + execute: async ({ location, units }) => { + await new Promise(resolve => setTimeout(resolve, 1500)) + const temp = units === 'celsius' + ? Math.floor(Math.random() * 35) + 5 + : Math.floor(Math.random() * 63) + 41 + + return { + location, + temperature: `${temp}°${units === 'celsius' ? 'C' : 'F'}`, + conditions: 'Sunny', + humidity: '12%', + windSpeed: `35 ${units === 'celsius' ? 'km/h' : 'mph'}`, + lastUpdated: new Date().toLocaleString(), + } + }, + }, + }, + }) + + return result.toUIMessageStreamResponse() +}) +``` + +## Features + +- Collapsible interface for showing/hiding tool details +- Visual status indicators with icons and badges +- Support for multiple tool execution states (pending, running, completed, error) +- Formatted parameter display with JSON syntax highlighting +- Result and error handling with appropriate styling +- Composable structure for flexible layouts +- Accessible keyboard navigation and screen reader support +- Consistent styling that matches your design system +- Auto-opens completed tools by default for better UX + +## Examples + +### Input Streaming (Pending) + +Shows a tool in its initial state while parameters are being processed. + +:::ComponentLoader{label="Preview" componentName="ToolInputStreaming"} +::: + +### Input Available (Running) + +Shows a tool that's actively executing with its parameters. + +:::ComponentLoader{label="Preview" componentName="ToolInputAvailable"} +::: + +### Input Streaming (Completed) + +Shows a completed tool with successful results. Opens by default to show the results. In this instance, the output is a JSON object, so we can use the `CodeBlock` component to display it. + +:::ComponentLoader{label="Preview" componentName="ToolOutputAvailable"} +::: + +### Output Error + +Shows a tool that encountered an error during execution. Opens by default to display the error. + +:::ComponentLoader{label="Preview" componentName="ToolOutputError"} +::: + +## Props + +### `` + +:::field-group + ::field{name="class" type="string"} + Additional CSS classes to apply to the component. + :: +::: + +### `` + +:::field-group + ::field{name="type" type="ToolUIPart['type']"} + The type/name of the tool. + :: + + ::field{name="state" type="ToolUIPart['state']"} + The current state of the tool (input-streaming, input-available, output-available, or output-error). + :: + + ::field{name="title" type="string"} + The title of the task. + :: + + ::field{name="class" type="string"} + Additional CSS classes to apply to the component. + :: +::: + +### `` + +:::field-group + ::field{name="class" type="string"} + Additional CSS classes to apply to the component. + :: +::: + +### `` + +:::field-group + ::field{name="input" type="ToolUIPart['input']"} + The input parameters passed to the tool, displayed as formatted JSON. + :: + + ::field{name="class" type="string"} + Additional CSS classes to apply to the component. + :: +::: + +### `` + +:::field-group + ::field{name="output" type="ToolUIPart['output']"} + The output/result of the tool execution. + :: + + ::field{name="errorText" type="ToolUIPart['errorText']"} + An error message if the tool execution failed. + :: + + ::field{name="class" type="string"} + Additional CSS classes to apply to the component. + :: +::: diff --git a/apps/www/plugins/ai-elements.ts b/apps/www/plugins/ai-elements.ts index ecc51da..f28cbb7 100644 --- a/apps/www/plugins/ai-elements.ts +++ b/apps/www/plugins/ai-elements.ts @@ -36,6 +36,11 @@ import { Suggestion, SuggestionAiInput, Task, + Tool, + ToolInputAvailable, + ToolInputStreaming, + ToolOutputAvailable, + ToolOutputError, Workflow, } from '@repo/examples' @@ -80,6 +85,11 @@ export default defineNuxtPlugin((nuxtApp) => { vueApp.component('CodeBlockDark', CodeBlockDark) vueApp.component('Checkpoint', Checkpoint) vueApp.component('Workflow', Workflow) + vueApp.component('Tool', Tool) + vueApp.component('ToolInputStreaming', ToolInputStreaming) + vueApp.component('ToolInputAvailable', ToolInputAvailable) + vueApp.component('ToolOutputAvailable', ToolOutputAvailable) + vueApp.component('ToolOutputError', ToolOutputError) vueApp.component('ModelSelector', ModelSelector) vueApp.component('Context', Context) vueApp.component('Confirmation', Confirmation) diff --git a/packages/elements/src/index.ts b/packages/elements/src/index.ts index a570bc5..5384f73 100644 --- a/packages/elements/src/index.ts +++ b/packages/elements/src/index.ts @@ -21,3 +21,4 @@ export * from './shimmer' export * from './sources' export * from './suggestion' export * from './task' +export * from './tool' diff --git a/packages/elements/src/tool/Tool.vue b/packages/elements/src/tool/Tool.vue new file mode 100644 index 0000000..5dd4800 --- /dev/null +++ b/packages/elements/src/tool/Tool.vue @@ -0,0 +1,18 @@ + + + diff --git a/packages/elements/src/tool/ToolContent.vue b/packages/elements/src/tool/ToolContent.vue new file mode 100644 index 0000000..ae2d817 --- /dev/null +++ b/packages/elements/src/tool/ToolContent.vue @@ -0,0 +1,23 @@ + + + diff --git a/packages/elements/src/tool/ToolHeader.vue b/packages/elements/src/tool/ToolHeader.vue new file mode 100644 index 0000000..1dc65b6 --- /dev/null +++ b/packages/elements/src/tool/ToolHeader.vue @@ -0,0 +1,38 @@ + + + diff --git a/packages/elements/src/tool/ToolInput.vue b/packages/elements/src/tool/ToolInput.vue new file mode 100644 index 0000000..1d9eade --- /dev/null +++ b/packages/elements/src/tool/ToolInput.vue @@ -0,0 +1,32 @@ + + + diff --git a/packages/elements/src/tool/ToolOutput.vue b/packages/elements/src/tool/ToolOutput.vue new file mode 100644 index 0000000..19fbbd6 --- /dev/null +++ b/packages/elements/src/tool/ToolOutput.vue @@ -0,0 +1,69 @@ + + + diff --git a/packages/elements/src/tool/ToolStatusBadge.vue b/packages/elements/src/tool/ToolStatusBadge.vue new file mode 100644 index 0000000..023dc29 --- /dev/null +++ b/packages/elements/src/tool/ToolStatusBadge.vue @@ -0,0 +1,63 @@ + + + + diff --git a/packages/elements/src/tool/index.ts b/packages/elements/src/tool/index.ts new file mode 100644 index 0000000..a3894f2 --- /dev/null +++ b/packages/elements/src/tool/index.ts @@ -0,0 +1,5 @@ +export { default as Tool } from './Tool.vue' +export { default as ToolContent } from './ToolContent.vue' +export { default as ToolHeader } from './ToolHeader.vue' +export { default as ToolInput } from './ToolInput.vue' +export { default as ToolOutput } from './ToolOutput.vue' diff --git a/packages/examples/src/index.ts b/packages/examples/src/index.ts index 56e8c68..113a3c5 100644 --- a/packages/examples/src/index.ts +++ b/packages/examples/src/index.ts @@ -35,4 +35,9 @@ export { default as Sources } from './sources.vue' export { default as SuggestionAiInput } from './suggestion-ai-input.vue' export { default as Suggestion } from './suggestion.vue' export { default as Task } from './task.vue' +export { default as ToolInputAvailable } from './tool-input-available.vue' +export { default as ToolInputStreaming } from './tool-input-streaming.vue' +export { default as ToolOutputAvailable } from './tool-output-available.vue' +export { default as ToolOutputError } from './tool-output-error.vue' +export { default as Tool } from './tool.vue' export { default as Workflow } from './workflow.vue' diff --git a/packages/examples/src/tool-input-available.vue b/packages/examples/src/tool-input-available.vue new file mode 100644 index 0000000..30e0c28 --- /dev/null +++ b/packages/examples/src/tool-input-available.vue @@ -0,0 +1,29 @@ + + + diff --git a/packages/examples/src/tool-input-streaming.vue b/packages/examples/src/tool-input-streaming.vue new file mode 100644 index 0000000..018eae0 --- /dev/null +++ b/packages/examples/src/tool-input-streaming.vue @@ -0,0 +1,28 @@ + + + diff --git a/packages/examples/src/tool-output-available.vue b/packages/examples/src/tool-output-available.vue new file mode 100644 index 0000000..7c31606 --- /dev/null +++ b/packages/examples/src/tool-output-available.vue @@ -0,0 +1,35 @@ + + + diff --git a/packages/examples/src/tool-output-error.vue b/packages/examples/src/tool-output-error.vue new file mode 100644 index 0000000..23ca231 --- /dev/null +++ b/packages/examples/src/tool-output-error.vue @@ -0,0 +1,37 @@ + + + diff --git a/packages/examples/src/tool.vue b/packages/examples/src/tool.vue new file mode 100644 index 0000000..60d72d8 --- /dev/null +++ b/packages/examples/src/tool.vue @@ -0,0 +1,212 @@ + + +