Vue创建一个简单的Agent聊天——流式输送

64 阅读2分钟

修改创建LLM方法

设置streaming为true

function createLLM() {
  // 创建LLM
  const llm = new ChatOpenAI({
   ...... // 省略其它
    streaming: true, // 设置
    
  })
  return llm
}

修改chat方法

export default async function chat(
  input: string,
  onChat: (text: string) => void,
  messages: Chat[] = []) {
    ......
  const res = await chain.stream({
    chat_history: allMessages,
    input: input,
  })

  let resText = ""
  // 必须等待每一段数据到达,才能循环处理
  for await (const chunk of res) {
    const content = String(chunk.content) ?? ""
    resText += content
    if (onChat) {
      onChat(String(chunk.content) || "")
    }
  }
}

pinia状态管理

请参考pinia文档

import type { Chat } from "@/types/chat";
import { defineStore } from "pinia";
import { reactive } from "vue";

export const useChat = defineStore('chat', () => {
  const history = reactive<Chat[]>([])

  const add = (chat: Chat) => {
    return history.push(chat) - 1 // 返回新增的索引
  }

  return { history, add }
})

输入组件修改

<template>
    ......
</template>

<script setup lang="ts">
import chat from '@/agent';
import { ElButton, ElMessage } from 'element-plus';
import { computed, ref } from 'vue';
import { useChat } from '@/stores/chat';// 导入创建的pinia状态库
const value = ref<string>('');
const loading = ref(false);
const history = useChat().history; // 修改为pinia保存的数据
const isDisabled = computed(() => !value.value && !loading.value);
const btnText = computed(() => loading.value ? '正在思考...' : '发送');

function randomId() {
  return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
}
const onChatHandler = async () => {
  if (!value.value) return ElMessage.error('请输入内容');
  loading.value = true;

  // 添加用户消息
  useChat().add({
    id: randomId(),
    name: 'user',
    messages: value.value,
    role: "user",
    created_at: new Date(),
  })

  // 获取AI回复,先绑定对象再修改数据信息,否则会将每个传输过来的内容重建为新对象
  const aiResponseItemIndex = useChat().add({
    id: randomId(),
    name: 'assistant',
    messages: '',
    role: "assistant",
    created_at: new Date(),
  })

  try {
    await chat(value.value, (text) => {
      history[aiResponseItemIndex]!.messages += text // 这里是追加
    }, history);
  } catch (error) {
    if (error instanceof Error)
      ElMessage.error('请求错误' + error.message);
  } finally {
    value.value = '';
    loading.value = false;
  }
};

</script>

聊天窗口组件

<template>
  <div class="messageBoard box-border rounded-2xl w-full max-w-200 h-full flex flex-col gap-5 overflow-y-auto bg-white">
    <div class="p-4 " v-if="history.length > 0">
      <template v-for="item in history" :key="item.id">
        <div :class="[
          'flex flex-col mt-5 relative cursor-default',
          item.role === 'user' ? 'items-end ml-10' : 'items-start mr-10'
        ]">
          <div :class="[
            ' p-4  rounded-xl min-w-20',
            item.role === 'user' ? 'bg-gray-100 ml-10' : ' mr-10'
          ]">
            <div>{{ item.messages }}</div>
          </div>
        </div>
      </template>
    </div>
    <div v-else class="w-full h-full flex justify-center items-center">
      <div class=" text-gray-400 font-bold">快来聊天吧(☆▽☆)</div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { useChat } from '@/stores/chat';

const { history } = useChat()
</script>

<style lang="css" scoped>
.messageBoard {
  /* 火狐 */
  scrollbar-width: none;
  /* IE 10+ */
  -ms-overflow-style: none;
}

.messageBoard::-webkit-scrollbar {
  display: none;
}
</style>

主页面修改

<script setup lang="ts">
import InputArea from '@/components/InputArea.vue';
import MessageArea from '@/components/MessageArea.vue';
</script>

<template>
  <div>
    <div class="w-screen h-screen flex flex-col bg-gray-100 pt-2">
      <div class="flex-1 w-full my-0 flex justify-center items-center overflow-y-auto">
        <MessageArea />
      </div>
      <div class="w-full ">
        <InputArea />
      </div>
    </div>
  </div>
</template>

目录