ChatGPT的回复即时渲染

我对前端不是很熟,然后chagpt玩的多,所以这两天想写个chagpt网页练练手,熟悉一下前端。可是我在实现消息的渲染遇到了问题。一开始,我没有使用流式,然后代码使用Prism、文本使用marked直接渲染还是很方便的。可是当我使用流式以后,发现两者渲染都需要将整段文本段落放入才行,而且代码也是拼凑在一行失去了排版。我看到官网对于文本会先显示markdown标识然后渲染成html,但是排版都是如何实现的,我也是没有很好的思路,目前就是将后端传过来的数据分成三份:第一段文本、代码、后面的文本,html+css+js,很粗糙,就放请求部分吧。有没有大佬指点 :sob:

// 发起请求
    async function postMessageAndHandleResponse(url, formData) {
      const resp = await fetch(url, {
        method: 'POST',
        body: formData
      });
      const reader = resp.body.getReader();
      const decoder = new TextDecoder('utf-8');
    
      let accumulatedText = '';
      let a = '';
      let c = '';
      let pos;
      let isCodeBlock = false;
      let codeCount = 0;
      let codeType = "";
      const replyElement = document.createElement("div");
      const codeElement = document.createElement("div");
      const replyBakElement = document.createElement("div");
      
      while(true) {
        const { done, value } = await reader.read();
        if(done){
          console.log('a:', a);
          console.log('c:', c);
  
          // replyElement.innerHTML = marked.parse(a);
          // chatBox.appendChild(replyElement);
          // const providedByText = document.createElement("div");
          // providedByText.innerHTML = 'provided by <span class="highlight">mhchat</span>';
          // providedByText.classList.add("provided-by");
          // replyElement.appendChild(providedByText);
          // chatBox.scrollTop = chatBox.scrollHeight;
          break;
        }
        
        const textChunk = decoder.decode(value);
        //console.log('textChunk:', textChunk); 
        accumulatedText += textChunk;
        // console.log('accumulatedText:', JSON.stringify(accumulatedText));
        // console.log('Index of "\\n\\n" in accumulatedText:', accumulatedText.indexOf("\n\n"));
        // 处理可能的多个消息
        
        // 每个消息以两个换行符结尾
        while ((pos = accumulatedText.indexOf("\n\n")) !== -1) { 
          const message = accumulatedText.substring(0, pos).trim();
          accumulatedText = accumulatedText.substring(pos + 2);
          console.log('message:', message);
    
            //代码块开始
            if (message === '```' || message === '``') {
              codeCount += 1;
              isCodeBlock = !isCodeBlock;
              console.log("isCode Change:" + isCodeBlock + ",count:" + codeCount);
              continue;
            //代码块结束
            }else if(message === '`' && codeCount == 3) {
              console.log("代码块结束 isCode Change:" + isCodeBlock);
              codeElement.classList.add("message", isCodeBlock ? "code-container" : "received");
              const codeBlock = document.createElement("code");
              if(codeCount === 3) {
                codeCount += 1;
                console.log('code Type message2:', codeType);
                codeBlock.className = `language-${codeType}`;
              }
              // 设置代码文本
              codeBlock.textContent = c; 
              const preElement = document.createElement("pre");
              preElement.appendChild(codeBlock);
              codeElement.appendChild(preElement);
              // 在这里调用Prism.highlightElement处理代码高亮
              Prism.highlightElement(codeBlock);
              // 将处理完成的replyElement添加到消息框
              chatBox.appendChild(codeElement); 
            }else if(codeCount === 1) {
              codeCount += 1;
              console.log('code Type message1:', message);
              codeType = message;
            }
            replyElement.classList.add("message", isCodeBlock ? "code-container" : "received");
            if (isCodeBlock) {
              c += message;
              // const codeBlock = document.createElement("code");
              // if(codeCount === 2) {
              //   codeCount += 1;
              //   console.log('code Type message2:', codeType);
              //   codeBlock.className = `language-${codeType}`;
              // }
              // codeBlock.textContent += message; 
              // const preElement = document.createElement("pre");
              // preElement.appendChild(codeBlock);
              // replyElement.appendChild(preElement);
              // // 在这里调用Prism.highlightElement
              // Prism.highlightElement(codeBlock);
            } else {
              if(codeCount === 4) {
                replyBakElement.classList.add("message", isCodeBlock ? "code-container" : "received");
                replyBakElement.textContent += message;
                chatBox.appendChild(replyBakElement);
              }else {
                a += message;
                // 如果是普通文本或图片链接,直接设置innerHTML
                //replyElement.innerHTML += marked.parse(message);
                replyElement.textContent = a;
                chatBox.appendChild(replyElement);
              }
            } 
            chatBox.scrollTop = chatBox.scrollHeight; 
        }
      }    
  }

官网也是marked渲染的吗?

1 个赞

我看官网在回复的时候,加粗文字会先显示**,然后完成渲染。然后我使用marked渲染也是这样的,就是没有换行,不知道怎么完成排版的。我是要手动的一个一个处理数据吗 :joy:

好了,我自己的问题,后端的转义没有处理好 :joy:

这种处理太费劲,送你个好东西:https://unpkg.com/[email protected]/dist/markdown-it.min.js

好的,谢谢,让我试试 :+1: