ChatTool
Gebruik
De ChatTool-component geeft een opvouwbaar blok weer dat de aanroepstatus van de AI-tool weergeeft, zoals 'Onderdelen zoeken' of 'Documentatie lezen'. Wanneer een standaardsleuf is voorzien, wordt deze inklapbaar om de gereedschapsuitvoer te onthullen.
$ pnpm run lint > eslint . ✔ No lint errors found.
<script setup lang="ts">
const streaming = ref(true)
const result = ref(`$ pnpm run lint
> eslint .
✔ No lint errors found.
`)
let timer: ReturnType<typeof setTimeout> | undefined
onMounted(() => {
timer = setTimeout(() => {
streaming.value = false
}, 5000)
})
onUnmounted(() => {
clearTimeout(timer)
})
</script>
<template>
<UChatTool
:text="streaming ? 'Running lint checks' : 'Lint checks completed'"
suffix="cd, pnpm run"
:streaming="streaming"
icon="i-lucide-terminal"
variant="card"
chevron="leading"
class="w-80"
>
<pre language="bash" v-text="result" />
</UChatTool>
</template><script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
const streaming = ref(true)
const result = ref(`$ pnpm run lint
> eslint .
✔ No lint errors found.
`)
let timer: ReturnType<typeof setTimeout> | undefined
onMounted(() => {
timer = setTimeout(() => {
streaming.value = false
}, 5000)
})
onUnmounted(() => {
clearTimeout(timer)
})
</script>
<template>
<UChatTool
:text="streaming ? 'Running lint checks' : 'Lint checks completed'"
suffix="cd, pnpm run"
:streaming="streaming"
icon="i-lucide-terminal"
variant="card"
chevron="leading"
class="w-80"
>
<pre language="bash" v-text="result" />
</UChatTool>
</template>Tekst
Gebruik de text prop om de gereedschapsstatustekst in te stellen.
<template>
<UChatTool text="Searched components" />
</template>Achtervoegsel
Gebruik de suffix prop om secundaire tekst na het hoofdlabel weer te geven.
<template>
<UChatTool text="Reading component" suffix="Button" />
</template>Streamen
Gebruik de streaming prop om aan te geven dat de tool actief actief is. De tekst geeft een glinsterende animatie weer.
<template>
<UChatTool streaming text="Searching components..." />
</template>isToolStreaming van @nuxt/ui/utils/ai om te bepalen of een gereedschapsonderdeel nog steeds actief is. Het retourneert false wanneer het gereedschap wacht op goedkeuring door de gebruiker.Shimmer
Bij het streamen gebruikt het triggerlabel de ChatShimmer component. Gebruik de shimmer prop om zijn duration en spread aan te passen.
<template>
<UChatTool
streaming
text="Searching components..."
:shimmer="{
duration: 2,
spread: 2
}"
/>
</template>Icoon
Gebruik de icon prop om een Icon naast de trigger weer te geven.
<template>
<UChatTool icon="i-lucide-search" text="Searched components" />
</template>Bezig met laden
Gebruik de loading prop om een laadindicator te tonen. Gebruik de loading-icon prop om het laadpictogram aan te passen.
<template>
<UChatTool loading text="Searching components..." />
</template>Icoon aan het laden
Gebruik de loading-icon prop om het laadpictogram aan te passen. Standaard i-lucide-loader-circle.
<template>
<UChatTool loading loading-icon="i-lucide-loader" text="Searching components..." />
</template>Chevron
Gebruik de chevron prop om de positie van het chevron icoon te veranderen.
chevron is ingesteld op leading met een icon, wisselt het pictogram met de chevron bij zweven en openen.<template>
<UChatTool chevron="leading" icon="i-lucide-search" text="Searched components">
Tool output content
</UChatTool>
</template>Chevron pictogram
Gebruik de chevron-icon prop om de chevron Icon aan te passen. Standaard i-lucide-chevron-down.
<template>
<UChatTool chevron-icon="i-lucide-arrow-down" text="Searched components">
Tool output content
</UChatTool>
</template>Variant
Gebruik de variant prop om de visuele stijl te wijzigen. Standaard is inline.
<template>
<UChatTool variant="card" text="Searched components" icon="i-lucide-search" chevron="trailing">
Tool output content
</UChatTool>
</template>Acties: badge{label="4.10+" class="align-text-top"}
Gebruik de actions prop om een lijst met Button onder de trigger weer te geven, handig voor tools die een gebruikersbevestiging vereisen voordat ze worden uitgevoerd.
<template>
<UChatTool
:actions="[
{
label: 'Approve'
},
{
label: 'Deny',
color: 'neutral',
variant: 'soft'
}
]"
text="Run terminal command"
variant="card"
icon="i-lucide-terminal"
>
$ pnpm run lint
</UChatTool>
</template>Voorbeelden
Met goedkeuringsstroom: badge{label="4.10+" class="align-text-top"}
Gebruik de actions prop om een gereedschapsgoedkeuringsstroom op te bouwen met de AI SDK.
Wanneer een gereedschapsonderdeel zich in de approval-requested-status bevindt, geeft u de acties goedkeuren en weigeren weer en reageert u met addToolApprovalResponse.
$ pnpm run lint
<script setup lang="ts">
import type { ButtonProps } from '@nuxt/ui'
const state = ref<'approval-requested' | 'output-available' | 'output-denied'>('approval-requested')
const result = ref('')
const text = computed(() => {
if (state.value === 'approval-requested') return 'Run terminal command'
if (state.value === 'output-denied') return 'Command cancelled'
return result.value ? 'Ran terminal command' : 'Running terminal command'
})
const output = computed(() => result.value || '$ pnpm run lint')
const actions = computed<ButtonProps[] | undefined>(() => {
if (state.value !== 'approval-requested') return undefined
return [
{ label: 'Approve', onClick: onApprove },
{ label: 'Deny', color: 'neutral', variant: 'soft', onClick: onDeny }
]
})
let timer: ReturnType<typeof setTimeout> | undefined
function onApprove() {
state.value = 'output-available'
timer = setTimeout(() => {
result.value = `$ pnpm run lint
> eslint .
✔ No lint errors found.
`
}, 2000)
}
function onDeny() {
state.value = 'output-denied'
}
function reset() {
clearTimeout(timer)
state.value = 'approval-requested'
result.value = ''
}
onUnmounted(() => {
clearTimeout(timer)
})
</script>
<template>
<div class="flex flex-col items-start gap-4">
<UChatTool
:text="text"
icon="i-lucide-terminal"
variant="card"
:streaming="state === 'output-available' && !result"
:actions="actions"
class="w-80"
>
<pre language="bash" v-text="output" />
</UChatTool>
<UButton
v-if="state !== 'approval-requested'"
label="Reset"
color="neutral"
variant="link"
size="xs"
icon="i-lucide-rotate-ccw"
class="p-0 absolute top-4 right-4"
@click="reset"
/>
</div>
</template><script setup lang="ts">
import { ref, computed, onUnmounted } from 'vue'
import type { ButtonProps } from '@nuxt/ui'
const state = ref<'approval-requested' | 'output-available' | 'output-denied'>('approval-requested')
const result = ref('')
const text = computed(() => {
if (state.value === 'approval-requested') return 'Run terminal command'
if (state.value === 'output-denied') return 'Command cancelled'
return result.value ? 'Ran terminal command' : 'Running terminal command'
})
const output = computed(() => result.value || '$ pnpm run lint')
const actions = computed<ButtonProps[] | undefined>(() => {
if (state.value !== 'approval-requested') return undefined
return [
{ label: 'Approve', onClick: onApprove },
{ label: 'Deny', color: 'neutral', variant: 'soft', onClick: onDeny }
]
})
let timer: ReturnType<typeof setTimeout> | undefined
function onApprove() {
state.value = 'output-available'
timer = setTimeout(() => {
result.value = `$ pnpm run lint
> eslint .
✔ No lint errors found.
`
}, 2000)
}
function onDeny() {
state.value = 'output-denied'
}
function reset() {
clearTimeout(timer)
state.value = 'approval-requested'
result.value = ''
}
onUnmounted(() => {
clearTimeout(timer)
})
</script>
<template>
<div class="flex flex-col items-start gap-4">
<UChatTool
:text="text"
icon="i-lucide-terminal"
variant="card"
:streaming="state === 'output-available' && !result"
:actions="actions"
class="w-80"
>
<pre language="bash" v-text="output" />
</UChatTool>
<UButton
v-if="state !== 'approval-requested'"
label="Reset"
color="neutral"
variant="link"
size="xs"
icon="i-lucide-rotate-ccw"
class="p-0 absolute top-4 right-4"
@click="reset"
/>
</div>
</template>isToolApprovalPending van @nuxt/ui/utils/ai om een goedkeuring in afwachting te detecteren, isToolStreaming retourneert false in deze staat.<script setup lang="ts">
import { useChat } from '@ai-sdk/vue'
import { lastAssistantMessageIsCompleteWithApprovalResponses } from 'ai'
const { messages, addToolApprovalResponse } = useChat({
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses
})
</script>
<template>
<UChatTool
v-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>
API
Props
| Prop | Default | Type |
|---|---|---|
text | stringThe text content to display. | |
suffix | stringThe suffix text displayed after the main text. | |
icon | anyThe icon displayed next to the trigger. | |
loading | false | boolean Whether the tool is in a loading state. |
loadingIcon | appConfig.ui.icons.loading | anyThe icon displayed when loading. |
streaming | false | boolean Whether the tool content is currently streaming. |
variant | 'inline' | "inline" | "card"The visual variant of the tool display. |
chevron | 'trailing' | "leading" | "trailing"The position of the chevron icon. |
chevronIcon | appConfig.ui.icons.chevronDown | anyThe icon displayed as the chevron. |
shimmer | Partial<Omit<ChatShimmerProps, "text">>Customize the | |
actions | ButtonProps[]Display a list of actions below the trigger, useful for tool approval flows.
| |
disabled | boolean When | |
open | undefined | boolean The controlled open state of the collapsible. Can be binded with |
defaultOpen | boolean The open state of the collapsible when it is initially rendered. | |
unmountOnHide | false | boolean When |
ui | { root?: SlotClass; trigger?: SlotClass; leading?: SlotClass; leadingIcon?: SlotClass; chevronIcon?: SlotClass; label?: SlotClass; suffix?: SlotClass; trailingIcon?: SlotClass; content?: SlotClass; body?: SlotClass; actions?: SlotClass; } |
Slots
| Slot | Type |
|---|---|
default | { open: boolean; } |
actions | {} |
Uitzendt
| Event | Type |
|---|---|
update:open | [value: boolean] |
Thema
export default defineAppConfig({
ui: {
chatTool: {
slots: {
root: '',
trigger: [
'group flex w-full items-center gap-1.5 text-muted text-sm disabled:cursor-default disabled:hover:text-muted hover:text-default min-w-0',
'transition-colors'
],
leading: 'relative size-4 shrink-0',
leadingIcon: 'size-4 shrink-0',
chevronIcon: 'size-4 shrink-0 group-data-[state=open]:rotate-180 transition-transform duration-200 ease-out motion-reduce:transition-none',
label: 'truncate',
suffix: 'text-dimmed ms-1',
trailingIcon: 'size-4 shrink-0 group-data-[state=open]:rotate-180 transition-transform duration-200 ease-out motion-reduce:transition-none',
content: 'data-[state=open]:animate-[collapsible-down_200ms_var(--ease-out)] data-[state=closed]:animate-[collapsible-up_200ms_var(--ease-out)] data-[state=closed]:overflow-hidden',
body: 'text-sm text-dimmed whitespace-pre-wrap',
actions: 'flex items-center justify-end gap-1.5'
},
variants: {
variant: {
inline: {
trigger: 'rounded-sm outline-primary/25 focus-visible:outline-3',
body: 'pt-2',
actions: 'pt-2'
},
card: {
root: 'rounded-md ring ring-default overflow-hidden outline-primary/25 has-focus-visible:outline-3 has-focus-visible:ring-primary',
trigger: 'px-2 py-1 focus:outline-none',
trailingIcon: 'ms-auto',
body: 'border-t border-default p-2 max-h-[200px] overflow-y-auto focus:outline-none',
actions: 'border-t border-default p-2'
}
},
chevron: {
leading: '',
trailing: ''
},
loading: {
true: {
leadingIcon: 'animate-spin'
}
},
alone: {
false: {
leadingIcon: [
'absolute inset-0 group-hover:opacity-0 group-data-[state=open]:opacity-0',
'transition-opacity duration-200 ease-out'
],
chevronIcon: [
'absolute inset-0 opacity-0 group-hover:opacity-100 group-data-[state=open]:opacity-100',
'transition-[rotate,opacity] duration-200 ease-out motion-reduce:transition-none'
]
}
}
}
}
}
})import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
chatTool: {
slots: {
root: '',
trigger: [
'group flex w-full items-center gap-1.5 text-muted text-sm disabled:cursor-default disabled:hover:text-muted hover:text-default min-w-0',
'transition-colors'
],
leading: 'relative size-4 shrink-0',
leadingIcon: 'size-4 shrink-0',
chevronIcon: 'size-4 shrink-0 group-data-[state=open]:rotate-180 transition-transform duration-200 ease-out motion-reduce:transition-none',
label: 'truncate',
suffix: 'text-dimmed ms-1',
trailingIcon: 'size-4 shrink-0 group-data-[state=open]:rotate-180 transition-transform duration-200 ease-out motion-reduce:transition-none',
content: 'data-[state=open]:animate-[collapsible-down_200ms_var(--ease-out)] data-[state=closed]:animate-[collapsible-up_200ms_var(--ease-out)] data-[state=closed]:overflow-hidden',
body: 'text-sm text-dimmed whitespace-pre-wrap',
actions: 'flex items-center justify-end gap-1.5'
},
variants: {
variant: {
inline: {
trigger: 'rounded-sm outline-primary/25 focus-visible:outline-3',
body: 'pt-2',
actions: 'pt-2'
},
card: {
root: 'rounded-md ring ring-default overflow-hidden outline-primary/25 has-focus-visible:outline-3 has-focus-visible:ring-primary',
trigger: 'px-2 py-1 focus:outline-none',
trailingIcon: 'ms-auto',
body: 'border-t border-default p-2 max-h-[200px] overflow-y-auto focus:outline-none',
actions: 'border-t border-default p-2'
}
},
chevron: {
leading: '',
trailing: ''
},
loading: {
true: {
leadingIcon: 'animate-spin'
}
},
alone: {
false: {
leadingIcon: [
'absolute inset-0 group-hover:opacity-0 group-data-[state=open]:opacity-0',
'transition-opacity duration-200 ease-out'
],
chevronIcon: [
'absolute inset-0 opacity-0 group-hover:opacity-100 group-data-[state=open]:opacity-100',
'transition-[rotate,opacity] duration-200 ease-out motion-reduce:transition-none'
]
}
}
}
}
}
})
]
})