チャットメッセージ
使用法
ChatMessagesコンポーネントはChatMessageコンポーネントのリストをデフォルトスロットまたはmessagesプロパティのいずれかを使用して表示します。
<template>
<UChatMessages>
<UChatMessage
v-for="(message, index) in messages"
:key="index"
v-bind="message"
/>
</UChatMessages>
</template>
- ロード時に最初にスクロールします(
shouldScrollToBottom)。 - 新しいメッセージが到着すると連続的にスクロールダウン(
shouldAutoScroll)。 - スクロールアップすると「自動スクロール」ボタンが表示され、最新のメッセージ(
autoScroll)にジャンプできます。 - Aアシスタントの処理中に読み込みインジケータが表示されます
status。 - Submittedメッセージはビューポートの上部までスクロールされ、最後のユーザーメッセージの高さは動的に調整されます。
メッセージ
messagesプロパティを使用して、チャットメッセージのリストを表示します。
<script setup lang="ts">
const messages = ref([
{
id: '6045235a-a435-46b8-989d-2df38ca2eb47',
role: 'user',
parts: [
{
type: 'text',
text: 'Hello, how are you?'
}
]
},
{
id: '7a92b3c1-d5f8-4e76-b8a9-3c1e5fb2e0d8',
role: 'assistant',
parts: [
{
type: 'text',
text: 'I am doing well, thank you for asking! How can I assist you today?'
}
]
},
{
id: '9c84d6a7-8b23-4f12-a1d5-e7f3b9c05e2a',
role: 'user',
parts: [
{
type: 'text',
text: 'What is the current weather in Tokyo?'
}
]
},
{
id: 'b2e5f8c3-a1d9-4e67-b3f2-c9d8e7a6b5f4',
role: 'assistant',
parts: [
{
type: 'text',
text: "Based on the latest data, Tokyo is currently experiencing sunny weather with temperatures around 24°C (75°F). It's a beautiful day with clear skies."
}
]
}
])
</script>
<template>
<UChatMessages :messages="messages" />
</template><script setup lang="ts">
import { ref } from 'vue'
const messages = ref([
{
id: '6045235a-a435-46b8-989d-2df38ca2eb47',
role: 'user',
parts: [
{
type: 'text',
text: 'Hello, how are you?'
}
]
},
{
id: '7a92b3c1-d5f8-4e76-b8a9-3c1e5fb2e0d8',
role: 'assistant',
parts: [
{
type: 'text',
text: 'I am doing well, thank you for asking! How can I assist you today?'
}
]
},
{
id: '9c84d6a7-8b23-4f12-a1d5-e7f3b9c05e2a',
role: 'user',
parts: [
{
type: 'text',
text: 'What is the current weather in Tokyo?'
}
]
},
{
id: 'b2e5f8c3-a1d9-4e67-b3f2-c9d8e7a6b5f4',
role: 'assistant',
parts: [
{
type: 'text',
text: "Based on the latest data, Tokyo is currently experiencing sunny weather with temperatures around 24°C (75°F). It's a beautiful day with clear skies."
}
]
}
])
</script>
<template>
<UChatMessages :messages="messages" />
</template>Status
statusプロパティを使用して、アシスタントが処理中にビジュアルインジケータを表示します。
<script setup lang="ts">
const messages = ref([
{
id: '6045235a-a435-46b8-989d-2df38ca2eb47',
role: 'user',
parts: [
{
type: 'text',
text: 'Hello, how are you?'
}
]
}
])
</script>
<template>
<UChatMessages status="submitted" :messages="messages" />
</template><script setup lang="ts">
import { ref } from 'vue'
const messages = ref([
{
id: '6045235a-a435-46b8-989d-2df38ca2eb47',
role: 'user',
parts: [
{
type: 'text',
text: 'Hello, how are you?'
}
]
}
])
</script>
<template>
<UChatMessages status="submitted" :messages="messages" />
</template>useChat composableの異なるステータスの詳細は次のとおりです。submitted:メッセージがAPIに送信され、レスポンスストリームの開始を待っています。streaming:レスポンスがAPIからアクティブにストリーミングされ、大量のデータを受信します。ready:完全なレスポンスが受信され処理されました。新しいユーザーメッセージを送信できます。error:APIリクエスト中にエラーが発生し、正常に完了できませんでした。
User
userプロパティを使用して、ChatMessageプロパティをuserメッセージに変更します。デフォルトは
side: 'right'variant: 'soft'
<script setup lang="ts">
const messages = ref([
{
id: '6045235a-a435-46b8-989d-2df38ca2eb47',
role: 'user',
parts: [
{
type: 'text',
text: 'Hello, how are you?'
}
]
},
{
id: '7a92b3c1-d5f8-4e76-b8a9-3c1e5fb2e0d8',
role: 'assistant',
parts: [
{
type: 'text',
text: 'I am doing well, thank you for asking! How can I assist you today?'
}
]
},
{
id: '9c84d6a7-8b23-4f12-a1d5-e7f3b9c05e2a',
role: 'user',
parts: [
{
type: 'text',
text: 'What is the current weather in Tokyo?'
}
]
},
{
id: 'b2e5f8c3-a1d9-4e67-b3f2-c9d8e7a6b5f4',
role: 'assistant',
parts: [
{
type: 'text',
text: "Based on the latest data, Tokyo is currently experiencing sunny weather with temperatures around 24°C (75°F). It's a beautiful day with clear skies."
}
]
}
])
</script>
<template>
<UChatMessages
:user="{
side: 'left',
variant: 'solid',
avatar: {
src: 'https://github.com/benjamincanac.png',
loading: 'lazy'
}
}"
:messages="messages"
/>
</template><script setup lang="ts">
import { ref } from 'vue'
const messages = ref([
{
id: '6045235a-a435-46b8-989d-2df38ca2eb47',
role: 'user',
parts: [
{
type: 'text',
text: 'Hello, how are you?'
}
]
},
{
id: '7a92b3c1-d5f8-4e76-b8a9-3c1e5fb2e0d8',
role: 'assistant',
parts: [
{
type: 'text',
text: 'I am doing well, thank you for asking! How can I assist you today?'
}
]
},
{
id: '9c84d6a7-8b23-4f12-a1d5-e7f3b9c05e2a',
role: 'user',
parts: [
{
type: 'text',
text: 'What is the current weather in Tokyo?'
}
]
},
{
id: 'b2e5f8c3-a1d9-4e67-b3f2-c9d8e7a6b5f4',
role: 'assistant',
parts: [
{
type: 'text',
text: "Based on the latest data, Tokyo is currently experiencing sunny weather with temperatures around 24°C (75°F). It's a beautiful day with clear skies."
}
]
}
])
</script>
<template>
<UChatMessages
:user="{
side: 'left',
variant: 'solid',
avatar: {
src: 'https://github.com/benjamincanac.png',
loading: 'lazy'
}
}"
:messages="messages"
/>
</template>Assistant
assistantプロパティを使用して、ChatMessageプロパティをassistantメッセージに変更します。デフォルトは
side: 'left'variant: 'naked'
<script setup lang="ts">
const messages = ref([
{
id: '6045235a-a435-46b8-989d-2df38ca2eb47',
role: 'user',
parts: [
{
type: 'text',
text: 'Hello, how are you?'
}
]
},
{
id: '7a92b3c1-d5f8-4e76-b8a9-3c1e5fb2e0d8',
role: 'assistant',
parts: [
{
type: 'text',
text: 'I am doing well, thank you for asking! How can I assist you today?'
}
]
},
{
id: '9c84d6a7-8b23-4f12-a1d5-e7f3b9c05e2a',
role: 'user',
parts: [
{
type: 'text',
text: 'What is the current weather in Tokyo?'
}
]
},
{
id: 'b2e5f8c3-a1d9-4e67-b3f2-c9d8e7a6b5f4',
role: 'assistant',
parts: [
{
type: 'text',
text: "Based on the latest data, Tokyo is currently experiencing sunny weather with temperatures around 24°C (75°F). It's a beautiful day with clear skies."
}
]
}
])
</script>
<template>
<UChatMessages
:assistant="{
side: 'left',
variant: 'outline',
avatar: {
icon: 'i-lucide-bot'
},
actions: [
{
label: 'Copy to clipboard',
icon: 'i-lucide-copy'
}
]
}"
:messages="messages"
/>
</template><script setup lang="ts">
import { ref } from 'vue'
const messages = ref([
{
id: '6045235a-a435-46b8-989d-2df38ca2eb47',
role: 'user',
parts: [
{
type: 'text',
text: 'Hello, how are you?'
}
]
},
{
id: '7a92b3c1-d5f8-4e76-b8a9-3c1e5fb2e0d8',
role: 'assistant',
parts: [
{
type: 'text',
text: 'I am doing well, thank you for asking! How can I assist you today?'
}
]
},
{
id: '9c84d6a7-8b23-4f12-a1d5-e7f3b9c05e2a',
role: 'user',
parts: [
{
type: 'text',
text: 'What is the current weather in Tokyo?'
}
]
},
{
id: 'b2e5f8c3-a1d9-4e67-b3f2-c9d8e7a6b5f4',
role: 'assistant',
parts: [
{
type: 'text',
text: "Based on the latest data, Tokyo is currently experiencing sunny weather with temperatures around 24°C (75°F). It's a beautiful day with clear skies."
}
]
}
])
</script>
<template>
<UChatMessages
:assistant="{
side: 'left',
variant: 'outline',
avatar: {
icon: 'i-lucide-bot'
},
actions: [
{
label: 'Copy to clipboard',
icon: 'i-lucide-copy'
}
]
}"
:messages="messages"
/>
</template>Autoスクロール
auto-scrollプロパティを使用して、チャットの先頭までスクロールするときに表示される自動スクロールボタン(false値)をカスタマイズまたは非表示にします。デフォルトは
color: 'neutral'variant: 'outline'
Buttonコンポーネントの任意のプロパティを渡してカスタマイズできます。
<script setup lang="ts">
const messages = ref([
{
id: '6045235a-a435-46b8-989d-2df38ca2eb47',
role: 'user',
parts: [
{
type: 'text',
text: 'Hello, how are you?'
}
]
},
{
id: '7a92b3c1-d5f8-4e76-b8a9-3c1e5fb2e0d8',
role: 'assistant',
parts: [
{
type: 'text',
text: 'I am doing well, thank you for asking! How can I assist you today?'
}
]
},
{
id: '9c84d6a7-8b23-4f12-a1d5-e7f3b9c05e2a',
role: 'user',
parts: [
{
type: 'text',
text: 'What is the current weather in Tokyo?'
}
]
},
{
id: 'b2e5f8c3-a1d9-4e67-b3f2-c9d8e7a6b5f4',
role: 'assistant',
parts: [
{
type: 'text',
text: "Based on the latest data, Tokyo is currently experiencing sunny weather with temperatures around 24°C (75°F). It's a beautiful day with clear skies. The forecast for the rest of the week shows a slight chance of rain on Thursday, with temperatures gradually rising to 28°C by the weekend. Humidity levels are moderate at around 65%, and wind speeds are light at 8 km/h from the southeast. Air quality is good with an index of 42. The UV index is high at 7, so it's recommended to wear sunscreen if you're planning to spend time outdoors. Sunrise was at 5:24 AM and sunset will be at 6:48 PM, giving Tokyo approximately 13 hours and 24 minutes of daylight today. The moon is currently in its waxing gibbous phase."
}
]
},
{
id: 'c3e5f8c3-a1d9-4e67-b3f2-c9d8e7a6b5f4',
role: 'user',
parts: [
{
type: 'text',
text: 'Can you recommend some popular tourist attractions in Kyoto?'
}
]
},
{
id: 'd4f5g8c3-a1d9-4e67-b3f2-c9d8e7a6b5f4',
role: 'assistant',
parts: [
{
type: 'text',
text: 'Kyoto is known for its beautiful temples, traditional tea houses, and gardens. Some popular attractions include Kinkaku-ji (Golden Pavilion) with its stunning gold leaf exterior reflecting in the mirror pond, Fushimi Inari Shrine with its thousands of vermilion torii gates winding up the mountainside, Arashiyama Bamboo Grove where towering stalks create an otherworldly atmosphere, Kiyomizu-dera Temple perched on a hillside offering panoramic views of the city, and the historic Gion district where you might spot geisha hurrying to evening appointments through narrow stone-paved streets lined with traditional wooden machiya houses.'
}
]
}
])
</script>
<template>
<UChatMessages
:auto-scroll="{
color: 'neutral',
variant: 'outline'
}"
:should-scroll-to-bottom="false"
:messages="messages"
/>
</template><script setup lang="ts">
import { ref, h } from 'vue'
const messages = ref([
{
id: '6045235a-a435-46b8-989d-2df38ca2eb47',
role: 'user',
parts: [
{
type: 'text',
text: 'Hello, how are you?'
}
]
},
{
id: '7a92b3c1-d5f8-4e76-b8a9-3c1e5fb2e0d8',
role: 'assistant',
parts: [
{
type: 'text',
text: 'I am doing well, thank you for asking! How can I assist you today?'
}
]
},
{
id: '9c84d6a7-8b23-4f12-a1d5-e7f3b9c05e2a',
role: 'user',
parts: [
{
type: 'text',
text: 'What is the current weather in Tokyo?'
}
]
},
{
id: 'b2e5f8c3-a1d9-4e67-b3f2-c9d8e7a6b5f4',
role: 'assistant',
parts: [
{
type: 'text',
text: "Based on the latest data, Tokyo is currently experiencing sunny weather with temperatures around 24°C (75°F). It's a beautiful day with clear skies. The forecast for the rest of the week shows a slight chance of rain on Thursday, with temperatures gradually rising to 28°C by the weekend. Humidity levels are moderate at around 65%, and wind speeds are light at 8 km/h from the southeast. Air quality is good with an index of 42. The UV index is high at 7, so it's recommended to wear sunscreen if you're planning to spend time outdoors. Sunrise was at 5:24 AM and sunset will be at 6:48 PM, giving Tokyo approximately 13 hours and 24 minutes of daylight today. The moon is currently in its waxing gibbous phase."
}
]
},
{
id: 'c3e5f8c3-a1d9-4e67-b3f2-c9d8e7a6b5f4',
role: 'user',
parts: [
{
type: 'text',
text: 'Can you recommend some popular tourist attractions in Kyoto?'
}
]
},
{
id: 'd4f5g8c3-a1d9-4e67-b3f2-c9d8e7a6b5f4',
role: 'assistant',
parts: [
{
type: 'text',
text: 'Kyoto is known for its beautiful temples, traditional tea houses, and gardens. Some popular attractions include Kinkaku-ji (Golden Pavilion) with its stunning gold leaf exterior reflecting in the mirror pond, Fushimi Inari Shrine with its thousands of vermilion torii gates winding up the mountainside, Arashiyama Bamboo Grove where towering stalks create an otherworldly atmosphere, Kiyomizu-dera Temple perched on a hillside offering panoramic views of the city, and the historic Gion district where you might spot geisha hurrying to evening appointments through narrow stone-paved streets lined with traditional wooden machiya houses.'
}
]
}
])
</script>
<template>
<UChatMessages
:auto-scroll="{
color: 'neutral',
variant: 'outline'
}"
:should-scroll-to-bottom="false"
:messages="messages"
/>
</template>Autoスクロールアイコン
auto-scroll-iconプロパティを使用して、自動スクロールボタンIconをカスタマイズします。デフォルトはi-lucide-arrow-downです。
<script setup lang="ts">
const messages = ref([
{
id: '6045235a-a435-46b8-989d-2df38ca2eb47',
role: 'user',
parts: [
{
type: 'text',
text: 'Hello, how are you?'
}
]
},
{
id: '7a92b3c1-d5f8-4e76-b8a9-3c1e5fb2e0d8',
role: 'assistant',
parts: [
{
type: 'text',
text: 'I am doing well, thank you for asking! How can I assist you today?'
}
]
},
{
id: '9c84d6a7-8b23-4f12-a1d5-e7f3b9c05e2a',
role: 'user',
parts: [
{
type: 'text',
text: 'What is the current weather in Tokyo?'
}
]
},
{
id: 'b2e5f8c3-a1d9-4e67-b3f2-c9d8e7a6b5f4',
role: 'assistant',
parts: [
{
type: 'text',
text: "Based on the latest data, Tokyo is currently experiencing sunny weather with temperatures around 24°C (75°F). It's a beautiful day with clear skies. The forecast for the rest of the week shows a slight chance of rain on Thursday, with temperatures gradually rising to 28°C by the weekend. Humidity levels are moderate at around 65%, and wind speeds are light at 8 km/h from the southeast. Air quality is good with an index of 42. The UV index is high at 7, so it's recommended to wear sunscreen if you're planning to spend time outdoors. Sunrise was at 5:24 AM and sunset will be at 6:48 PM, giving Tokyo approximately 13 hours and 24 minutes of daylight today. The moon is currently in its waxing gibbous phase."
}
]
},
{
id: 'c3e5f8c3-a1d9-4e67-b3f2-c9d8e7a6b5f4',
role: 'user',
parts: [
{
type: 'text',
text: 'Can you recommend some popular tourist attractions in Kyoto?'
}
]
},
{
id: 'd4f5g8c3-a1d9-4e67-b3f2-c9d8e7a6b5f4',
role: 'assistant',
parts: [
{
type: 'text',
text: 'Kyoto is known for its beautiful temples, traditional tea houses, and gardens. Some popular attractions include Kinkaku-ji (Golden Pavilion) with its stunning gold leaf exterior reflecting in the mirror pond, Fushimi Inari Shrine with its thousands of vermilion torii gates winding up the mountainside, Arashiyama Bamboo Grove where towering stalks create an otherworldly atmosphere, Kiyomizu-dera Temple perched on a hillside offering panoramic views of the city, and the historic Gion district where you might spot geisha hurrying to evening appointments through narrow stone-paved streets lined with traditional wooden machiya houses.'
}
]
}
])
</script>
<template>
<UChatMessages
auto-scroll-icon="i-lucide-chevron-down"
:should-scroll-to-bottom="false"
:messages="messages"
/>
</template><script setup lang="ts">
import { ref, h } from 'vue'
const messages = ref([
{
id: '6045235a-a435-46b8-989d-2df38ca2eb47',
role: 'user',
parts: [
{
type: 'text',
text: 'Hello, how are you?'
}
]
},
{
id: '7a92b3c1-d5f8-4e76-b8a9-3c1e5fb2e0d8',
role: 'assistant',
parts: [
{
type: 'text',
text: 'I am doing well, thank you for asking! How can I assist you today?'
}
]
},
{
id: '9c84d6a7-8b23-4f12-a1d5-e7f3b9c05e2a',
role: 'user',
parts: [
{
type: 'text',
text: 'What is the current weather in Tokyo?'
}
]
},
{
id: 'b2e5f8c3-a1d9-4e67-b3f2-c9d8e7a6b5f4',
role: 'assistant',
parts: [
{
type: 'text',
text: "Based on the latest data, Tokyo is currently experiencing sunny weather with temperatures around 24°C (75°F). It's a beautiful day with clear skies. The forecast for the rest of the week shows a slight chance of rain on Thursday, with temperatures gradually rising to 28°C by the weekend. Humidity levels are moderate at around 65%, and wind speeds are light at 8 km/h from the southeast. Air quality is good with an index of 42. The UV index is high at 7, so it's recommended to wear sunscreen if you're planning to spend time outdoors. Sunrise was at 5:24 AM and sunset will be at 6:48 PM, giving Tokyo approximately 13 hours and 24 minutes of daylight today. The moon is currently in its waxing gibbous phase."
}
]
},
{
id: 'c3e5f8c3-a1d9-4e67-b3f2-c9d8e7a6b5f4',
role: 'user',
parts: [
{
type: 'text',
text: 'Can you recommend some popular tourist attractions in Kyoto?'
}
]
},
{
id: 'd4f5g8c3-a1d9-4e67-b3f2-c9d8e7a6b5f4',
role: 'assistant',
parts: [
{
type: 'text',
text: 'Kyoto is known for its beautiful temples, traditional tea houses, and gardens. Some popular attractions include Kinkaku-ji (Golden Pavilion) with its stunning gold leaf exterior reflecting in the mirror pond, Fushimi Inari Shrine with its thousands of vermilion torii gates winding up the mountainside, Arashiyama Bamboo Grove where towering stalks create an otherworldly atmosphere, Kiyomizu-dera Temple perched on a hillside offering panoramic views of the city, and the historic Gion district where you might spot geisha hurrying to evening appointments through narrow stone-paved streets lined with traditional wooden machiya houses.'
}
]
}
])
</script>
<template>
<UChatMessages
auto-scroll-icon="i-lucide-chevron-down"
:should-scroll-to-bottom="false"
:messages="messages"
/>
</template>自動スクロール
should-auto-scrollプロパティを使用して、メッセージのストリーミング中に連続自動スクロールを有効/無効にします。デフォルトはfalseです。
<template>
<UChatMessages :messages="messages" should-auto-scroll />
</template>
下までスクロールしてください
should-scroll-to-bottomプロパティを使用して、コンポーネントがマウントされたときに下部の自動スクロールを有効/無効にします。デフォルトはtrueです。
<template>
<UChatMessages :messages="messages" :should-scroll-to-bottom="false" />
</template>
サンプル
インジケータスロット付
#indicatorスロットを使用して、ChatShimmerエフェクトでロードインジケータをカスタマイズします。
<script setup lang="ts">
import type { UIMessage } from 'ai'
import { useChat } from '@ai-sdk/vue'
const initialMessages: UIMessage[] = [{
id: '1',
role: 'user',
parts: [{ type: 'text', text: 'Hello! Can you help me with something?' }]
}]
const { messages } = useChat({
messages: initialMessages
})
const size = 4
const gap = 2
const totalDots = size * size
const patterns = [
[[0], [1], [2], [3], [7], [11], [15], [14], [13], [12], [8], [4], [5], [6], [10], [9]],
[[0, 4, 8, 12], [1, 5, 9, 13], [2, 6, 10, 14], [3, 7, 11, 15]],
[[5, 6, 9, 10], [1, 4, 7, 8, 11, 14], [0, 3, 12, 15], [1, 4, 7, 8, 11, 14], [5, 6, 9, 10]],
[[0], [1, 4], [2, 5, 8], [3, 6, 9, 12], [7, 10, 13], [11, 14], [15]]
]
const activeDots = ref<Set<number>>(new Set())
let patternIndex = 0
let stepIndex = 0
function nextStep() {
const pattern = patterns[patternIndex]
if (!pattern) return
activeDots.value = new Set(pattern[stepIndex])
stepIndex++
if (stepIndex >= pattern.length) {
stepIndex = 0
patternIndex = (patternIndex + 1) % patterns.length
}
}
const statusMessages = ['Searching...', 'Reading...', 'Analyzing...', 'Thinking...']
const currentIndex = ref(0)
const displayedText = ref(statusMessages[0]!)
const chars = 'abcdefghijklmnopqrstuvwxyz'
function scramble(from: string, to: string) {
const maxLength = Math.max(from.length, to.length)
let frame = 0
const totalFrames = 15
const step = () => {
frame++
let result = ''
const progress = (frame / totalFrames) * maxLength
for (let i = 0; i < maxLength; i++) {
if (i < progress - 2) {
result += to[i] || ''
} else if (i < progress) {
result += chars[Math.floor(Math.random() * chars.length)]
} else {
result += from[i] || ''
}
}
displayedText.value = result
if (frame < totalFrames) {
requestAnimationFrame(step)
} else {
displayedText.value = to
}
}
requestAnimationFrame(step)
}
let matrixInterval: ReturnType<typeof setInterval> | undefined
let textInterval: ReturnType<typeof setInterval> | undefined
onMounted(() => {
nextStep()
matrixInterval = setInterval(nextStep, 120)
textInterval = setInterval(() => {
const prev = displayedText.value
currentIndex.value = (currentIndex.value + 1) % statusMessages.length
scramble(prev, statusMessages[currentIndex.value]!)
}, 3000)
})
onUnmounted(() => {
clearInterval(matrixInterval)
clearInterval(textInterval)
})
</script>
<template>
<UChatMessages
:messages="messages"
status="submitted"
:should-scroll-to-bottom="false"
>
<template #indicator>
<div class="flex items-center gap-2 text-muted overflow-hidden">
<div
class="shrink-0 grid size-4"
:style="{
gridTemplateColumns: `repeat(${size}, 1fr)`,
gap: `${gap}px`
}"
>
<span
v-for="i in totalDots"
:key="i"
class="rounded-sm bg-current transition-opacity duration-100"
:class="activeDots.has(i - 1) ? 'opacity-100' : 'opacity-20'"
/>
</div>
<UChatShimmer :text="displayedText" class="text-sm font-mono" />
</div>
</template>
</UChatMessages>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import type { UIMessage } from 'ai'
import { useChat } from '@ai-sdk/vue'
const initialMessages: UIMessage[] = [{
id: '1',
role: 'user',
parts: [{ type: 'text', text: 'Hello! Can you help me with something?' }]
}]
const { messages } = useChat({
messages: initialMessages
})
const size = 4
const gap = 2
const totalDots = size * size
const patterns = [
[[0], [1], [2], [3], [7], [11], [15], [14], [13], [12], [8], [4], [5], [6], [10], [9]],
[[0, 4, 8, 12], [1, 5, 9, 13], [2, 6, 10, 14], [3, 7, 11, 15]],
[[5, 6, 9, 10], [1, 4, 7, 8, 11, 14], [0, 3, 12, 15], [1, 4, 7, 8, 11, 14], [5, 6, 9, 10]],
[[0], [1, 4], [2, 5, 8], [3, 6, 9, 12], [7, 10, 13], [11, 14], [15]]
]
const activeDots = ref<Set<number>>(new Set())
let patternIndex = 0
let stepIndex = 0
function nextStep() {
const pattern = patterns[patternIndex]
if (!pattern) return
activeDots.value = new Set(pattern[stepIndex])
stepIndex++
if (stepIndex >= pattern.length) {
stepIndex = 0
patternIndex = (patternIndex + 1) % patterns.length
}
}
const statusMessages = ['Searching...', 'Reading...', 'Analyzing...', 'Thinking...']
const currentIndex = ref(0)
const displayedText = ref(statusMessages[0]!)
const chars = 'abcdefghijklmnopqrstuvwxyz'
function scramble(from: string, to: string) {
const maxLength = Math.max(from.length, to.length)
let frame = 0
const totalFrames = 15
const step = () => {
frame++
let result = ''
const progress = (frame / totalFrames) * maxLength
for (let i = 0; i < maxLength; i++) {
if (i < progress - 2) {
result += to[i] || ''
} else if (i < progress) {
result += chars[Math.floor(Math.random() * chars.length)]
} else {
result += from[i] || ''
}
}
displayedText.value = result
if (frame < totalFrames) {
requestAnimationFrame(step)
} else {
displayedText.value = to
}
}
requestAnimationFrame(step)
}
let matrixInterval: ReturnType<typeof setInterval> | undefined
let textInterval: ReturnType<typeof setInterval> | undefined
onMounted(() => {
nextStep()
matrixInterval = setInterval(nextStep, 120)
textInterval = setInterval(() => {
const prev = displayedText.value
currentIndex.value = (currentIndex.value + 1) % statusMessages.length
scramble(prev, statusMessages[currentIndex.value]!)
}, 3000)
})
onUnmounted(() => {
clearInterval(matrixInterval)
clearInterval(textInterval)
})
</script>
<template>
<UChatMessages
:messages="messages"
status="submitted"
:should-scroll-to-bottom="false"
>
<template #indicator>
<div class="flex items-center gap-2 text-muted overflow-hidden">
<div
class="shrink-0 grid size-4"
:style="{
gridTemplateColumns: `repeat(${size}, 1fr)`,
gap: `${gap}px`
}"
>
<span
v-for="i in totalDots"
:key="i"
class="rounded-sm bg-current transition-opacity duration-100"
:class="activeDots.has(i - 1) ? 'opacity-100' : 'opacity-20'"
/>
</div>
<UChatShimmer :text="displayedText" class="text-sm font-mono" />
</div>
</template>
</UChatMessages>
</template>
API
Props
| Prop | Default | Type |
|---|---|---|
messages | T | |
status | "submitted" | "streaming" | "ready" | "error" | |
shouldAutoScroll | false | boolean Whether to automatically scroll to the bottom when a message is streaming. |
shouldScrollToBottom | true | boolean Whether to scroll to the bottom on mounted. |
autoScroll | true | boolean | Omit<ButtonProps, LinkPropsKeys> Display an auto scroll button.
|
autoScrollIcon | appConfig.ui.icons.arrowDown | anyThe icon displayed in the auto scroll button. |
user | Pick<PropsBase<T>, "actions" | "ui" | "color" | "variant" | "icon" | "avatar" | "side">The | |
assistant | Pick<PropsBase<T>, "actions" | "ui" | "color" | "variant" | "icon" | "avatar" | "side">The | |
compact | false | boolean Render the messages in a compact style.
This is done automatically when used inside a |
spacingOffset | 0 | numberThe spacing offset for the last message in px. Can be useful when the prompt is sticky for example. |
ui | { root?: SlotClass; indicator?: SlotClass; viewport?: SlotClass; autoScroll?: SlotClass; } |
スロット
| Slot | Type |
|---|---|
default | { registerMessageRef: (id: string, element: ComponentPublicInstance | null) => void; } |
indicator | { ui: object; } |
viewport | { ui: object; } |
header | UIMessage<unknown, UIDataTypes, UITools> & { message: MessageBase<T>; } |
leading | UIMessage<unknown, UIDataTypes, UITools> & { avatar: (AvatarProps & { [key: string]: any; }) | undefined; ui: object; } & { message: MessageBase<T>; } |
files | Omit<UIMessage<unknown, UIDataTypes, UITools>, "parts"> & { parts: FileUIPart[]; } & { message: MessageBase<T>; } |
body | UIMessage<unknown, UIDataTypes, UITools> & { message: MessageBase<T>; } |
content | UIMessage<unknown, UIDataTypes, UITools> & { content?: string | undefined; } & { message: MessageBase<T>; } |
actions | UIMessage<unknown, UIDataTypes, UITools> & { actions: (Omit<ButtonProps, "onClick"> & { onClick?: ((e: MouseEvent, message: UIMessage<unknown, UIDataTypes, UITools>) => void) | undefined; })[] | undefined; } & { message: MessageBase<T>; } |
ChatMessageコンポーネントのすべてのスロットをChatMessages内で使用できます。messagesプロパティを使用するときに個々のメッセージをカスタマイズできるように自動的に転送されます。<script setup lang="ts">
import { isTextUIPart } from 'ai'
</script>
<template>
<UChatMessages :messages="messages" :status="status">
<template #content="{ message }">
<template
v-for="(part, index) in message.parts"
:key="`${message.id}-${part.type}-${index}`"
>
<p v-if="isTextUIPart(part)" class="whitespace-pre-wrap">
{{ part.text }}
</p>
</template>
</template>
</UChatMessages>
</template>
Expose
テンプレート参照を介してコンポーネントにアクセスする場合、以下を使用できます:
| 名前 | タイプ |
|---|---|
registerMessageRef(id: string, element: ComponentPublicInstance | null) | void |
Theme
export default defineAppConfig({
ui: {
chatMessages: {
slots: {
root: 'w-full flex flex-col gap-1 flex-1 px-2.5 [&>article]:last-of-type:min-h-(--last-message-height)',
indicator: 'h-6 flex items-center gap-1 py-3 *:size-2 *:rounded-full *:bg-elevated motion-safe:[&>*:nth-child(1)]:animate-[bounce_1s_infinite] motion-safe:[&>*:nth-child(2)]:animate-[bounce_1s_0.15s_infinite] motion-safe:[&>*:nth-child(3)]:animate-[bounce_1s_0.3s_infinite]',
viewport: 'absolute inset-x-0 top-[86%] data-[state=open]:animate-[fade-in_200ms_var(--ease-out)] data-[state=closed]:animate-[fade-out_200ms_var(--ease-out)]',
autoScroll: 'rounded-full absolute right-1/2 translate-x-1/2 bottom-0'
},
variants: {
compact: {
true: '',
false: ''
}
}
}
}
})import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
chatMessages: {
slots: {
root: 'w-full flex flex-col gap-1 flex-1 px-2.5 [&>article]:last-of-type:min-h-(--last-message-height)',
indicator: 'h-6 flex items-center gap-1 py-3 *:size-2 *:rounded-full *:bg-elevated motion-safe:[&>*:nth-child(1)]:animate-[bounce_1s_infinite] motion-safe:[&>*:nth-child(2)]:animate-[bounce_1s_0.15s_infinite] motion-safe:[&>*:nth-child(3)]:animate-[bounce_1s_0.3s_infinite]',
viewport: 'absolute inset-x-0 top-[86%] data-[state=open]:animate-[fade-in_200ms_var(--ease-out)] data-[state=closed]:animate-[fade-out_200ms_var(--ease-out)]',
autoScroll: 'rounded-full absolute right-1/2 translate-x-1/2 bottom-0'
},
variants: {
compact: {
true: '',
false: ''
}
}
}
}
})
]
})