Repository: skovy/llm-markdown
Branch: main
Commit: 5f404dfd20f4
Files: 25
Total size: 40.8 KB
Directory structure:
gitextract_7slcflxo/
├── .eslintrc.json
├── .gitignore
├── LICENSE
├── README.md
├── next.config.js
├── package.json
├── postcss.config.js
├── src/
│ ├── app/
│ │ ├── api/
│ │ │ └── chat/
│ │ │ └── route.ts
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ ├── markdown/
│ │ │ └── page.tsx
│ │ └── page.tsx
│ ├── components/
│ │ ├── assistant-message.tsx
│ │ ├── dialog.tsx
│ │ ├── empty-message.tsx
│ │ ├── message-list.tsx
│ │ ├── message.tsx
│ │ ├── model-dialog.tsx
│ │ ├── nav.tsx
│ │ ├── token-dialog.tsx
│ │ └── user-message.tsx
│ └── hooks/
│ ├── use-local-storage.ts
│ └── use-markdown-processor.tsx
├── tailwind.config.js
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.json
================================================
{
"extends": "next/core-web-vitals"
}
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2023 Spencer Miskoviak
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# 📝 LLM Markdown
A [Nextjs](https://nextjs.org) app demonstrating how to display rich-text responses from Large Language Models (LLMs) by prompting and rendering Markdown formatting, Mermaid diagrams, and LaTeX equations.
Read more in this blog post: [Rendering rich responses from LLMs](https://www.skovy.dev/blog/vercel-ai-rendering-markdown)
## Examples
This example is asking for the top grossing movies, structured as a Mermaid pie chart.

This example is asking when vegetables should be planted, structured as a Mermaid Gantt chart.

## Technologies
- [Nextjs](https://nextjs.org)
- [Vercel AI](https://sdk.vercel.ai/docs)
- [`remark`](https://remark.js.org)
- [`mermaid`](https://mermaid.js.org)
- [`latex.js`](https://latex.js.org)
- And more...
## Setup
- Clone the project
- `npm install`
- `npm run dev`
- Open in your browser
- Set your OpenAI API Key
================================================
FILE: next.config.js
================================================
/** @type {import('next').NextConfig} */
const nextConfig = {}
module.exports = nextConfig
================================================
FILE: package.json
================================================
{
"name": "nextjs-ai-markdown",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@capsizecss/metrics": "^1.2.0",
"@phosphor-icons/react": "^2.0.10",
"@radix-ui/react-dialog": "^1.0.4",
"@themosaad/tailwindcss-capsize": "^1.0.0",
"@types/node": "20.3.3",
"@types/react": "18.2.14",
"@types/react-dom": "18.2.6",
"@vercel/analytics": "^1.0.1",
"ai": "2.1.12",
"autoprefixer": "10.4.14",
"classnames": "^2.3.2",
"eslint": "8.44.0",
"eslint-config-next": "13.4.7",
"latex.js": "^0.12.6",
"mermaid": "^10.2.4",
"next": "13.4.7",
"openai-edge": "^1.1.1",
"postcss": "8.4.24",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-keyed-flatten-children": "^2.2.1",
"rehype-highlight": "^6.0.0",
"rehype-react": "^7.2.0",
"remark-gfm": "^3.0.1",
"remark-parse": "^10.0.2",
"remark-rehype": "^10.1.0",
"tailwindcss": "3.3.2",
"typescript": "5.1.6",
"unified": "^10.1.2"
},
"devDependencies": {
"prettier-plugin-organize-imports": "^3.2.2"
}
}
================================================
FILE: postcss.config.js
================================================
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
================================================
FILE: src/app/api/chat/route.ts
================================================
import { OpenAIStream, StreamingTextResponse } from "ai";
import { NextResponse } from "next/server";
import {
ChatCompletionRequestMessageRoleEnum,
Configuration,
OpenAIApi,
} from "openai-edge";
export const runtime = "edge";
const SYSTEM_PROMPT = `You are a general answering assistant that can comply with any request.
You always answer the with markdown formatting. You will be penalized if you do not answer with markdown when it would be possible.
The markdown formatting you support: headings, bold, italic, links, tables, lists, code blocks, and blockquotes.
You do not support images and never include images. You will be penalized if you render images.
You also support Mermaid diagrams. You will be penalized if you do not render Mermaid diagrams when it would be possible.
The Mermaid diagrams you support: sequenceDiagram, flowChart, classDiagram, stateDiagram, erDiagram, gantt, journey, gitGraph, pie.
You also support LaTeX equation syntax only in markdown code blocks with the "latex" language.
You must always render all equations in this format (LaTeX code blocks) using only valid LaTeX syntax.
For example:
\`\`\`latex
\\[ F = \\frac{{G \\cdot m_1 \\cdot m_2}}{{r^2}} \\]
\`\`\`latex
`;
export async function POST(req: Request) {
const { messages, token, model = "gpt-3.5-turbo" } = await req.json();
const configuration = new Configuration({ apiKey: token });
const openai = new OpenAIApi(configuration);
try {
const response = await openai.createChatCompletion({
model,
stream: true,
messages: [
{
role: ChatCompletionRequestMessageRoleEnum.System,
content: SYSTEM_PROMPT,
},
...messages,
],
});
if (response.status >= 300) {
const body = await response.json();
return NextResponse.json(
{ error: `OpenAI error encountered: ${body?.error?.message}.` },
{ status: response.status }
);
}
const stream = OpenAIStream(response);
return new StreamingTextResponse(stream);
} catch (e) {
console.error(e);
return NextResponse.json(
{ error: "An unexpected error occurred. Please try again later." },
{ status: 500 }
);
}
}
================================================
FILE: src/app/globals.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
================================================
FILE: src/app/layout.tsx
================================================
import { Analytics } from "@vercel/analytics/react";
import classNames from "classnames";
import { Inter, JetBrains_Mono } from "next/font/google";
import "./globals.css";
const sans = Inter({
subsets: ["latin"],
variable: "--font-sans",
});
const monospace = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-mono",
});
export const metadata = {
title: "LLM Markdown",
description:
"App demo for rendering rich-text (markdown) from a Large Language Model.",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}
================================================
FILE: src/app/markdown/page.tsx
================================================
"use client";
import { Nav } from "@/components/nav";
import {
MARKDOWN_TEST_MESSAGE,
useMarkdownProcessor,
} from "@/hooks/use-markdown-processor";
export default function Chat() {
const content = useMarkdownProcessor(MARKDOWN_TEST_MESSAGE);
return (
Supported Markdown
Below is the markdown that is supported in the LLM responses.
This app is a demo for supporting rich-text responses from a{" "}
Large Language Model
{" "}
(LLM). Once you provide your OpenAI API Key, you can start prompting and
receiving rich-text responses.
Try something like:{" "}
"Top 10 grossing movies of all time as a pie chart"
How it works
A system prompt is used to encourage the LLM to generate a response with
Markdown and Mermaid formatting.
The response is rendered using{" "}
unified
,{" "}
Mermaid
, and{" "}
LaTeX.js
{" "}
with{" "}
Nextjs
.
For an example of the supported formatting, see{" "}
supported markdown
.
);
} else {
return ;
}
};
const Mermaid = ({ content }: { content: string }) => {
const [diagram, setDiagram] = useState(true);
useEffect(() => {
const render = async () => {
// Generate a random ID for mermaid to use.
const id = `mermaid-svg-${Math.round(Math.random() * 10000000)}`;
// Confirm the diagram is valid before rendering.
if (await mermaid.parse(content, { suppressErrors: true })) {
const { svg } = await mermaid.render(id, content);
setDiagram(svg);
} else {
setDiagram(false);
}
};
render();
}, [content]);
if (diagram === true) {
return (
Rendering diagram...
);
} else if (diagram === false) {
return (
Unable to render this diagram. Try copying it into the{" "}
Mermaid Live Editor
.
);
} else {
return ;
}
};
export const MARKDOWN_TEST_MESSAGE = `
# Heading level 1
This is the first paragraph.
This is the second paragraph.
This is the third paragraph.
## Heading level 2
This is an [anchor](https://github.com).
### Heading level 3
This is **bold** and _italics_.
#### Heading level 4
This is \`inline\` code.
This is a code block:
\`\`\`tsx
const Message = () => {
return
hi
;
};
\`\`\`
##### Heading level 5
This is an unordered list:
- One
- Two
- Three, and **bold**
This is an ordered list:
1. One
1. Two
1. Three
This is a complex list:
1. **Bold**: One
- One
- Two
- Three
2. **Bold**: Three
- One
- Two
- Three
3. **Bold**: Four
- One
- Two
- Three
###### Heading level 6
> This is a blockquote.
This is a table:
| Vegetable | Description |
|-----------|-------------|
| Carrot | A crunchy, orange root vegetable that is rich in vitamins and minerals. It is commonly used in soups, salads, and as a snack. |
| Broccoli | A green vegetable with tightly packed florets that is high in fiber, vitamins, and antioxidants. It can be steamed, boiled, stir-fried, or roasted. |
| Spinach | A leafy green vegetable that is dense in nutrients like iron, calcium, and vitamins. It can be eaten raw in salads or cooked in various dishes. |
| Bell Pepper | A colorful, sweet vegetable available in different colors such as red, yellow, and green. It is often used in stir-fries, salads, or stuffed recipes. |
| Tomato | A juicy fruit often used as a vegetable in culinary preparations. It comes in various shapes, sizes, and colors and is used in salads, sauces, and sandwiches. |
| Cucumber | A cool and refreshing vegetable with a high water content. It is commonly used in salads, sandwiches, or as a crunchy snack. |
| Zucchini | A summer squash with a mild flavor and tender texture. It can be sautéed, grilled, roasted, or used in baking recipes. |
| Cauliflower | A versatile vegetable that can be roasted, steamed, mashed, or used to make gluten-free alternatives like cauliflower rice or pizza crust. |
| Green Beans | Long, slender pods that are low in calories and rich in vitamins. They can be steamed, stir-fried, or used in casseroles and salads. |
| Potato | A starchy vegetable available in various varieties. It can be boiled, baked, mashed, or used in soups, fries, and many other dishes. |
This is a mermaid diagram:
\`\`\`mermaid
gitGraph
commit
commit
branch develop
checkout develop
commit
commit
checkout main
merge develop
commit
commit
\`\`\`
\`\`\`latex
\\[F(x) = \\int_{a}^{b} f(x) \\, dx\\]
\`\`\`
`;
================================================
FILE: tailwind.config.js
================================================
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/hooks/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
fontFamily: {
sans: ["var(--font-sans)", "sans-serif"],
mono: ["var(--font-mono)", "monospace"],
},
capsize: {
fontMetrics: {
sans: require("@capsizecss/metrics/inter"),
monospace: require("@capsizecss/metrics/jetBrainsMono"),
},
},
},
plugins: [require("@themosaad/tailwindcss-capsize")],
};
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}