Das Chattool
Bearbeiten
Die ChatTool-Komponente rendert einen zusammenklappbaren Block, der den Aufrufstatus des KI-Tools anzeigt, z. B. „ Komponenten suchen "oder „ Dokumentation lesen". Wenn ein Standard-Slot bereitgestellt wird, wird er zusammenklappbar, um die Werkzeugausgabe anzuzeigen.
$ 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>Text Bearbeiten
Verwenden Sie die text-Prop, um den Werkzeugstatustext festzulegen.
<template>
<UChatTool text="Searched components" />
</template>Suffix (englisch)
Verwenden Sie die suffix-Prop, um sekundären Text nach dem Hauptlabel anzuzeigen.
<template>
<UChatTool text="Reading component" suffix="Button" />
</template>Streaming (englisch)
Verwenden Sie die streaming-Prop, um anzuzeigen, dass das Tool aktiv ausgeführt wird. Der Text zeigt eine Schimmer-Animation an.
<template>
<UChatTool streaming text="Searching components..." />
</template>isToolStreaming von @nuxt/ui/utils/ai, um festzustellen, ob ein Werkzeugteil noch läuft.Shimmer (nicht)
Beim Streamen verwendet das Trigger-Label die Komponente ChatShimmer. Verwenden Sie die shimmer-Prop, um die duration und spread anzupassen.
<template>
<UChatTool
streaming
text="Searching components..."
:shimmer="{
duration: 2,
spread: 2
}"
/>
</template>Icon (nicht)
Verwenden Sie die icon-Prop, um eine Icon-Komponente neben dem Trigger anzuzeigen.
<template>
<UChatTool icon="i-lucide-search" text="Searched components" />
</template>loading (englisch)
Verwenden Sie die loading-prop, um eine Ladeanzeige anzuzeigen. Verwenden Sie die loading-icon-prop, um das Ladesymbol anzupassen.
<template>
<UChatTool loading text="Searching components..." />
</template>Loading Icon [Bearbeiten | Quelltext bearbeiten
Verwenden Sie die loading-icon-prop, um das Ladesymbol anzupassen. Standardmäßig ist i-lucide-loader-circle.
<template>
<UChatTool loading loading-icon="i-lucide-loader" text="Searching components..." />
</template>Chevron Bearbeiten
Verwenden Sie die chevron-Prop, um die Position des Chevron-Symbols zu ändern.
chevron mit einem icon auf leading gesetzt ist, wechselt das Symbol mit dem Chevron auf Hover und wenn es geöffnet ist.<template>
<UChatTool chevron="leading" icon="i-lucide-search" text="Searched components">
Tool output content
</UChatTool>
</template>Chevron Icon (Deutsche Ausgabe)
Verwenden Sie die chevron-icon-Prop, um den chevron Icon. Defaults auf i-lucide-chevron-down.
<template>
<UChatTool chevron-icon="i-lucide-arrow-down" text="Searched components">
Tool output content
</UChatTool>
</template>app.config.ts unter ui.icons.chevronDown Schlüssel anpassen.vite.config.ts unter ui.icons.chevronDown Schlüssel anpassen.Variant Bearbeiten
Verwenden Sie die variant-prop, um den visuellen Stil zu ändern. Standardmäßig auf 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"} (Aktion)
Verwenden Sie die actions-Prop, um eine Liste von Button unterhalb des Auslösers anzuzeigen, nützlich für Tools, die vor der Ausführung eine Benutzerbestätigung benötigen.
<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>Beispiele
With Genehmigungsfluss: badge{label="4.10+" class="align-text-top"}
Verwenden Sie die actions-Prop, um einen Werkzeuggenehmigungsfluss mit dem [AI SDK](https://ai-sdk.dev/docs/agents/tool-approvalsxph22x zu erstellen. Wenn sich ein Werkzeugteil im approval-requested-Status befindet, zeigen Sie die Aktionen zum Genehmigen und Ablehnen an und antworten Sie mit 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-Dienstprogramm von @nuxt/ui/utils/ai, um eine ausstehende Genehmigung zu erkennen, isToolStreaming gibt false in diesem Zustand zurück.<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 (nicht)
| 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 Bearbeiten
| Slot | Type |
|---|---|
default | { open: boolean; } |
actions | {} |
Emits (englisch)
| Event | Type |
|---|---|
update:open | [value: boolean] |
Theme Bearbeiten
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'
]
}
}
}
}
}
})
]
})