Skip to content

EventSource - 實作一個 ChatGPT 打字機效果

Posted on:2023/04/07 09:17:00

前言

從開始使用 ChatGPT 開始就有一個疑問,這種透過 request 的方式是如何做到打字機效果,天真的我以為是將 response 的文字拉回來後再慢慢呈現,發來仔細看才發現,又有新東西可以學了。雖然這東西已經誕生好久好久了 - EventSource

EventSource

EventSource 是一個函式庫,它可以讓你使用 Server-Sent Events (SSE) 技術,讓瀏覽器和伺服器建立單向的即時溝通,基於 Http 的協定使用文字格式作為傳遞,相比 Socket 簡單多了,其他的優點如下:

瀏覽器端

在 html js 的部份需要建立一個 EventSource 物件,並且監聽 message 事件

<div id="output"></div>
const source = new EventSource("網址");
source.addEventListener(
  "message",
  function (e) {
    document.getElementById("output").innerHTML += e.data + " ";
  },
  false
);

在伺服器回傳新的內容時,就會觸發 message 事件,並且將內容加入到 #output 元素中

伺服器端

而在伺服器端的部分,因為是純文字格式,所以在傳輸上有做格式限制,為冒號開頭像是

event: <event-name>
data: <event-data>
id: <event-id>
retry: <milliseconds>

特別需要注意每個訊息間都需要用 \n\n 來做分隔,否則瀏覽器端可能沒辦法做正確的辨識。

在伺服器的部分使用 node Express 來模擬,需要設定 header 的部份為 text/event-streamconnection keep-alive,接著在程式中我引用一段假文,並用空白切割為陣列,再每秒回傳一個單字,直到所有單字都回傳完畢。

const express = require("express");
const cors = require("cors");
const app = express();
app.use(cors());

app.get("/chat", function (req, res) {
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.setHeader("Connection", "keep-alive");

  const text =
    "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
  const textArray = text.split(" ");
  let textIndex = 0;

  let e = setInterval(() => {
    if (textIndex === textArray.length) {
      clearInterval(e);
      res.end();
      return;
    }

    const data = `data: ${textArray[textIndex]}\n\n`;
    res.write(data);
    textIndex++;
  }, 1000);

  req.on("close", () => {
    console.log("Client closed SSE connection");
    clearInterval(e);
    res.end();
  });
});

app.listen(3000, function () {
  console.log("Server is running on port 3000");
});

Demo 效果

感想

以往在程式碼的部分可能都需要兜好幾個文件、文章來拼拼湊湊才能做出一個 demo,不過有了 AI 之後只有幾個 prompts 就可以完成,程式碼的方面基本上也都沒問題,只有少數要進行修改,再來就來看看 demo 的效果吧