| import remarkGfm from 'remark-gfm' | |
| import remarkMath from 'remark-math' | |
| import supersub from 'remark-supersub' | |
| import remarkBreaks from 'remark-breaks' | |
| import { cn } from '@/lib/utils' | |
| import { CodeBlock } from '@/components/ui/codeblock' | |
| import { MemoizedReactMarkdown } from '@/components/markdown' | |
| import { LearnMore } from './learn-more' | |
| import { ChatMessageModel } from '@/lib/bots/bing/types' | |
| import { useEffect } from 'react' | |
| import { TurnCounter } from './turn-counter' | |
| export interface ChatMessageProps { | |
| message: ChatMessageModel | |
| } | |
| export function ChatMessage({ message, ...props }: ChatMessageProps) { | |
| useEffect(() => { | |
| if (document.body.scrollHeight - window.innerHeight - window.scrollY - 200 < 0) { | |
| window.scrollBy(0, 200) | |
| } | |
| }, [message.text]) | |
| return message.text ? ( | |
| <div | |
| className={cn('text-message', message.author)} | |
| {...props} | |
| > | |
| <div className="text-message-content"> | |
| <MemoizedReactMarkdown | |
| linkTarget="_blank" | |
| className="prose break-words dark:prose-invert prose-p:leading-relaxed prose-pre:p-0" | |
| remarkPlugins={[remarkGfm, remarkMath, supersub, remarkBreaks]} | |
| components={{ | |
| img(obj) { | |
| try { | |
| const uri = new URL(obj.src!) | |
| const w = uri.searchParams.get('w') | |
| const h = uri.searchParams.get('h') | |
| if (w && h) { | |
| uri.searchParams.delete('w') | |
| uri.searchParams.delete('h') | |
| return <a style={{ float: 'left', maxWidth: '50%' }} href={uri.toString()} target="_blank" rel="noopener noreferrer"><img src={obj.src} alt={obj.alt} width={w!} height={h!}/></a> | |
| } | |
| } catch (e) { | |
| } | |
| return <img src={obj.src} alt={obj.alt} title={obj.title} /> | |
| }, | |
| p({ children }) { | |
| return <p className="mb-2">{children}</p> | |
| }, | |
| code({ node, inline, className, children, ...props }) { | |
| if (children.length) { | |
| if (children[0] == 'β') { | |
| return ( | |
| <span className="mt-1 animate-pulse cursor-default">β</span> | |
| ) | |
| } | |
| children[0] = (children[0] as string).replace('`β`', 'β') | |
| } | |
| const match = /language-(\w+)/.exec(className || '') | |
| if (inline) { | |
| return ( | |
| <code className={className} {...props}> | |
| {children} | |
| </code> | |
| ) | |
| } | |
| return ( | |
| <CodeBlock | |
| key={Math.random()} | |
| language={(match && match[1]) || ''} | |
| value={String(children).replace(/\n$/, '')} | |
| {...props} | |
| /> | |
| ) | |
| } | |
| }} | |
| > | |
| {message.text} | |
| </MemoizedReactMarkdown> | |
| </div> | |
| <div className="text-message-footer"> | |
| {message.author === 'bot' && <LearnMore sourceAttributions={message.sourceAttributions} />} | |
| {message.author === 'bot' && <TurnCounter throttling={message.throttling} />} | |
| </div> | |
| </div> | |
| ) : null | |
| } | |