chattool
Utilisation
Le composant ChatTool rend un bloc pliable qui affiche le statut d'appel de l'outil d'IA, tel que "Recherche de composants" ou "Lecture de documentation". Lorsqu 'un emplacement par défaut est fourni, il devient pliable pour révéler la sortie de l'outil.
$ 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>Texte écrit
Utilisez la prop text pour définir le texte d'état de l'outil.
<template>
<UChatTool text="Searched components" />
</template>suffixe
Utilisez le prop suffix pour afficher le texte secondaire après l'étiquette principale.
<template>
<UChatTool text="Reading component" suffix="Button" />
</template>Streaming écouter
Utilisez la prop streaming pour indiquer que l'outil est activement en cours d'exécution. Le texte affiche une animation de miroitement.
<template>
<UChatTool streaming text="Searching components..." />
</template>isToolStreaming de @nuxt/ui/utils/ai pour déterminer si une pièce d'outil est toujours en cours d'exécution. Il renvoie false lorsque l'outil est en attente d'une approbation de l'utilisateur.écran
Lors du streaming, l'étiquette de déclenchement utilise le composant ChatShimmer. Utilisez la prop shimmer pour personnaliser ses duration et spread.
<template>
<UChatTool
streaming
text="Searching components..."
:shimmer="{
duration: 2,
spread: 2
}"
/>
</template>icône
Utilisez la prop icon pour afficher un composant Icon à côté du déclencheur.
<template>
<UChatTool icon="i-lucide-search" text="Searched components" />
</template>Chargement
Utilisez le prop loading pour afficher un indicateur de chargement. Utilisez le prop loading-icon pour personnaliser l'icône de chargement.
<template>
<UChatTool loading text="Searching components..." />
</template>Icône de chargement
Utilisez la prop loading-icon pour personnaliser l'icône de chargement. Par défaut, i-lucide-loader-circle.
<template>
<UChatTool loading loading-icon="i-lucide-loader" text="Searching components..." />
</template>app.config.ts sous la touche ui.icons.loading.vite.config.ts sous la touche ui.icons.loading.chevron
Utilisez le prop chevron pour modifier la position de l'icône du chevron.
chevron est réglé sur leading avec un icon, l'icône change avec le chevron en survol et lorsqu 'il est ouvert.<template>
<UChatTool chevron="leading" icon="i-lucide-search" text="Searched components">
Tool output content
</UChatTool>
</template>Icône ### Chevron
Utilisez la prop chevron-icon pour personnaliser le chevron Icon.
<template>
<UChatTool chevron-icon="i-lucide-arrow-down" text="Searched components">
Tool output content
</UChatTool>
</template>app.config.ts sous la touche ui.icons.chevronDown.vite.config.ts sous la touche ui.icons.chevronDown.Variant
Utilisez la prop variant pour modifier le style visuel. Par défaut, inline.
<template>
<UChatTool variant="card" text="Searched components" icon="i-lucide-search" chevron="trailing">
Tool output content
</UChatTool>
</template>Actions: badge{label="4.10+" class="align-text-top"}
Utilisez la prop actions pour afficher une liste de Button sous le déclencheur, utile pour les outils qui nécessitent une confirmation de l'utilisateur avant de s'exécuter.
<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>Exemples
Avec flux d'approbation: badge{label="4.10+" class="align-text-top"}
Utilisez la prop actions pour créer un flux d'approbation d'outil avec le AI SDK. Lorsqu 'une pièce d'outil est dans l'état approval-requested, affichez les actions approuver et refuser et répondez avec 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 de @nuxt/ui/utils/ai pour détecter une approbation en attente, isToolStreaming renvoie false dans cet état.<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 | {} |
Emits
| Event | Type |
|---|---|
update:open | [value: boolean] |
Thème
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'
]
}
}
}
}
}
})
]
})