Skip to content

Commit

Permalink
[1.x] Use @jupyterlab/rendermime for in-chat markdown rendering (jupy…
Browse files Browse the repository at this point in the history
…terlab#631)

* Use @jupyterlab/rendermime for in-chat markdown rendering (jupyterlab#564)

* create markdown widget and component

* add rendermime

* add rendermime registry requrement, activation to plugin

* update var naming

* use rendermime directrly in the react component

* use css to emulate code styling

* add @types/react-dom

* create CopyButton component based on chat-code-view.tsx by @dlqqq

* add CopyButton and styling

* remoev unsused code

* add min width to the button

* omit units for 0

* use RendermimeMarkdown for ChatSettings model help

* remove react-markdown dependency

* revert CopyButton styling

* remove comments

* memoize RendermimeMarkdown (10% faster)

* Update packages/jupyter-ai/src/components/copy-button.tsx

Co-authored-by: Jason Weill <[email protected]>

* remove copying status

* update yarn.lock

* detect mimetype, render latex

* set latexTypesetter

* MathJax typeset redered md

* use rmRegistry typesetter

* remove MathJaxTypesetter import

* Modify CHAT_SYSTEM_PROMPT, use $ and $$ style LaTeX delimiters only

* remove @jupyterlab/mathjax-extension dependency

* remove @jupyterlab/builder dependency

* Update packages/jupyter-ai/src/index.ts

Co-authored-by: Michał Krassowski <[email protected]>

* render content after attaching buttons per @krassowski

* remove duplicate rmRegistry per @krassowski

* update snapshots

* Remove unused react-syntax-highlighter, rehype-katex, remark-math dependencies

* move @types/react-dom dependency from root to packages/jupyter-ai per @krassowski

* Update packages/jupyter-ai/package.json

Co-authored-by: Michał Krassowski <[email protected]>

* update yarn.lock

* Escale LaTeX delimeters with regex instead of prompt engineering

* add .jp-ai-rendermime-markdown mjx-container  styling to match jlab

* update escapeLatexDelimiters comment

* update lockfile

* update lockfile

* move escapeLatexDelimiters outside of RendermimeMarkdownBase to avoid redefinitions on every rerender

* adjust escapeLatexDelimiters parameter naming

* Rename escapeLatexDelimiters  argument from str to text

* Update packages/jupyter-ai/src/components/rendermime-markdown.tsx

Co-authored-by: david qiu <[email protected]>

* Update escapeLatexDelimiters docstring per @dlqqq

* bump react-dom version per @dlqqq

* add @jupyterlab/rendermime as a direct dependenct per @dlqqq to avoid potential build errors

---------

Co-authored-by: Jason Weill <[email protected]>
Co-authored-by: Michał Krassowski <[email protected]>
Co-authored-by: david qiu <[email protected]>

* Add "@jupyterlab/rendermime": "^3.6.3" dependency

---------

Co-authored-by: Jason Weill <[email protected]>
Co-authored-by: Michał Krassowski <[email protected]>
Co-authored-by: david qiu <[email protected]>
  • Loading branch information
4 people authored Feb 8, 2024
1 parent bd52241 commit c5b0cf8
Show file tree
Hide file tree
Showing 14 changed files with 2,430 additions and 3,114 deletions.
8 changes: 3 additions & 5 deletions packages/jupyter-ai/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,13 @@
"@jupyterlab/coreutils": "^5.6.3",
"@jupyterlab/fileeditor": "^3.6.3",
"@jupyterlab/notebook": "^3.6.3",
"@jupyterlab/rendermime": "^3.6.3",
"@jupyterlab/services": "^6.6.3",
"@jupyterlab/ui-components": "^3.6.3",
"@mui/icons-material": "^5.11.0",
"@mui/material": "^5.11.0",
"react": "^17.0.0",
"react-dom": "^17.0.0",
"react-markdown": "^8.0.6",
"react-syntax-highlighter": "^15.5.0",
"rehype-katex": "^6.0.2",
"remark-math": "^5.1.1"
"react-dom": "^17.0.0"
},
"devDependencies": {
"@babel/core": "^7.0.0",
Expand All @@ -85,6 +82,7 @@
"@jupyterlab/testutils": "^3.0.0",
"@types/jest": "^26.0.0",
"@types/react-syntax-highlighter": "^15.5.6",
"@types/react-dom": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^4.8.1",
"@typescript-eslint/parser": "^4.8.1",
"eslint": "^7.14.0",
Expand Down
93 changes: 0 additions & 93 deletions packages/jupyter-ai/src/components/chat-code-view.tsx

This file was deleted.

40 changes: 8 additions & 32 deletions packages/jupyter-ai/src/components/chat-messages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@ import React, { useState, useEffect } from 'react';

import { Avatar, Box, Typography } from '@mui/material';
import type { SxProps, Theme } from '@mui/material';
import ReactMarkdown from 'react-markdown';
import remarkMath from 'remark-math';
import rehypeKatex from 'rehype-katex';
import 'katex/dist/katex.min.css';

import { ChatCodeView } from './chat-code-view';
import { AiService } from '../handler';
import { useCollaboratorsContext } from '../contexts/collaborators-context';
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import { Jupyternaut } from '../icons';
import { RendermimeMarkdown } from './rendermime-markdown';
import { useCollaboratorsContext } from '../contexts/collaborators-context';

type ChatMessagesProps = {
rmRegistry: IRenderMimeRegistry;
messages: AiService.ChatMessage[];
};

Expand All @@ -22,11 +20,6 @@ type ChatMessageHeaderProps = {
sx?: SxProps<Theme>;
};

type NewTabLinkProps = {
children: React.ReactNode;
href?: string;
};

export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
const collaborators = useCollaboratorsContext();

Expand Down Expand Up @@ -133,14 +126,6 @@ export function ChatMessages(props: ChatMessagesProps): JSX.Element {
}
}, [props.messages]);

function NewTabLink(props: NewTabLinkProps) {
return (
<a href={props.href ?? '#'} target="_blank" rel="noopener noreferrer">
{props.children}
</a>
);
}

return (
<Box
sx={{
Expand All @@ -157,19 +142,10 @@ export function ChatMessages(props: ChatMessagesProps): JSX.Element {
timestamp={timestamps[message.id]}
sx={{ marginBottom: 3 }}
/>
<ReactMarkdown
// We are using the jp-RenderedHTMLCommon class here to get the default Jupyter
// markdown styling and then overriding any CSS to make it more compact.
className="jp-RenderedHTMLCommon jp-ai-react-markdown"
components={{
a: NewTabLink,
code: ChatCodeView
}}
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeKatex]}
>
{message.body}
</ReactMarkdown>
<RendermimeMarkdown
rmRegistry={props.rmRegistry}
markdownStr={message.body}
/>
</Box>
))}
</Box>
Expand Down
20 changes: 12 additions & 8 deletions packages/jupyter-ai/src/components/chat-settings.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import React, { useEffect, useState, useMemo } from 'react';

import ReactMarkdown from 'react-markdown';

import { Box } from '@mui/system';
import {
Alert,
Expand All @@ -13,22 +11,27 @@ import {
Radio,
RadioGroup,
TextField,
CircularProgress,
Typography
CircularProgress
} from '@mui/material';

import { Select } from './select';
import { AiService } from '../handler';
import { ModelFields } from './settings/model-fields';
import { ServerInfoState, useServerInfo } from './settings/use-server-info';
import { ExistingApiKeys } from './settings/existing-api-keys';
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import { minifyUpdate } from './settings/minify';
import { useStackingAlert } from './mui-extras/stacking-alert';
import { RendermimeMarkdown } from './rendermime-markdown';

type ChatSettingsProps = {
rmRegistry: IRenderMimeRegistry;
};

/**
* Component that returns the settings view in the chat panel.
*/
export function ChatSettings(): JSX.Element {
export function ChatSettings(props: ChatSettingsProps): JSX.Element {
// state fetched on initial render
const server = useServerInfo();

Expand Down Expand Up @@ -287,9 +290,10 @@ export function ChatSettings(): JSX.Element {
/>
)}
{helpMarkdown && (
<Typography className="jp-ai-ChatSettings-model-help">
<ReactMarkdown linkTarget="_blank">{helpMarkdown}</ReactMarkdown>
</Typography>
<RendermimeMarkdown
rmRegistry={props.rmRegistry}
markdownStr={helpMarkdown}
/>
)}
{lmGlobalId && (
<ModelFields
Expand Down
18 changes: 14 additions & 4 deletions packages/jupyter-ai/src/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,19 @@ import {
import { SelectionWatcher } from '../selection-watcher';
import { ChatHandler } from '../chat_handler';
import { CollaboratorsContextProvider } from '../contexts/collaborators-context';
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import { ScrollContainer } from './scroll-container';

type ChatBodyProps = {
chatHandler: ChatHandler;
setChatView: (view: ChatView) => void;
rmRegistry: IRenderMimeRegistry;
};

function ChatBody({
chatHandler,
setChatView: chatViewHandler
setChatView: chatViewHandler,
rmRegistry: renderMimeRegistry
}: ChatBodyProps): JSX.Element {
const [messages, setMessages] = useState<AiService.ChatMessage[]>([]);
const [showWelcomeMessage, setShowWelcomeMessage] = useState<boolean>(false);
Expand Down Expand Up @@ -147,7 +150,7 @@ function ChatBody({
return (
<>
<ScrollContainer sx={{ flexGrow: 1 }}>
<ChatMessages messages={messages} />
<ChatMessages messages={messages} rmRegistry={renderMimeRegistry} />
</ScrollContainer>
<ChatInput
value={input}
Expand Down Expand Up @@ -180,6 +183,7 @@ export type ChatProps = {
chatHandler: ChatHandler;
globalAwareness: Awareness | null;
themeManager: IThemeManager | null;
rmRegistry: IRenderMimeRegistry;
chatView?: ChatView;
};

Expand Down Expand Up @@ -226,9 +230,15 @@ export function Chat(props: ChatProps): JSX.Element {
</Box>
{/* body */}
{view === ChatView.Chat && (
<ChatBody chatHandler={props.chatHandler} setChatView={setView} />
<ChatBody
chatHandler={props.chatHandler}
setChatView={setView}
rmRegistry={props.rmRegistry}
/>
)}
{view === ChatView.Settings && (
<ChatSettings rmRegistry={props.rmRegistry} />
)}
{view === ChatView.Settings && <ChatSettings />}
</Box>
</CollaboratorsContextProvider>
</SelectionContextProvider>
Expand Down
50 changes: 50 additions & 0 deletions packages/jupyter-ai/src/components/copy-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React, { useState, useCallback } from 'react';

import { Box, Button } from '@mui/material';

enum CopyStatus {
None,
Copied
}

const COPYBTN_TEXT_BY_STATUS: Record<CopyStatus, string> = {
[CopyStatus.None]: 'Copy to Clipboard',
[CopyStatus.Copied]: 'Copied!'
};

type CopyButtonProps = {
value: string;
};

export function CopyButton(props: CopyButtonProps): JSX.Element {
const [copyStatus, setCopyStatus] = useState<CopyStatus>(CopyStatus.None);

const copy = useCallback(async () => {
try {
await navigator.clipboard.writeText(props.value);
} catch (err) {
console.error('Failed to copy text: ', err);
setCopyStatus(CopyStatus.None);
return;
}

setCopyStatus(CopyStatus.Copied);
setTimeout(() => setCopyStatus(CopyStatus.None), 1000);
}, [props.value]);

return (
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<Button
onClick={copy}
disabled={copyStatus !== CopyStatus.None}
aria-label="Copy To Clipboard"
sx={{
alignSelf: 'flex-end',
textTransform: 'none'
}}
>
{COPYBTN_TEXT_BY_STATUS[copyStatus]}
</Button>
</Box>
);
}
Loading

0 comments on commit c5b0cf8

Please sign in to comment.