Skip to content

Chat UI ​

Полноценный интерфейс чата, код которого принадлежит вам. Компоненты добавляются в проект через shadcn-vue CLI как обычные .vue-файлы, так что их можно перестилизовать или переписать, как любой другой компонент. Поведение, в котором легко ошибиться, поставляется в пакете vue-llm и обновляется вместе с ним.

Демо использует заскриптованную модель. Компоненты — те же, что устанавливает CLI.

Как всё разделено ​

ЧастьГде находитсяОбновления
useChat, провайдеры, StreamMarkdownпакет vue-llmnpm update vue-llm
usePromptInput, useAutoScrollпакет vue-llmnpm update vue-llm
Chat, ChatMessage, ChatPromptInput, ChatReasoning, ChatToolCallкопируются в ваш проектваш код; запустите add ещё раз, чтобы подтянуть изменения из апстрима

Установка ​

Нужен проект с shadcn-vue, то есть с Tailwind CSS и lib/utils.ts.

bash
npx shadcn-vue@latest add https://vue-llm.js.org/r/chat.json

CLI устанавливает vue-llm и вашу библиотеку иконок (по умолчанию Lucide; другие библиотеки иконок CLI сопоставляет сам) и записывает компоненты в components/ui/chat/.

Использование ​

vue
<script setup lang="ts">
import { useChat } from 'vue-llm'
import { createSSEProvider } from 'vue-llm/providers'
import { Chat } from '@/components/ui/chat'

const chat = useChat({
  provider: createSSEProvider({ url: '/api/chat' }),
  model: 'gpt-4o',
})
</script>

<template>
  <div class="h-dvh">
    <Chat :chat="chat" :suggestions="['Summarize this page', 'Write a haiku']" />
  </div>
</template>

Chat заполняет родителя целиком, поэтому задайте родителю высоту.

Generative UI ​

Передайте компоненты для своих инструментов. Инструменты без компонента используют ChatToolCall — сворачиваемую карточку с аргументами и результатом.

vue
<Chat :chat="chat" :components="{ get_weather: WeatherCard, confirm_booking: BookingCard }" />

Как писать компоненты инструментов, см. в разделе Generative UI.

Компоненты ​

Chat ​

Чат целиком: список сообщений с автопрокруткой, пустое состояние с подсказками, индикатор набора, ошибка с кнопкой повтора, кнопка прокрутки вниз и поле ввода.

ПропТипОписание
chatUseChatReturnЗначение, возвращённое useChat
componentsRecord<string, Component>Имя инструмента → компонент
suggestionsstring[]Подсказки, которые показываются, пока чат пуст
placeholderstringПлейсхолдер поля ввода
acceptstring | falseДопустимые вложения; false скрывает кнопку прикрепления

Слот #empty заменяет заголовок пустого состояния.

ChatMessage ​

Одно сообщение: Markdown, рассуждения, вызовы инструментов, изображения и файлы, источники, а также действия «скопировать» и «сгенерировать заново». Генерирует события tool-result и regenerate.

ChatPromptInput ​

Авторастущий textarea с вложениями (выбор файла, вставка и drag-and-drop) и кнопками отправки и остановки. Принимает chat, placeholder, accept и maxFiles.

ChatReasoning ​

Сворачиваемые рассуждения. Блок раскрывается, пока модель думает, и сворачивается, когда начинается ответ.

ChatToolCall ​

Запасная карточка для вызовов инструментов: состояние и сворачиваемые аргументы и результат.

Темы ​

Компоненты используют только токены темы shadcn: background, foreground, primary, muted, border, ring, card и destructive. Они следуют вашей теме и тёмному режиму. Попробуйте Vue green в демо выше.

Без Tailwind ​

Не используйте реестр. Соберите интерфейс на headless-композаблах — в них заключена вся сложная часть компонентов выше:

vue
<script setup lang="ts">
import { useAutoScroll, useChat, usePromptInput } from 'vue-llm'

const chat = useChat({ provider, model: 'gpt-4o' })
const { input, messages } = chat
const prompt = usePromptInput(chat)
const { files, canSubmit, submit, removeFile, handlers, dropzone } = prompt
const scroll = useAutoScroll()
const { isAtBottom, scrollToBottom } = scroll
</script>

<template>
  <div :ref="scroll.container" class="messages">
    <MyMessage v-for="m in messages" :key="m.id" :message="m" />
  </div>
  <button v-if="!isAtBottom" @click="scrollToBottom('smooth')">↓</button>

  <form v-bind="dropzone" @submit.prevent="submit">
    <span v-for="f in files" :key="f.name">{{ f.name }} <button type="button" @click="removeFile(f)">×</button></span>
    <textarea :ref="prompt.textarea" v-model="input" rows="1" v-bind="handlers" />
    <button :disabled="!canSubmit">Send</button>
  </form>
</template>

Привязывайте template ref-ы через :ref="scroll.container", а не деструктурируя container. vue-tsc с noUnusedLocals, который включён в шаблонах Vite, не считает строковый ref="container" использованием переменной.

usePromptInput(chat, options?) ​

  • Enter отправляет сообщение, Shift+Enter добавляет перенос строки. Enter, которым подтверждается ввод через IME (китайский, японский, корейский), никогда не отправляет сообщение.
  • Textarea растёт вместе с содержимым до maxRows строк (по умолчанию 8).
  • Файлы прикрепляются через addFiles (для file input), вставкой и перетаскиванием на элемент dropzone. С помощью isDragging можно показать оверлей для сброса файлов.
  • accept и maxFiles фильтруют вложения, а onReject(files, reason) сообщает об отклонённых.
  • submit() отправляет текст и файлы и очищает их. canSubmit равен false, пока чат занят.

На Vue 2.7 привязывайте обработчики явно (@keydown="handlers.onKeydown" @paste="handlers.onPaste"), потому что там v-bind не навешивает слушатели.

useAutoScroll(options?) ​

  • Держит список прижатым к низу, пока стримятся сообщения и пока растут изображения или блоки кода.
  • Перестаёт следовать, когда пользователь прокручивает вверх, чтобы он мог спокойно читать. isAtBottom становится false.
  • scrollToBottom('smooth') снова прижимает список к низу.
  • threshold (по умолчанию 48 px) задаёт, насколько близко к низу ещё считается «внизу».

Распространяется по лицензии MIT.