Thinking
用于显示思考过程的组件。
介绍
Thinking
是一个用于展示思考中状态的组件,支持 状态管理 、内容展开/收起 和 自定义样式。通过不同状态(开始/思考中/完成/错误)的视觉反馈,帮助用户直观理解AI的思考流程。组件内提供灵活的扩展插槽,适合在智能对话、数据分析等场景中使用。
💌 消息
此组件可以和 Bubble
、 BubbleList
等组件一起使用,以实现更丰富的交互体验(例如DeepSeek的思考过程)。
基础用法
基础用法
Thinking 的基础用法
<template>
<div>
<Thinking v-model="thinking" content="思考中..." />
</div>
</template>
<script setup lang="ts">
import { Thinking } from 'vue-chat-pro'
import { ref } from 'vue'
const thinking = ref<boolean>(false)
</script>
<style scoped></style>
💌 props
通过 content
属性可以设置内容展示。
通过 v-model
可以控制组件的展开/收起。
状态
状态用法
通过 status 属性设置组件的状态,一共有四个默认状态,分别是 start、thinking、end、error
<template>
<div style="display: flex; gap: 30px; flex-direction: column; align-items: flex-start; justify-content: flex-start;">
<div>
<Thinking v-model="thinking1" content="开始思考" status="start" />
</div>
<div>
<Thinking v-model="thinking2" content="思考中..." status="thinking" />
</div>
<div>
<Thinking v-model="thinking3" content="思考完成" status="end" />
</div>
<div>
<Thinking v-model="thinking4" content="思考错误" status="error" />
</div>
</div>
</template>
<script setup lang="ts">
import { Thinking } from 'vue-chat-pro'
import { ref } from 'vue'
const thinking1 = ref(false)
const thinking2 = ref(false)
const thinking3 = ref(false)
const thinking4 = ref(false)
</script>
<style scoped></style>
自动收起
自动收起
通过 auto-collapse 属性设置组件的自动收起
<template>
<div>
<el-radio-group v-model="statusValue" style="margin-bottom: 12px;">
<el-radio-button value="thinking">
thinking
</el-radio-button>
<el-radio-button value="end">
end
</el-radio-button>
</el-radio-group>
<Thinking v-model="thinking" :status="statusValue" auto-collapse content="欢迎使用 Vue3_Chat" button-width="150px"
max-width="100%" />
</div>
</template>
<script setup lang="ts">
import { Thinking } from 'vue-chat-pro'
import type { ThinkingStatus } from 'vue-chat-pro/types'
const statusValue = ref<ThinkingStatus>('thinking')
import { ref } from 'vue'
const thinking = ref<boolean>(false)
</script>
<style scoped></style>
禁用
禁用
通过 disabled 属性设置组件的禁用状态
<template>
<div style="display: flex; gap: 30px; flex-direction: column; align-items: flex-start; justify-content: flex-start;">
<div>
<Thinking v-model="thinking1" disabled content="欢迎使用 Vue3_Chat" status="start" />
</div>
<div>
<Thinking v-model="thinking2" disabled content="欢迎使用 Vue3_Chat" status="thinking" />
</div>
<div>
<Thinking v-model="thinking3" disabled content="欢迎使用 Vue3_Chat" status="end" />
</div>
<div>
<Thinking v-model="thinking4" disabled content="欢迎使用 Vue3_Chat" status="error" />
</div>
</div>
</template>
<script setup lang="ts">
import { Thinking } from 'vue-chat-pro'
import type { ThinkingStatus } from 'vue-chat-pro/types'
const statusValue = ref<ThinkingStatus>('thinking')
import { ref } from 'vue'
const thinking1 = ref(false)
const thinking2 = ref(false)
const thinking3 = ref(false)
const thinking4 = ref(false)
</script>
<style scoped></style>
宽度定制
宽度定制
通过 button-width 和 max-width 属性设置组件的宽度
<template>
<div>
<Thinking v-model="thinking1" button-width="250px" max-width="100%" content="欢迎使用 Vue3_Chat" />
</div>
</template>
<script setup lang="ts">
import { Thinking } from 'vue-chat-pro'
import { ref } from 'vue'
const thinking1 = ref<boolean>(false)
</script>
<style scoped></style>
颜色定制
颜色定制
通过 color 和 background-color 属性设置组件的颜色
<template>
<div>
<Thinking v-model="thinking1" color="#fff"
backgroundColor="linear-gradient(to bottom right, rgba(190, 126, 246, 1), rgba(95, 13, 245, 1), rgba(186, 74, 227, 1))"
:lb="false" content="欢迎使用 Vue3_Chat" />
</div>
</template>
<script setup lang="ts">
import { Thinking } from 'vue-chat-pro'
import { ref } from 'vue'
const thinking1 = ref(false)
</script>
<style scoped></style>
深度思考

🧁 用户
男子100米世界最好的成绩是多少。(请用中文回答, 10个字以内)
深度思考
通过深度思考,实现更复杂的思考过程(类似DeepSeek的思考过程)
<template>
<div>
<div style="display: flex">
<el-button @click="callOpenAI" type="primary" :disabled="showOnce">开始请求Open AI</el-button>
</div>
<BubbleList ref="bubbleListRef" :items="items" style="height: 200px; overflow: auto">
<template #header="{ info }">
<div>
{{ info.role === 'ai' ? 'VueChat 🍧' : '🧁 用户' }}
</div>
</template>
<template #content="{ info }">
<Thinking v-if="info.reason" v-model="info.modelValue" :status="info.status" :content="info.reason"
@change="handleChange" autoCollapse />
<div>
{{ info.content }}
</div>
</template>
<template #footer="{ info }">
<div class="footer-wrapper">
<div class="footer-container">
<el-button type="info" :icon="Refresh" size="small" circle />
<el-button type="success" :icon="Search" size="small" circle />
<el-button type="warning" :icon="Star" size="small" circle />
<el-button color="#626aef" :icon="DocumentCopy" size="small" circle />
</div>
<div class="footer-time">
{{ info.role === 'ai' ? '下午 2:32' : '下午 2:33' }}
</div>
</div>
</template>
</BubbleList>
</div>
</template>
<script setup lang="ts">
import type { BubbleDataType, ThinkingStatus } from 'vue-chat-pro/types'
import { DocumentCopy, Refresh, Search, Star } from '@element-plus/icons-vue'
import { Thinking, BubbleList } from 'vue-chat-pro'
import { ref, computed, reactive, watch } from 'vue'
const bubbleListRef = ref<InstanceType<typeof BubbleList>>()
const data = ref<any[]>([])
const showOnce = ref<boolean>(false)
const handleChange = (value: boolean, status: ThinkingStatus) => {
console.log(value, status)
}
const items = reactive<BubbleDataType[]>([
{
role: 'user',
content: '男子100米世界最好的成绩是多少。(请用中文回答, 10个字以内)',
headerProps: 'user头部',
modelValue: true,
placement: 'end',
avatar: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png',
key: `persit_0`,
}
])
// 模拟数据生成函数
function createMockResponse() {
const mockData = [
{
data: JSON.stringify({
choices: [{
delta: {
reasoning_content: "让我思考一下...\n"
}
}]
})
},
{
data: JSON.stringify({
choices: [{
delta: {
reasoning_content: "我需要查找男子100米的世界纪录...\n"
}
}]
})
},
{
data: JSON.stringify({
choices: [{
delta: {
reasoning_content: "根据最新数据,男子100米世界纪录是9.58秒,由博尔特创造。\n"
}
}]
})
},
{
data: JSON.stringify({
choices: [{
delta: {
content: "9.58秒"
}
}]
})
},
{
data: ' [DONE]'
}
]
return mockData
}
async function callOpenAI() {
showOnce.value = true
// 添加新的消息项
items.push({
role: 'ai',
content: '',
headerProps: 'ai头部',
reason: '',
modelValue: true,
status: 'thinking',
placement: 'start',
avatar: 'https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp',
loading: true,
key: `persit_${items.length}`,
transparent: true
})
// 使用模拟数据
const mockResponse = createMockResponse()
// 模拟流式响应
for (const chunk of mockResponse) {
await new Promise(resolve => setTimeout(resolve, 500)) // 模拟延迟
data.value.push(chunk)
}
}
const content = computed(() => {
if (!data.value.length) return { text: '', textReason: '' }
let textReason = ''
let text = ''
for (let index = 0; index < data.value.length; index++) {
const chunk = data.value[index].data
try {
const parsedChunk = JSON.parse(chunk).choices[0].delta
// 优先处理 reasoning_content
if (parsedChunk.reasoning_content !== null) {
textReason += parsedChunk.reasoning_content
}
// 然后处理 content
if (parsedChunk.content) {
text += parsedChunk.content
}
} catch (error) {
if (chunk === ' [DONE]') {
// 处理数据结束的情况
} else {
console.error('解析数据时出错:', error)
}
}
}
return {
text,
textReason
}
})
// 监听content变化,更新items中最后一个对象的content和reason
watch(
() => content.value,
newVal => {
if (items.length > 0) {
const lastItem = items[items.length - 1]
if (lastItem.role === 'ai') {
if (newVal.text.length > 0) {
lastItem.content = newVal.text
lastItem.status = 'end'
lastItem.loading = false
}
if (newVal.textReason.length > 0) {
lastItem.reason = newVal.textReason
lastItem.loading = false
}
}
}
},
{ deep: true }
)
</script>
<style scoped lang="scss">
.footer-wrapper {
display: flex;
align-items: center;
gap: 10px;
.footer-time {
font-size: 12px;
margin-top: 3px;
}
}
.footer-container {
:deep(.el-button+.el-button) {
margin-left: 8px;
}
}
</style>
💌 提示
本例是模拟的DeepSeek的思考过程,实际使用时,请使用 useStream
钩子函数。
插槽
插槽
通过插槽自定义组件的样式
<template>
<el-radio-group v-model="statusValue" style="margin-bottom: 12px;">
<el-radio-button value="start">
start
</el-radio-button>
<el-radio-button value="thinking">
thinking
</el-radio-button>
<el-radio-button value="end">
end
</el-radio-button>
<el-radio-button value="error">
error
</el-radio-button>
</el-radio-group>
<Thinking v-model="thinking" :status="statusValue" content="欢迎使用 VueChat" button-width="200px" max-width="100%">
<template #icon="{ status }">
<span v-if="status === 'start'">😄</span>
<span v-else-if="status === 'error'">😭</span>
<span v-else-if="status === 'thinking'">🤔</span>
<span v-else-if="status === 'end'">😊</span>
</template>
<template #label="{ status }">
<span v-if="status === 'start'">有什么指示嘛?</span>
<span v-else-if="status === 'thinking'">容我想想</span>
<span v-else-if="status === 'end'">想出来了</span>
<span v-else-if="status === 'error'">想不出来</span>
</template>
<template #arrow>
<span>👇</span>
</template>
<template #content="{ content, status }">
<span>{{ status }}: {{ content }}</span>
</template>
<template #error>
<span>抱歉,无法解决您的问题</span>
</template>
</Thinking>
</template>
<script setup lang="ts">
import { Thinking } from 'vue-chat-pro'
import { ref } from 'vue'
import type { ThinkingStatus } from 'vue-chat-pro/types'
const thinking = ref<boolean>(false)
const statusValue = ref<ThinkingStatus>('start')
</script>
<style scoped></style>
💌 slot
通过 icon
插槽可以自定义图标。
通过 label
插槽可以自定义标签。
通过 arrow
插槽可以自定义箭头。
通过 content
插槽可以自定义内容。
通过 error
插槽可以自定义错误内容。
属性
属性名 | 类型 | 是否必填 | 默认值 | 描述 |
---|---|---|---|---|
content | String | 否 | '' | 显示的主要内容文本 无打字效果,由接口返回决定 |
modelValue | Boolean | 是 | 通过 v-model 绑定展开状态,默认为展开状态 | |
status | ThinkingStatus | 否 | 'start' | 组件状态:start (开始) / thinking (思考中) / end (完成) / error (错误) |
autoCollapse | Boolean | 否 | true | 是否在组件状态变为 end 时自动收起内容区域 |
disabled | Boolean | 否 | false | 是否禁用组件交互 |
buttonWidth | String | 否 | '160px' | 触发按钮宽度 |
duration | String | 否 | '0.2s' | 过渡动画时长 |
maxWidth | String | 否 | '500px' | 内容区域最大宽度 |
backgroundColor | String | 否 | '#fcfcfc' | 内容区域背景色 |
color | String | 否 | 'var(--el-color-info)' | 内容文字颜色 |
isBorder | Boolean | 否 | false | 是否显示边框 |
lb | Boolean | 否 | true | 是否显示深度思考左边框 |
事件
事件名 | 参数 | 类型 | 描述 |
---|---|---|---|
@change | { value: boolean, status: ThinkingStatus } | Function | 展开状态或状态变化时触发 |
插槽
插槽名 | 参数 | 类型 | 描述 |
---|---|---|---|
#icon | { status } | Slot | 自定义状态图标 |
#label | { status } | Slot | 自定义按钮文字 |
#arrow | - | Slot | 自定义箭头图标 |
#content | { content, status } | Slot | 自定义内容区域(非错误状态) |
#error | - | Slot | 自定义错误信息内容展示 |