Skip to content

チャット UI ​

自分で所有できる 完全なチャットインターフェースです。コンポーネントは shadcn-vue CLI でプレーンな .vue ファイルとしてプロジェクトに追加されるため、他のコンポーネントと同じようにスタイルを変えたり書き直したりできます。間違えやすい振る舞いの部分は vue-llm パッケージに含まれており、パッケージとともに更新されます。

このデモはスクリプト化されたモデルを使っています。コンポーネントは CLI がインストールするものと同じです。

構成の分け方 ​

部分置き場所更新方法
useChat、プロバイダー、StreamMarkdownvue-llm パッケージnpm update vue-llm
usePromptInput、useAutoScrollvue-llm パッケージnpm 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 ​

チャット全体です。自動スクロール付きのメッセージリスト、提案付きの空の状態、入力中インジケーター、リトライ付きのエラー表示、最下部へのスクロールボタン、入力欄を含みます。

プロパティ型説明
chatUseChatReturnuseChat の戻り値
componentsRecord<string, Component>ツール名 → コンポーネント
suggestionsstring[]チャットが空の間に表示するプロンプト
placeholderstring入力欄のプレースホルダー
acceptstring | false受け付ける添付ファイル。false で添付ボタンを非表示にする

#empty スロットで、空の状態の見出しを置き換えられます。

ChatMessage ​

1 つのメッセージです。Markdown、推論、ツール呼び出し、画像とファイル、ソース、コピーと再生成のアクションを含みます。tool-result と regenerate を emit します。

ChatPromptInput ​

内容に合わせて高さが伸びるテキストエリアで、添付ファイル(ファイル選択、ペースト、ドラッグ&ドロップ)と送信・停止ボタンを備えています。chat、placeholder、accept、maxFiles を受け取ります。

ChatReasoning ​

折りたたみ可能な推論表示です。モデルが考えている間は開き、回答が始まると折りたたまれます。

ChatToolCall ​

ツール呼び出し用のフォールバックカードで、状態と折りたたみ可能な引数・結果を表示します。

テーマ ​

コンポーネントが使うのは shadcn のテーマトークン background、foreground、primary、muted、border、ring、card、destructive だけです。テーマとダークモードに追従します。上のデモで Vue green を試してみてください。

Tailwind を使わない場合 ​

レジストリは使いません。上記のコンポーネントの難しい部分を担うヘッドレスなコンポーザブルを使って、UI を構築します。

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>

テンプレート ref は、container を分割代入するのではなく :ref="scroll.container" でバインドしてください。Vite のテンプレートで有効になっている noUnusedLocals を使う vue-tsc は、文字列の ref="container" を使用箇所として数えません。

usePromptInput(chat, options?) ​

  • Enter で送信し、Shift+Enter で改行します。IME の変換(中国語、日本語、韓国語)を確定する Enter で送信されることはありません。
  • テキストエリアは内容に合わせて maxRows(デフォルト 8)まで高くなります。
  • ファイルは addFiles(ファイル入力)、ペースト、dropzone 要素へのドラッグ&ドロップから添付されます。isDragging を使えばドロップ用のオーバーレイを表示できます。
  • accept と maxFiles で添付ファイルを絞り込み、onReject(files, reason) で拒否されたファイルが通知されます。
  • submit() はテキストとファイルを送信し、両方をクリアします。チャットが処理中の間、canSubmit は false です。

Vue 2.7 では v-bind でリスナーが付与されないため、ハンドラーを明示的にバインドしてください(@keydown="handlers.onKeydown" @paste="handlers.onPaste")。

useAutoScroll(options?) ​

  • メッセージのストリーミング中や、画像やコードブロックが大きくなる間も、リストを最下部に固定します。
  • ユーザーが上にスクロールすると追従を止めるので、落ち着いて読めます。このとき isAtBottom は false になります。
  • scrollToBottom('smooth') で再び最下部に固定します。
  • threshold(デフォルト 48 px)は、最下部からどれだけ近ければ「最下部にいる」とみなすかを設定します。

MIT ライセンスの下で公開されています。