Spaces:
Sleeping
Sleeping
// Inspired by Chatbot-UI and modified to fit the needs of this project | |
// @see https://github.com/mckaywrigley/chatbot-ui/blob/main/components/Chat/ChatMessage.tsx | |
import remarkGfm from 'remark-gfm'; | |
import remarkMath from 'remark-math'; | |
import { cn } from '@/lib/utils'; | |
import { CodeBlock } from '@/components/ui/CodeBlock'; | |
import { MemoizedReactMarkdown } from '@/components/chat/MemoizedReactMarkdown'; | |
import { IconOpenAI, IconUser } from '@/components/ui/Icons'; | |
import { ChatMessageActions } from '@/components/chat/ChatMessageActions'; | |
import { MessageBase } from '../../lib/types'; | |
export interface ChatMessageProps { | |
message: MessageBase; | |
} | |
const PAIRS: Record<string, string> = { | |
'β': 'β', | |
'β': 'β₯', | |
'β': 'β€', | |
'β': 'β', | |
}; | |
const MIDDLE_STARTER = 'β'; | |
const MIDDLE_SEPARATOR = 'βΏ'; | |
export function ChatMessage({ message, ...props }: ChatMessageProps) { | |
const cleanupMessage = ({ content, role }: MessageBase) => { | |
if (role === 'user') { | |
return { | |
content, | |
}; | |
} | |
const [logs = '', answer = ''] = content.split('<ANSWER>'); | |
const cleanedLogs = []; | |
let left = 0; | |
let right = 0; | |
while (right < logs.length) { | |
if (Object.keys(PAIRS).includes(content[right])) { | |
cleanedLogs.push(content.substring(left, right)); | |
left = right++; | |
while ( | |
right < content.length && | |
PAIRS[content[left]] !== content[right] | |
) { | |
right++; | |
} | |
if (content[left] === MIDDLE_STARTER) { | |
// add the text alignment so it can be shown as a table | |
const separators = logs | |
.substring(left, right) | |
.split(MIDDLE_SEPARATOR).length; | |
if (separators > 0) { | |
cleanedLogs.push( | |
Array(separators + 1) | |
.fill('|') | |
.join(' :- '), | |
); | |
} | |
} | |
left = ++right; | |
} else { | |
right++; | |
} | |
} | |
cleanedLogs.push(content.substring(left, right)); | |
return { | |
logs: cleanedLogs | |
.join('') | |
.replace(/β/g, '|') | |
.split('|\n\n|') | |
.join('|\n|'), | |
content: answer.replace('</</ANSWER>', '').replace('</ANSWER>', ''), | |
}; | |
}; | |
const { logs, content } = cleanupMessage(message); | |
return ( | |
<div className={cn('group relative mb-4 flex items-start')} {...props}> | |
<div | |
className={cn( | |
'flex size-8 shrink-0 select-none items-center justify-center rounded-md border shadow', | |
message.role === 'user' | |
? 'bg-background' | |
: 'bg-primary text-primary-foreground', | |
)} | |
> | |
{message.role === 'user' ? <IconUser /> : <IconOpenAI />} | |
</div> | |
<div className="flex-1 px-1 ml-4 space-y-2 overflow-hidden"> | |
{logs && message.role !== 'user' && ( | |
<div className="bg-slate-100 mb-4 p-4 max-h-60 overflow-auto"> | |
<div className="text-xl font-bold">Thinking Process</div> | |
<MemoizedReactMarkdown | |
className="break-words text-sm" | |
remarkPlugins={[remarkGfm, remarkMath]} | |
components={{ | |
p({ children }) { | |
return ( | |
<p className="my-2 last:mb-0 whitespace-pre-line"> | |
{children} | |
</p> | |
); | |
}, | |
code({ children, ...props }) { | |
return ( | |
<code className="whitespace-pre-line">{children}</code> | |
); | |
}, | |
}} | |
> | |
{logs} | |
</MemoizedReactMarkdown> | |
</div> | |
)} | |
<MemoizedReactMarkdown | |
className="break-words" | |
remarkPlugins={[remarkGfm, remarkMath]} | |
components={{ | |
p({ children }) { | |
return ( | |
<p className="my-2 last:mb-0 whitespace-pre-line">{children}</p> | |
); | |
}, | |
code({ node, inline, className, children, ...props }) { | |
if (children.length) { | |
if (children[0] == 'β') { | |
return ( | |
<span className="mt-1 cursor-default animate-pulse">β</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} | |
/> | |
); | |
}, | |
}} | |
> | |
{content} | |
</MemoizedReactMarkdown> | |
<ChatMessageActions message={message} /> | |
</div> | |
</div> | |
); | |
} | |