likes
comments
collection
share

来实现一个ChatGPT!!

作者站长头像
站长
· 阅读数 11

动手实现一个简化版的 ChatGPT。对,没错,就是那个神奇的聊天机器人。 话不多说, 直接开整,让它在屏幕上跳舞!

分析

首先是一闪一闪的光标, chatGPT是使用伪元素实现的。以及blink的动画效果 来实现一个ChatGPT!! 我们来看看chatGPT是如何做到一点一点接收返回的消息, 并且有时候还会直接断掉,显示networkerror, 这里一开始想的是用的websocket,但是控制台一看,如下图。并不是ws, 而是使用了fetchAPI的 ReadableStream 特性 在这里使用它的好处就是,可以实时的获取信息, 不必等chatgpt分析完整个请求再一次性的返回, 不然等待时间过长,体验太差。

来实现一个ChatGPT!!

不过值得一提的是New Bing使用的就是ws来实时获取信息的。

"ws" 是 WebSocket 的缩写,而 "event stream" 是指服务器发送事件(Server-Sent Events,SSE)。WebSocket 是一种双向通信协议,旨在在 Web 应用程序中提供实时的数据交换。它使用标准的 HTTP 协议作为握手阶段,随后将 HTTP 连接升级为 WebSocket 连接。WebSocket 连接是一种持久连接,可以在客户端和服务器之间实时传输数据,而不需要进行轮询或定时轮询。WebSocket 可以使用任何数据格式(如 JSON、XML 或二进制数据)进行通信。 相反,Server-Sent Events 是一种仅由服务器向客户端发送数据的单向通信协议。服务器使用标准的 HTTP 协议作为握手阶段,但不升级连接。服务器随后使用 HTTP 连接向客户端发送事件流。这些事件可以是任何格式(如 JSON、XML 或文本),并且事件流通常用于向客户端提供实时更新,例如股票报价、天气预报等。 因此,WebSocket 和 Server-Sent Events 都提供了在 Web 应用程序中实时交换数据的机制,但它们的实现方式略有不同,WebSocket 提供双向通信,而 Server-Sent Events 仅提供单向通信。

实现

服务端

后端部分使用nodejs实现,比较简单

// server.js
const http = require('http');

const server = http.createServer((req, res) => {
    if (req.url === '/event-stream') {
        // 设置跨域头
        res.setHeader('Access-Control-Allow-Origin', '*');
        res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
        res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
        res.setHeader('Content-Type', 'text/event-stream');
        res.setHeader('Cache-Control', 'no-cache');
        res.setHeader('Connection', 'keep-alive');
        res.flushHeaders();

        // 发送事件
        let counter = 0;
        const interval = setInterval(() => {
            counter++;
            res.write(`hello!`); // 发送数据

            if (counter >= 10) {
                clearInterval(interval);
                res.end();
            }
        }, 1000);
    } else {
        res.writeHead(404);
        res.end();
    }
});

const PORT = 3000;
server.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

用setInterval来模拟服务端缓慢的数据处理~~~

前端

首先是基本的样式, 这里就用div来模拟光标

<div id="input-container">
    <span id="text-container"></span>
    <span id="cursor"></span>
</div>
#input-container {
    display: flex;
    align-items: center;
}

#cursor {
    display: inline-block;
    background-color: black;
    width: 8px;
    height: 1em;
    animation: blink 1s infinite;
}

@keyframes blink {
    0% {
        opacity: 1;
    }
    50% {
        opacity: 0;
    }
    100% {
        opacity: 1;
    }
}

然后是js部分

<script type="module">
    const textContainer = document.getElementById('text-container');
    async function fetchEventStream() {
        const response = await fetch('http://localhost:3000/event-stream');

        if (response.ok) {
            const reader = response.body.getReader(); //主要是这里
            const decoder = new TextDecoder('utf-8');

            while (true) {
                const {value, done} = await reader.read();

                if (done) {
                    console.log('发送完毕!');
                    break;
                }

                const data = decoder.decode(value);
                console.log(`接收到的: ${data}`);
                textContainer.textContent += data;
            }
        } else {
            console.error('Failed to fetch event stream.');
        }
    }

    fetchEventStream();
</script>

完成!效果如图:

来实现一个ChatGPT!!

至于怎么变成真的ChatGPT, 哈哈, 当然是接入调参了

最后

一般ReadableStream可以用来处理以下几种情况

  1. 处理大型文件:通过将文件分割为多个小块,您可以在客户端或服务器上逐步处理大型文件,而不是一次性加载整个文件。
  2. 实时数据处理:在处理实时数据流(如传感器数据、股票行情或实时聊天等)时,你可以使用 ReadableStream 在数据到达时立即处理数据,而不需要等待请求完成。
  3. 网络请求:Fetch API 返回的响应对象包含一个 ReadableStream,它允许您逐步读取和处理从服务器返回的数据。这对于处理大型响应、视频流或音频流等非常有用。

更加具体的使用可以参照MDN