0

I am getting data as stream which i pass to ReactMarkdown but somehow it syntax highlight doesnot work. however the same string when completed i pass to ReactMarkdown it is working as expected. its seems that i cannot parse partially streamed string with ReactMarkdown ?

here is how i am getting streamed

 const response = await fetch(
    `${publicRuntimeConfig.REACT_APP_API_URL}/api/v1/get_response_stream`,
    {
      method: "POST",
      body: JSON.stringify({
        user_input: user_input,
        topic: router.query.topic,
      }),

      headers: {
        "Content-Type": "application/json",

        authorization: "Bearer " + globaluser.token,
      },
    }
  );
  const reader = response.body.getReader();
  let first = false;
  while (true) {
    const { done, value } = await reader.read();
    if (done) {
      //when copied last one and hardcode it. It does work
      console.log(chats, "Done");
      break;
    }
    const text = new TextDecoder().decode(value);
    let updated = text
      ?.replace(/data:/g, "")
      ?.replace(/data: /g, "")
      ?.replace(/\s+/g, " ");
    //?.replace(/\n/g, "<br>")
    //.trim();
    // console.log(updated);
    if (!first) {
      //add new entry
      setChats((d) => [...d, updated]);
      first = true;
    } else {
      //update last one
      setChats((d) => {
        let existingChats = [...d];
        let newValue = `${
          existingChats[existingChats.length - 1]
        }${updated}`;

        //console.log(newValue);
        existingChats[existingChats.length - 1] = newValue;
        return existingChats;
      });
      bottomRef.current?.scrollIntoView();
    }
  }

here is how i render each chat, The stream one will be last.. Other chat works fine

 {chats.map((chat, i) => 
       <Typography key={i}
          variant='subtitle1'
          sx={{
             whiteSpace: "break-spaces",
          }}
          className='editor'
        >
                         
                          <ReactMarkdown
                            children={chat
                              .replace("then user: ", "")
                              .replace("then system: ", "")
                              .replace("system : ", "")
                              .replace("system: ", "")
                              .replace("user: ", "")
                              .replace(/^(\d+)\.\s/gm, "$1\\. ")
                              .replace(/^\*\s/gm, "\\* ")}
                            components={{
                              code({
                                node,
                                inline,
                                className,
                                children,
                                ...props
                              }) {
                                const match = /language-(\w+)/.exec(
                                  className || ""
                                );
                                return !inline && match ? (
                                  <SyntaxHighlighter
                                    {...props}
                                    children={String(children).replace(
                                      /\n$/,
                                      ""
                                    )}
                                    style={dark}
                                    language={match[1]}
                                    PreTag='div'
                                  />
                                ) : (
                                  <code {...props} className={className}>
                                    {children}
                                  </code>
                                );
                              },
                            }}
                          />
      </Typography>
)}
Muhammad Sami
  • 520
  • 2
  • 8
  • 21

1 Answers1

0

markdown syntax expects a complete code block to render it with syntax highlighting. The streamed data you are fetching comes in parts so React Markdown cannot apply syntax highlighting.

So you need to design this in a way that markdown only gets a complete code block to parse.

One way to do this is sort of form an intermediate state which keeps accumulating data until a complete markdown is formed. and as soon as a complete block is formed, push that block to you chats state.

something like this:

  let intermediate = '';
  while (true) {
    const { done, value } = await reader.read();
    if (done) {
      console.log(chats, "Done");
      break;
    }
    const text = new TextDecoder().decode(value);
    let updated = text
      ?.replace(/data:/g, "")
      ?.replace(/data: /g, "")
      ?.replace(/\s+/g, " ");

    // add to intermediate string
    intermediate += updated;

    // check if a complete block has formed
    if (intermediate.endsWith('```')) {
      setChats((d) => [...d, intermediate]);
      intermediate = ''; // reset intermediate string
      bottomRef.current?.scrollIntoView();
    }
  }

  // add any remaining content to chats
  if (intermediate.trim() !== '') {
    setChats((d) => [...d, intermediate]);
  }

adjust the if statement based on your data structure.

Super MaxLv4
  • 148
  • 9
  • The problem i need to preview exactly same like chatgpt. In that case i have to render stream data with cursor. Can you suggest me any other markdown library that can achieve this? – Muhammad Sami Jul 14 '23 at 05:59
  • I did something like that but not exactly in real time, I only preview the effect, after I fetched the data. Yes it will have some latency but not really noticeable. – Super MaxLv4 Jul 14 '23 at 06:10