ChatMessages (대화 메시지)
Usage
ChatMessages 구성 요소는 기본 슬롯 또는 messages prop를 사용하여 ChatMessage 구성 요소 목록을 표시합니다.
<template>
<UChatMessages>
<UChatMessage
v-for="(message, index) in messages"
:key="index"
v-bind="message"
/>
</UChatMessages>
</template>
- 로드 시 초기 스크롤 (
shouldScrollToBottom). - 새 메시지가 도착하면 계속 아래로 스크롤합니다 (
shouldAutoScroll). - 스크롤할 때 자동 스크롤 버튼이 나타나 사용자가 최신 메시지(
autoScroll)로 다시 이동할 수 있습니다. 보조 장치가 처리하는 동안 - A 로드 표시기가 표시됩니다(status). - Submitted 메시지는 뷰포트의 맨 위로 스크롤되고 마지막 사용자 메시지의 높이가 동적으로 조정됩니다.
Message 정보
messages prop를 사용하여 채팅 메시지 목록을 표시합니다.
<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 소품을 사용하여 시각적 표시기를 표시합니다.
<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 컴포블 가능 상태의 다양한 세부 정보입니다.submitted: 메시지가 API로 전송되었으며 응답 스트림의 시작을 기다리고 있습니다.streaming: 응답이 API에서 활발하게 스트리밍되어 데이터 청크를 수신합니다.ready: 전체 응답을 수신하고 처리했으며 새 사용자 메시지를 제출할 수 있습니다.error: API 요청 중에 오류가 발생하여 성공적으로 완료되지 않습니다.
User 사용자
user prop을 사용하여 user 메시지에 대한 [ChatMessage](xph14xxph15x prop을 변경합니다. 기본값은 다음과 같습니다.
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 prop을 사용하여 ](/docs/components/chat-message) prop을 assistant 메시지에 대한 [ChatMessagexph178) prop을 변경합니다. 기본값은 다음과 같습니다.
side: 'left'- {lang="ts-type"} (-side: 'left') /side: 'left'/ -side: 'left'/ {lang="ts-type"} / - {lang="ts-type"} / xph181side: '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'(-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 자동 스크롤
메시지가 스트리밍되는 동안 should-auto-scroll Prop을 사용하여 연속 자동 스크롤을 활성화/비활성화합니다. 기본값은 false입니다.
<template>
<UChatMessages :messages="messages" should-auto-scroll />
</template>
아래로 스크롤해야 합니다.
구성 요소가 마운트될 때 should-scroll-to-bottom 소품을 사용하여 아래쪽 자동 스크롤을 활성화/비활성화합니다. 기본값은 true입니다.
<template>
<UChatMessages :messages="messages" :should-scroll-to-bottom="false" />
</template>
예제
LED 슬롯 포함
#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 (### 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 구성 요소의 모든 슬롯을 사용할 수 있으며, messages prop을 사용할 때 개별 메시지를 사용자 정의 할 수 있도록 자동으로 전달됩니다.<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>
exose 소개
템플릿 참조를 통해 컴포넌트에 액세스하는 경우 다음을 사용할 수 있습니다.
| 이름 Name | 유형 (Type) |
|---|---|
registerMessageRef(id: string, element: ComponentPublicInstance | null) (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: ''
}
}
}
}
})
]
})