ChatTool

Geef de aanroepstatus van een opvouwbare AI-tool weer.

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.

<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>
Gebruik het hulpprogramma 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>
U kunt dit pictogram globaal aanpassen in uw app.config.ts onder ui.icons.loading-sleutel.
U kunt dit pictogram globaal aanpassen in uw vite.config.ts onder ui.icons.loading-toets.

Chevron

Gebruik de chevron prop om de positie van het chevron icoon te veranderen.

Wanneer 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>
U kunt dit pictogram globaal aanpassen in uw app.config.ts onder ui.icons.chevronDown-sleutel.
U kunt dit pictogram globaal aanpassen in uw vite.config.ts onder ui.icons.chevronDown-sleutel.

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.

$ pnpm run lint
<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

Bekijk de Chat overzichtspagina voor installatie-instructies, serverinstellingen en gebruiksvoorbeelden.

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>
Gebruik het hulpprogramma 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
string

The text content to display.

suffix
string

The suffix text displayed after the main text.

icon
any

The icon displayed next to the trigger.

loading
false
boolean

Whether the tool is in a loading state.

loadingIcon
appConfig.ui.icons.loading
any

The 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
any

The icon displayed as the chevron.

shimmer
Partial<Omit<ChatShimmerProps, "text">>

Customize the ChatShimmer component when streaming.

actions
ButtonProps[]

Display a list of actions below the trigger, useful for tool approval flows. { size: 'xs' }

disabled
boolean

When true, prevents the user from interacting with the collapsible.

open
undefined
boolean

The controlled open state of the collapsible. Can be binded with v-model.

defaultOpen
boolean

The open state of the collapsible when it is initially rendered.
Use when you do not need to control its open state.

unmountOnHide
false
boolean

When true, the element will be unmounted on closed state.

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

app.config.ts
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'
            ]
          }
        }
      }
    }
  }
})
vite.config.ts
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'
                ]
              }
            }
          }
        }
      }
    })
  ]
})

Changelog

No recent changes