react实现模拟chatGPT问答页

目录

需求

需要使用react实现模拟chatGPT的页面,后端接口使用流式传输 stream: true,并且用 POST 方法进行传参

前端方法

用原始的方案

大概思路:使用 fetch 接受数据,然后读取数据流,解析数据,获取到需要的数据结构,然后再封装展示的方法,html 用原生js获取id实现页面的展示,展示的过程中自定义定时器,实现打字机的效果

以下代码:

javascript 复制代码
if (input.trim()) {
    const streamUrl = '/opentrek-chat/completions';

    fetch(streamUrl, {
        method: 'POST',
        headers: {
            'Content-Type': '',//根据需求写
            'Authorization': '', //根据需求写
        },
        body: JSON.stringify({
            "messages": [
                {
                    "role": "user",
                    "content": "全新CT6照明功能介绍"
                },
            ],
            "stream": true,
        }),
    })
    .then(response => {
        const decoder = new TextDecoder('utf-8');
        const reader = response.body.getReader();
        let buffer = ''; // 用于累计流数据
        let allMessagesArr = []; // 用于存储所有的消息内容

        // 使用 async/await 来简化递归流读取
        async function readStream() {
            try {
                const { done, value } = await reader.read();
                if (done) {
                    console.log('流读取完成');
                    // 解析并处理所有流数据
                    handleMessages();
                    return;
                }

                // 解码流数据并更新 buffer
                const chunk = decoder.decode(value, { stream: true });
                buffer += chunk;
                readStream(); // 继续读取流数据
            } catch (error) {
                console.error("读取流数据出错:", error);
            }
        }

        // 处理流数据并提取消息
        function handleMessages() {
            // 以行为单位拆分消息
            const messages = buffer.split('\n')
                .filter(item => item.trim() !== '' && item.startsWith('data: '))
                .map(item => item.slice(5).replace(/\n$/, '').trim()) // 去掉 'data: ' 和末尾的换行
                .filter(item => item !== '[DONE]') // 忽略 '[DONE]' 标记
                .map(item => {
                    try {
                        return JSON.parse(item)?.message?.content; // 解析 JSON 数据并提取 content
                    } catch (e) {
                        console.error("消息解析失败:", e);
                        return null;
                    }
                })
                .filter(Boolean); // 去除无效数据

            // 将所有有效消息添加到 allMessagesArr 中
            allMessagesArr = allMessagesArr.concat(messages);
            displayMessages(allMessagesArr); // 调用函数展示消息
        }

        // 展示逐条输出消息
        async function displayMessages(messages) {
            let index = 0;
            const typingElement = document.getElementById('typing-element'); // 假设这是你要展示消息的 DOM 元素

            async function type() {
                if (index < messages.length) {
                    typingElement.textContent += messages[index];  // 展示当前消息
                    index++;
                    await new Promise(resolve => setTimeout(resolve, 50)); // 延时 50ms
                    type();  // 递归调用实现逐条输出
                }
            }

            // 调用逐条输出消息的函数
            await type();

            // 更新 React 状态,展示所有有效消息
            setMessages(prevMessages => [...prevMessages, ...messages]);
        }

        // 开始读取流数据
        readStream();

    })
    .catch(error => {
        console.error('请求出错:', error);
    });
}

用SSE实现方案

如果后端是get请求的话,可以直接用SSE的方案,一下是一个使用vue实现的demo

react实现的方法大差不差

GET请求

js 复制代码
 <template >
  <div>
    <h1>Streamed Data</h1>
    <div v-for="(message, index) in messages" :key="index">
    {{ message }}
  </div>
  </div >
</template >

<script>
export default {
  data() {
    return {
      messages: [], // 保存从服务端接收到的流式数据
    };
  },
  methods: {
    connectToStream() {
      // 创建 EventSource 对象,连接后端流式接口
      const eventSource = new EventSource(`http://192.168.254.200:8081/ask/stream`);

      // 监听 'message' 事件(默认事件)
      eventSource.onmessage = (event) => {
        try {
          const data = event.data; // 解析 JSON 数据
          this.messages.push(data); // 将数据添加到消息列表
        } catch (error) {
          console.error("Error parsing JSON:", error);
        }
      };

      // 错误处理
      eventSource.onerror = (error) => {
        console.error("EventSource failed:", error);
        eventSource.close(); // 关闭连接
      };
    },
  },
  mounted() {
    this.connectToStream(); // 在组件挂载时启动 SSE 连接
  },
};
</script>

<style>
/* 可选样式 */
h1 {
  color: #333;
}
div {
  font-family: Arial, sans-serif;
  margin: 10px 0;
}
</style>

POST请求

EventSource(SSE):是一种持久的连接,用于从服务器向客户端推送实时更新,通常是 GET 请求,它会持续保持连接,不会像普通的请求那样一次性响应数据。

所以我们需要通过转化,来实现POST获取SSE的数据流

需要下载插件:@microsoft/fetch-event-source

javascript 复制代码
npm install @microsoft/fetch-event-source
或
yarn add @microsoft/fetch-event-source
javascript 复制代码
//在代码当中进行引入
import { fetchEventSource } from '@microsoft/fetch-event-source';

//这个方法写到触发的事件当中
async function startSseWithPost() {
    try {
        // 启动服务器事件源请求
        await fetchEventSource('/opentrek-chat/v2/completions', {
            method: 'POST',
            headers: {
                'Content-Type': '',//看项目需求
                'Authorization': '', //看项目需求
            },
            body: JSON.stringify({
                "messages": [
                    {
                        "role": "user",
                        "content": "全新CT6照明功能介绍"
                    },
                ],
                "stream": true,
            }),
            onopen(response) {
                // 连接建立成功时的回调
                console.log('连接已建立:', response);
            },
            onmessage(event) {
                // 处理收到的消息
                if (event.data) {
                    try {
                        const messageData = JSON.parse(event.data);
                        if (messageData?.message?.content) {
                            // 显示接收到的消息内容,封装的方法
                            displayMessage(messageData.message.content);
                        } else {
                            console.error('接收到的数据没有有效的消息内容');
                        }
                    } catch (error) {
                        // 解析消息数据时出现错误
                        console.error('解析消息数据时出错:', error);
                    }
                }
            },
            onerror(err) {
                // 发生错误时的回调
                console.error('SSE 发生错误:', err);
            },
        });
    } catch (error) {
        // 启动 SSE 连接时的错误处理
        console.error('启动 SSE 失败:', error);
    }
}

function displayMessage(messageContent) {
    // 获取消息显示的容器
    const messageContainer = document.getElementById('text');
    if (!messageContainer || !messageContent) {
        console.error('无效的消息内容或容器未找到');
        return;
    }

    // 创建新的元素来显示消息
    const newMessageElement = document.createElement('p');
    messageContainer.appendChild(newMessageElement);

    // 使用 requestAnimationFrame 进行逐字显示效果
    let index = 0;
    const typingSpeed = 100;  // 每个字符的显示间隔时间(毫秒)

    // 清空之前的消息内容
    newMessageElement.textContent = '';

    // 定义逐字显示的函数
    function typeNextCharacter() {
        if (index < messageContent.length) {
            newMessageElement.textContent += messageContent[index];  // 添加下一个字符
            index++;
            // 使用 requestAnimationFrame 来平滑显示字符
            setTimeout(() => requestAnimationFrame(typeNextCharacter), typingSpeed);
        }
    }

    // 开始逐字显示消息
    requestAnimationFrame(typeNextCharacter);
}

// 启动 SSE 流
startSseWithPost();
相关推荐
蒜蓉大猩猩11 分钟前
Vue.js - 组件化编程
开发语言·前端·javascript·vue.js·前端框架·ecmascript
Guofu_Liao20 分钟前
大语言模型---Llama不同系列的权重参数文件提取;Llama-7B权重文件提取;Llama-8B权重文件提取;主要代码功能解析
人工智能·语言模型·自然语言处理·chatgpt·aigc·llama·python3.11
qq_2147826135 分钟前
ChatGPT如何辅助academic writing?
人工智能·学习·chatgpt
王解42 分钟前
一篇文章读懂 Prettier CLI 命令:从基础到进阶 (3)
前端·perttier
乐闻x1 小时前
最佳实践:如何在 Vue.js 项目中使用 Jest 进行单元测试
前端·vue.js·单元测试
檀越剑指大厂1 小时前
【Python系列】异步 Web 服务器
服务器·前端·python
我是Superman丶1 小时前
【前端】js vue 屏蔽BackSpace键删除键导致页面后退的方法
开发语言·前端·javascript
Hello Dam1 小时前
基于 Spring Boot 实现图片的服务器本地存储及前端回显
服务器·前端·spring boot
小仓桑1 小时前
利用 Vue 组合式 API 与 requestAnimationFrame 优化大量元素渲染
前端·javascript·vue.js
Hacker_xingchen1 小时前
Web 学习笔记 - 网络安全
前端·笔记·学习