Beyond boring 🙄 markdown rendering with LLMs ✨ and React ⚛️

fibonacid

Lorenzo Rivosecchi

Posted on October 28, 2024

Beyond boring 🙄 markdown rendering with LLMs ✨ and React ⚛️

Sometimes rendering LLM outputs simply means taking the response text and shoving it inside a paragraph:

function LLMOutput({ children }) {
   return <p>{children}</p>
}
Enter fullscreen mode Exit fullscreen mode

This works for simple LLM response like "What's your favorite color" but maybe it's not the best solutions for a question like "Explain the reasons that brought us WW1". Complicated topics cannot be well represented by a simple paragraph, in fact if you ask this questions to ChatGPT this is the output:

World War I was ignited by a complex mix of political, economic, and social factors that had been building for years. Key reasons include:

1. **Alliances**: European nations had formed tangled alliances for protection. Germany allied with Austria-Hungary and Italy (Triple Alliance), while France, Russia, and Britain formed the Triple Entente. These alliances created a precarious balance where conflict between two countries could draw in many others.

2. **Nationalism**: Intense pride and competition among European nations fueled tensions. Nationalistic fervor often led to a desire for dominance, particularly in regions like the Balkans, where Slavic nationalism threatened Austria-Hungary's stability.

[...]
Enter fullscreen mode Exit fullscreen mode

If you put this text inside a paragraph it would look pretty bad, that's where Markdown rendering can help you. Let's install a markdown-to-jsx to help us render markdown with React:

npm i markdown-to-jsx
# pnpm add markdown-to-jsx
# yarn add markdown-to-jsx
Enter fullscreen mode Exit fullscreen mode

Then just swap the p tag with the Markdown component:

import Markdown from 'markdown-to-jsx';

function LLMOutout({ text }) {
  return <Markdown>{text}</Markdown>
}
Enter fullscreen mode Exit fullscreen mode

With this simple change, the response is rendered as an expressive document with numbered lists and bold keywords. Beautiful, but we can go further. There is no slideshow syntax in markdown so how do we render this? Half of the answer is in this modified prompt:

Explain the reasons that brought us WW1.
Instead of rendering the reasons as a list, use the Slideshow component like in this example:

'''
# My slides

Below this text, a slideshow will appear 🪄

<Slideshow slides={[{ "id": 1, text: "first slide" },{ "id": 2, text: "second slide" }]}
'''
Enter fullscreen mode Exit fullscreen mode

Here is an answer that ChatGPT gave me with this query:

# Causes of World War I

Below this text, a slideshow will appear :magic:

<Slideshow slides={[
  { "id": 1, "text": "The Rise of Nationalism: National pride and competitive fervor among nations fueled tension. Countries like Germany, France, and Britain were proud of their military power and colonial empires, creating a rivalry that would later ignite conflict." },
  { "id": 2, "text": "The Alliance System: European powers formed alliances for mutual protection. Two major blocs emerged—the Triple Entente (France, Russia, Britain) and the Triple Alliance (Germany, Austria-Hungary, Italy). This network of alliances meant that a conflict between two countries could easily escalate." },
  { "id": 3, "text": "Militarization and Arms Race: With militarization on the rise, nations were investing heavily in their armies and navies. Germany and Britain especially competed in a naval arms race, creating massive fleets, which fostered distrust and prepared them for war." },
  { "id": 4, "text": "Imperialism: European powers were vying for colonial dominance, leading to conflicts in Africa, Asia, and the Balkans. This imperial competition heightened tensions and fueled a sense of entitlement and rivalry among nations." },
  { "id": 5, "text": "The Assassination of Archduke Franz Ferdinand: On June 28, 1914, the assassination of Austria-Hungary’s heir in Sarajevo by a Bosnian Serb nationalist triggered a chain reaction. Austria-Hungary’s ultimatum to Serbia led to a series of declarations of war, dragging the allied powers into conflict." }
]} />
Enter fullscreen mode Exit fullscreen mode

This is great but how do we actually render the slideshow?
The answer is HTML tag overrides! Let's pass some options to the Markdown component to include an override for the non-existent "Slideshow" tag:

import Markdown from "markdown-to-jsx";

const options = {
  overrides: {
    Slideshow: {
      component: Slideshow,
    },
  },
};

function LLMOutput({ children }) {
  return <Markdown options={options}>{children}</Markdown>;
}

function Slideshow({ slides }) {
  return (
    <div>
      <p>This is not a slideshow but you get the point</p>
      <pre style={{ whiteSpace: "pre-wrap" }}>
        {JSON.stringify(slides, null, 2)}
      </pre>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Et Vue-a-Là, or whatever they say at the end of front-end blogs 🙄


Closing notes

  • It seems like you can do this also with react-markdown
  • If you want to play with this you can fork this repo i made
  • For streaming i recommend accumulating chunks and flushing state updates on newlines
đź’– đź’Ş đź™… đźš©
fibonacid
Lorenzo Rivosecchi

Posted on October 28, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related