自学内容网 自学内容网

react实现模拟chatGPT问答页

需求

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

前端方法

用原始的方案

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

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请求


 <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

npm install @microsoft/fetch-event-source
或
yarn add @microsoft/fetch-event-source

//在代码当中进行引入
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();


原文地址:https://blog.csdn.net/m0_60676278/article/details/144065364

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!