This commit is contained in:
Cogent Apps
2023-03-06 05:30:58 -08:00
committed by GitHub
parent 512348c0db
commit 1726f5b0f2
32 changed files with 2784 additions and 0 deletions

267
src/components/message.tsx Normal file
View File

@@ -0,0 +1,267 @@
import styled from '@emotion/styled';
import ReactMarkdown from 'react-markdown';
import { Button, CopyButton, Loader } from '@mantine/core';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
import { Message } from "../types";
import { share } from '../utils';
import { ElevenLabsReaderButton } from '../elevenlabs';
// hide for everyone but screen readers
const SROnly = styled.span`
position: fixed;
left: -9999px;
top: -9999px;
`;
const Container = styled.div`
&.by-user {
}
&.by-assistant {
background: rgba(255, 255, 255, 0.02);
}
&.by-assistant + &.by-assistant, &.by-user + &.by-user {
border-top: 0.2rem dotted rgba(0, 0, 0, 0.1);
}
position: relative;
padding: 1.618rem;
@media (max-width: 40em) {
padding: 1rem;
}
.inner {
margin: auto;
}
.content {
font-family: "Open Sans", sans-serif;
margin-top: 0rem;
max-width: 100%;
* {
color: white;
}
p, ol, ul, li, h1, h2, h3, h4, h5, h6, img, blockquote, &>pre {
max-width: 50rem;
margin-left: auto;
margin-right: auto;
}
img {
display: block;
max-width: 50rem;
@media (max-width: 50rem) {
max-width: 100%;
}
}
ol {
counter-reset: list-item;
li {
counter-increment: list-item;
}
}
em, i {
font-style: italic;
}
code {
&, * {
font-family: "Fira Code", monospace !important;
}
vertical-align: bottom;
}
/* Tables */
table {
margin-top: 1.618rem;
border-spacing: 0px;
border-collapse: collapse;
border: thin solid rgba(255, 255, 255, 0.1);
width: 100%;
max-width: 55rem;
margin-left: auto;
margin-right: auto;
}
td + td, th + th {
border-left: thin solid rgba(255, 255, 255, 0.1);
}
tr {
border-top: thin solid rgba(255, 255, 255, 0.1);
}
table td,
table th {
padding: 0.618rem 1rem;
}
th {
font-weight: 600;
background: rgba(255, 255, 255, 0.1);
}
}
.metadata {
display: flex;
flex-wrap: wrap;
align-items: center;
font-family: "Work Sans", sans-serif;
font-size: 0.8rem;
font-weight: 400;
opacity: 0.6;
max-width: 50rem;
margin-bottom: 0.0rem;
margin-right: -0.5rem;
margin-left: auto;
margin-right: auto;
span + span {
margin-left: 1em;
}
.fa {
font-size: 85%;
}
.fa + span {
margin-left: 0.2em;
}
.mantine-Button-root {
color: #ccc;
font-size: 0.8rem;
font-weight: 400;
.mantine-Button-label {
display: flex;
align-items: center;
}
}
}
.fa {
margin-right: 0.5em;
font-size: 85%;
}
.buttons {
text-align: right;
}
strong {
font-weight: bold;
}
`;
const EndOfChatMarker = styled.div`
position: absolute;
bottom: calc(-1.618rem - 0.5rem);
left: 50%;
width: 0.5rem;
height: 0.5rem;
margin-left: -0.25rem;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
`;
function getRoleName(role: string, share = false) {
switch (role) {
case 'user':
return !share ? 'You' : 'User';
case 'assistant':
return 'ChatGPT';
case 'system':
return 'System';
default:
return role;
}
}
function InlineLoader() {
return (
<Loader variant="dots" size="xs" style={{
marginLeft: '1rem',
position: 'relative',
top: '-0.2rem',
}} />
);
}
export default function MessageComponent(props: { message: Message, last: boolean, share?: boolean }) {
if (props.message.role === 'system') {
return null;
}
return <Container className={"message by-" + props.message.role}>
<div className="inner">
<div className="metadata">
<span>
<strong>
{getRoleName(props.message.role, props.share)}<SROnly>:</SROnly>
</strong>
{props.message.role === 'assistant' && props.last && !props.message.done && <InlineLoader />}
</span>
{props.message.done && <ElevenLabsReaderButton selector={'.content-' + props.message.id} />}
<div style={{ flexGrow: 1 }} />
<CopyButton value={props.message.content}>
{({ copy, copied }) => (
<Button variant="subtle" size="sm" compact onClick={copy} style={{ marginLeft: '1rem' }}>
<i className="fa fa-clipboard" />
<span>{copied ? 'Copied' : 'Copy'}</span>
</Button>
)}
</CopyButton>
{typeof navigator.share !== 'undefined' && (
<Button variant="subtle" size="sm" compact onClick={() => share(props.message.content)}>
<i className="fa fa-share" />
<span>Share</span>
</Button>
)}
</div>
<div className={"prose dark:prose-invert content content-" + props.message.id}>
<ReactMarkdown remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeKatex]}
components={{
code({ node, inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '')
return !inline ? (
<div>
<CopyButton value={String(children)}>
{({ copy, copied }) => (
<Button variant="subtle" size="sm" compact onClick={copy}>
<i className="fa fa-clipboard" />
<span>{copied ? 'Copied' : 'Copy'}</span>
</Button>
)}
</CopyButton>
<SyntaxHighlighter
children={String(children).replace(/\n$/, '')}
style={vscDarkPlus as any}
language={match?.[1] || 'text'}
PreTag="div"
{...props}
/>
</div>
) : (
<code className={className} {...props}>
{children}
</code>
)
}
}}>{props.message.content}</ReactMarkdown>
</div>
</div>
{props.last && <EndOfChatMarker />}
</Container>
}