Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Markdown rendering #1544

Merged
merged 15 commits into from
Feb 15, 2023
Merged
2,639 changes: 2,579 additions & 60 deletions website/package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@next-auth/prisma-adapter": "^1.0.5",
"@next/bundle-analyzer": "^13.1.6",
"@next/font": "^13.1.0",
"@nikolovlazar/chakra-ui-prose": "^1.2.1",
"@prisma/client": "^4.7.1",
"@tailwindcss/forms": "^0.5.3",
"@tanstack/react-table": "^8.7.6",
Expand Down Expand Up @@ -66,6 +67,9 @@
"react-feature-flags": "^1.0.0",
"react-hook-form": "^7.42.1",
"react-i18next": "^12.1.4",
"react-markdown": "^8.0.5",
"react-syntax-highlighter": "^15.5.0",
"remark-gfm": "^3.0.1",
"sharp": "^0.31.3",
"storybook-addon-next-router": "^4.0.2",
"swr": "^2.0.0",
Expand All @@ -90,6 +94,7 @@
"@types/accept-language-parser": "^1.5.3",
"@types/node": "^18.11.17",
"@types/react": "18.0.26",
"@types/react-syntax-highlighter": "^15.5.6",
"@typescript-eslint/eslint-plugin": "^5.47.1",
"babel-loader": "^8.3.0",
"cross-env": "^7.0.3",
Expand Down
34 changes: 12 additions & 22 deletions website/src/components/Messages/MessageTableEntry.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
Avatar,
AvatarProps,
Badge,
Box,
Flex,
Expand Down Expand Up @@ -34,7 +33,7 @@ import {
import NextLink from "next/link";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import { useCallback, useEffect, useMemo, useState } from "react";
import { lazy, Suspense, useCallback, useEffect, useMemo, useState } from "react";
import { LabelMessagePopup } from "src/components/Messages/LabelPopup";
import { MessageEmojiButton } from "src/components/Messages/MessageEmojiButton";
import { ReportPopup } from "src/components/Messages/ReportPopup";
Expand All @@ -46,23 +45,17 @@ import { Message, MessageEmojis } from "src/types/Conversation";
import { emojiIcons, isKnownEmoji } from "src/types/Emoji";
import { mutate } from "swr";
import useSWRMutation from "swr/mutation";

const RenderedMarkdown = lazy(() => import("./RenderedMarkdown"));

interface MessageTableEntryProps {
message: Message;
enabled?: boolean;
highlight?: boolean;
avartarPosition?: "middle" | "top";
avartarProps?: AvatarProps;
showAuthorBadge?: boolean;
}

export function MessageTableEntry({
message,
enabled,
highlight,
avartarPosition = "middle",
avartarProps,
showAuthorBadge,
}: MessageTableEntryProps) {
export function MessageTableEntry({ message, enabled, highlight, showAuthorBadge }: MessageTableEntryProps) {
const router = useRouter();
const [emojiState, setEmojis] = useState<MessageEmojis>({ emojis: {}, user_emojis: [] });
useEffect(() => {
Expand Down Expand Up @@ -94,12 +87,12 @@ export function MessageTableEntry({
borderColor={borderColor}
size={inlineAvatar ? "xs" : "sm"}
mr={inlineAvatar ? 2 : 0}
mt={inlineAvatar ? 0 : `6px`}
name={`${boolean(message.is_assistant) ? "Assistant" : "User"}`}
src={`${boolean(message.is_assistant) ? "/images/logos/logo.png" : "/images/temp-avatars/av1.jpg"}`}
{...avartarProps}
/>
),
[avartarProps, borderColor, inlineAvatar, message.is_assistant]
[borderColor, inlineAvatar, message.is_assistant]
);
const highlightColor = useColorModeValue(colors.light.active, colors.dark.active);

Expand All @@ -118,11 +111,7 @@ export function MessageTableEntry({
const { t } = useTranslation(["message"]);

return (
<HStack
w={["full", "full", "full", "fit-content"]}
gap={0.5}
alignItems={avartarPosition === "top" ? "start" : "center"}
>
<HStack w={["full", "full", "full", "fit-content"]} gap={0.5} alignItems="start">
{!inlineAvatar && avatar}
<Box
width={["full", "full", "full", "fit-content"]}
Expand All @@ -133,12 +122,13 @@ export function MessageTableEntry({
outline={highlight ? "2px solid black" : undefined}
outlineColor={highlightColor}
onClick={goToMessage}
whiteSpace="pre-wrap"
cursor={enabled ? "pointer" : undefined}
style={{ position: "relative", wordBreak: "break-word" }}
style={{ position: "relative" }}
>
{inlineAvatar && avatar}
{message.text}
<Suspense fallback={message.text}>
<RenderedMarkdown markdown={message.text}></RenderedMarkdown>
</Suspense>
<HStack
style={{ float: "right", position: "relative", right: "-0.3em", bottom: "-0em", marginLeft: "1em" }}
onClick={(e) => e.stopPropagation()}
Expand Down
61 changes: 28 additions & 33 deletions website/src/components/Messages/MessageTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ export const MessageTree = memo(({ tree, messageId }: { tree: MessageWithChildre
{hasChildren && depth < maxDepth && <Connection className="connection1"></Connection>}
<MessageTableEntry
showAuthorBadge
avartarProps={{
mt: { base: 0, md: `${avartarMarginTop}px` },
}}
avartarPosition="top"
highlight={child.id === messageId}
message={child}
></MessageTableEntry>
Expand All @@ -47,35 +43,34 @@ export const MessageTree = memo(({ tree, messageId }: { tree: MessageWithChildre
return (
<>
<Box position="relative" className="root">
<Box
className="root-connection"
top={{ base: toPx(0), md: 0 }}
height={{ base: `calc(100% - ${toPx(8 + avatarSize / 2)})`, md: `100%` }}
position="absolute"
width="2px"
bg={connectionColor}
left={toPx(avatarSize / 2 - 1)}
></Box>
<Box
display={{ base: "block", md: "none" }}
position="absolute"
height={`calc(100% - ${toPx(6 + avatarSize / 2)})`}
width={`10px`}
top={toPx(6 + avatarSize / 2)}
borderTopWidth="2px"
borderTopLeftRadius="10px"
left="-8px"
borderTopStyle="solid"
borderLeftWidth="2px"
borderColor={connectionColor}
className="root-curve"
></Box>
<MessageTableEntry
showAuthorBadge
message={tree}
avartarPosition="top"
highlight={tree.id === messageId}
></MessageTableEntry>
{tree.children.length > 0 && (
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fix issue where the tree still display connection when it only have root (no children).

<>
<Box
className="root-connection"
top={{ base: toPx(0), md: toPx(8) }}
height={{ base: `calc(100% - ${toPx(8 + avatarSize / 2)})`, md: `calc(100% - 8px)` }}
position="absolute"
width="2px"
bg={connectionColor}
left={toPx(avatarSize / 2 - 1)}
></Box>
<Box
display={{ base: "block", md: "none" }}
position="absolute"
height={`calc(100% - ${toPx(6 + avatarSize / 2)})`}
width={`10px`}
top={toPx(6 + avatarSize / 2)}
borderTopWidth="2px"
borderTopLeftRadius="10px"
left="-8px"
borderTopStyle="solid"
borderLeftWidth="2px"
borderColor={connectionColor}
className="root-curve"
></Box>
</>
)}
<MessageTableEntry showAuthorBadge message={tree} highlight={tree.id === messageId}></MessageTableEntry>
</Box>
{renderChildren(tree.children)}
</>
Expand Down
87 changes: 87 additions & 0 deletions website/src/components/Messages/RenderedMarkdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { SystemStyleObject } from "@chakra-ui/react";
import { Prose } from "@nikolovlazar/chakra-ui-prose";
import { memo } from "react";
import { ReactMarkdown } from "react-markdown/lib/react-markdown";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
import remarkGfm from "remark-gfm";
interface RenderedMarkdownProps {
markdown: string;
}

const components = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
code({ node, inline, className, children, style, ...props }) {
const match = /language-(\w+)/.exec(className || "");
const lang = match ? match[1] : "";
return !inline ? (
<SyntaxHighlighter style={oneDark} language={lang} {...props}>
{String(children).replace(/\n$/, "")}
</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
};

const sx: SystemStyleObject = {
pre: {
bg: "transparent",
},
code: {
before: {
content: `""`, // charka prose come with "`" by default
},
bg: "gray.300",
p: 0.5,
borderRadius: "2px",
_dark: {
bg: "gray.700",
},
},
"p:only-child": {
my: 0, // ovoid margin when markdown only render 1 p tag
},
p: {
whiteSpace: "pre-wrap",
display: "inline-block",
},
display: "inline-block",
wordBreak: "break-word",
"> blockquote": {
borderInlineStartColor: "gray.300",
_dark: {
borderInlineStartColor: "gray.500",
},
},
"table tbody tr": {
borderBottomColor: "gray.400",
_dark: {
borderBottomColor: "gray.700",
},
},
"table thead tr": {
borderBottomColor: "gray.400",
borderBottomWidth: "1px",
_dark: {
borderBottomColor: "gray.700",
},
},
};

const plugins = [remarkGfm];

// eslint-disable-next-line react/display-name
const RenderedMarkdown = memo(({ markdown }: RenderedMarkdownProps) => {
return (
<Prose as="div" sx={sx}>
<ReactMarkdown remarkPlugins={plugins} components={components}>
{markdown}
</ReactMarkdown>
</Prose>
);
});

export default RenderedMarkdown;
10 changes: 8 additions & 2 deletions website/src/components/Sortable/Sortable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ import {
sortableKeyboardCoordinates,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { ReactNode, useEffect, useState } from "react";
import { lazy, ReactNode, Suspense, useEffect, useState } from "react";

import { CollapsableText } from "../CollapsableText";
import { SortableItem } from "./SortableItem";

const RenderedMarkdown = lazy(() => import("../Messages/RenderedMarkdown"));

export interface SortableProps {
items: ReactNode[];
onChange?: (newSortedIndices: number[]) => void;
Expand Down Expand Up @@ -110,7 +112,11 @@ export const Sortable = (props: SortableProps) => {
<ModalContent pb={5} alignItems="center">
<ModalHeader>Full Text</ModalHeader>
<ModalCloseButton />
<ModalBody whiteSpace="pre-line">{modalText}</ModalBody>
<ModalBody>
<Suspense fallback={modalText}>
<RenderedMarkdown markdown={modalText}></RenderedMarkdown>
</Suspense>
</ModalBody>
</ModalContent>
</ModalOverlay>
</Modal>
Expand Down
2 changes: 1 addition & 1 deletion website/src/styles/Chakra.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function Chakra({ cookies, children }) {
const colorModeManager = typeof cookies === "string" ? cookieStorageManagerSSR(cookies) : localStorageManager;

return (
<ChakraProvider theme={theme} colorModeManager={colorModeManager}>
<ChakraProvider theme={theme} colorModeManager={colorModeManager} resetCSS>
{children}
</ChakraProvider>
);
Expand Down
38 changes: 37 additions & 1 deletion website/src/styles/Theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type ThemeConfig, extendTheme } from "@chakra-ui/react";
import { Styles } from "@chakra-ui/theme-tools";
import { withProse } from "@nikolovlazar/chakra-ui-prose";

import { colors } from "./colors";
import { badgeTheme } from "./components/Badge";
Expand Down Expand Up @@ -50,4 +51,39 @@ const styles: Styles = {
}),
};

export const theme = extendTheme({ colors, config, fonts, styles, components, breakpoints });
export const theme = extendTheme(
{ colors, config, fonts, styles, components, breakpoints },
withProse({
baseStyle: {
"h1, h2, h3, h4, h5, h6": {
fontWeight: "500",
lineHeight: 1.2,
},
"h1, h2, h3": {
mt: 5,
mb: 2.5,
},
"h4, h5, h6": {
my: 2.5,
},
h1: {
fontSize: "2.5rem",
},
h2: {
fontSize: "2rem",
},
h3: {
fontSize: "1.75rem",
},
h4: {
fontSize: "1.5rem",
},
h5: {
fontSize: "1.25rem",
},
h6: {
fontSize: "1rem",
},
},
})
);
4 changes: 2 additions & 2 deletions website/src/styles/globals.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@tailwind base;
/* @tailwind base; */
@tailwind components;
@tailwind utilities;

Expand All @@ -20,7 +20,7 @@
src: url("/fonts/Inter-italic.var.woff2") format("woff2");
}

@tailwind base;
/* @tailwind base; */
@tailwind components;
@tailwind utilities;

Expand Down