Skip to content

Thinking

用于显示思考过程的组件。

介绍

Thinking 是一个用于展示思考中状态的组件,支持 状态管理 、内容展开/收起 和 自定义样式。通过不同状态(开始/思考中/完成/错误)的视觉反馈,帮助用户直观理解AI的思考流程。组件内提供灵活的扩展插槽,适合在智能对话、数据分析等场景中使用。

💌 消息

此组件可以和 BubbleBubbleList 等组件一起使用,以实现更丰富的交互体验(例如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 插槽可以自定义错误内容。

属性

属性名类型是否必填默认值描述
contentString''显示的主要内容文本 无打字效果,由接口返回决定
modelValueBoolean通过 v-model 绑定展开状态,默认为展开状态
statusThinkingStatus'start'组件状态:start(开始) / thinking(思考中) / end(完成) / error(错误)
autoCollapseBooleantrue是否在组件状态变为 end 时自动收起内容区域
disabledBooleanfalse是否禁用组件交互
buttonWidthString'160px'触发按钮宽度
durationString'0.2s'过渡动画时长
maxWidthString'500px'内容区域最大宽度
backgroundColorString'#fcfcfc'内容区域背景色
colorString'var(--el-color-info)'内容文字颜色
isBorderBooleanfalse是否显示边框
lbBooleantrue是否显示深度思考左边框

事件

事件名参数类型描述
@change{ value: boolean, status: ThinkingStatus }Function展开状态或状态变化时触发

插槽

插槽名参数类型描述
#icon{ status }Slot自定义状态图标
#label{ status }Slot自定义按钮文字
#arrow-Slot自定义箭头图标
#content{ content, status }Slot自定义内容区域(非错误状态)
#error-Slot自定义错误信息内容展示