Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 3 additions & 40 deletions components/message.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,8 @@
'use client'

import { StreamableValue, useStreamableValue } from 'ai/rsc'
import { MemoizedReactMarkdown } from './ui/markdown'
import rehypeExternalLinks from 'rehype-external-links'
import remarkGfm from 'remark-gfm'
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
import 'katex/dist/katex.min.css'
import { StreamableValue } from 'ai/rsc'
import { StreamingMarkdown } from './ui/StreamingMarkdown'

export function BotMessage({ content }: { content: StreamableValue<string> }) {
const [data, error, pending] = useStreamableValue(content)

// Currently, sometimes error occurs after finishing the stream.
if (error) return <div>Error</div>

//modify the content to render LaTeX equations
const processedData = preprocessLaTeX(data || '')

return (
<div className="overflow-x-auto">
<MemoizedReactMarkdown
rehypePlugins={[[rehypeExternalLinks, { target: '_blank' }], rehypeKatex]}
remarkPlugins={[remarkGfm, remarkMath]}
className="prose-sm prose-neutral prose-a:text-accent-foreground/50"
>
{processedData}
</MemoizedReactMarkdown>
</div>
)
}

// Preprocess LaTeX equations to be rendered by KaTeX
// ref: https://github.com/remarkjs/react-markdown/issues/785
const preprocessLaTeX = (content: string) => {
const blockProcessedContent = content.replace(
/\\\[([\s\S]*?)\\\]/g,
(_, equation) => `$$${equation}$$`
)
const inlineProcessedContent = blockProcessedContent.replace(
/\\\(([\s\S]*?)\\\)/g,
(_, equation) => `$${equation}$`
)
return inlineProcessedContent
return <StreamingMarkdown content={content} />
}
90 changes: 90 additions & 0 deletions components/ui/StreamingMarkdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'use client'

import { StreamableValue, useStreamableValue } from 'ai/rsc'
import { MemoizedReactMarkdown } from './markdown'
import { motion } from 'framer-motion'
import rehypeExternalLinks from 'rehype-external-links'
import remarkGfm from 'remark-gfm'
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
import 'katex/dist/katex.min.css'

export function StreamingMarkdown({
content
}: {
content: StreamableValue<string>
}) {
const [data] = useStreamableValue(content)

const processedData = preprocessLaTeX(data || '')

const words = processedData.split(' ')

return (
<div className="overflow-x-auto">
<MemoizedReactMarkdown
rehypePlugins={[[rehypeExternalLinks, { target: '_blank' }], rehypeKatex]}
remarkPlugins={[remarkGfm, remarkMath]}
className="prose-sm prose-neutral prose-a:text-accent-foreground/50"
components={{
p: ({ children }) => {
const words = String(children).split(' ')
return (
<p>
{words.map((word, i) => (
<motion.span
key={i}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{
duration: 0.25,
delay: i * 0.05
}}
>
{word}{' '}
</motion.span>
))}
</p>
)
},
li: ({ children }) => {
const words = String(children).split(' ')
return (
<li>
{words.map((word, i) => (
<motion.span
key={i}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{
duration: 0.25,
delay: i * 0.05
}}
>
{word}{' '}
</motion.span>
))}
</li>
)
}
}}
>
{processedData}
</MemoizedReactMarkdown>
</div>
)
}

// Preprocess LaTeX equations to be rendered by KaTeX
// ref: https://github.com/remarkjs/react-markdown/issues/785
const preprocessLaTeX = (content: string) => {
const blockProcessedContent = content.replace(
/\\\[([\s\S]*?)\\\]/g,
(_, equation) => `$$${equation}$$`
)
const inlineProcessedContent = blockProcessedContent.replace(
/\\\(([\s\S]*?)\\\)/g,
(_, equation) => `$${equation}$`
)
return inlineProcessedContent
}
Empty file added server.log
Empty file.