diff --git a/packages/jupyter-ai/src/components/chat-messages.tsx b/packages/jupyter-ai/src/components/chat-messages.tsx index 052d4ffda..0bb2ca0cb 100644 --- a/packages/jupyter-ai/src/components/chat-messages.tsx +++ b/packages/jupyter-ai/src/components/chat-messages.tsx @@ -5,11 +5,13 @@ import type { SxProps, Theme } from '@mui/material'; import 'katex/dist/katex.min.css'; import { AiService } from '../handler'; -import { useCollaboratorsContext } from '../contexts/collaborators-context'; +import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; import { Jupyternaut } from '../icons'; import { MarkdownComponent } from './markdown-component'; +import { useCollaboratorsContext } from '../contexts/collaborators-context'; type ChatMessagesProps = { + rendermime: IRenderMimeRegistry; messages: AiService.ChatMessage[]; }; @@ -19,11 +21,6 @@ type ChatMessageHeaderProps = { sx?: SxProps; }; -type NewTabLinkProps = { - children: React.ReactNode; - href?: string; -}; - export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element { const collaborators = useCollaboratorsContext(); @@ -130,14 +127,6 @@ export function ChatMessages(props: ChatMessagesProps): JSX.Element { } }, [props.messages]); - function NewTabLink(props: NewTabLinkProps) { - return ( - - {props.children} - - ); - } - return ( diff --git a/packages/jupyter-ai/src/components/chat.tsx b/packages/jupyter-ai/src/components/chat.tsx index ded339c70..8960e9615 100644 --- a/packages/jupyter-ai/src/components/chat.tsx +++ b/packages/jupyter-ai/src/components/chat.tsx @@ -17,16 +17,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; + renderMimeRegistry: IRenderMimeRegistry; }; function ChatBody({ chatHandler, - setChatView: chatViewHandler + setChatView: chatViewHandler, + renderMimeRegistry }: ChatBodyProps): JSX.Element { const [messages, setMessages] = useState([]); const [showWelcomeMessage, setShowWelcomeMessage] = useState(false); @@ -146,7 +149,7 @@ function ChatBody({ return ( <> - + {/* body */} {view === ChatView.Chat && ( - + )} {view === ChatView.Settings && } diff --git a/packages/jupyter-ai/src/components/lumino-component.tsx b/packages/jupyter-ai/src/components/lumino-component.tsx index 210507cdb..cb1767675 100644 --- a/packages/jupyter-ai/src/components/lumino-component.tsx +++ b/packages/jupyter-ai/src/components/lumino-component.tsx @@ -1,25 +1,39 @@ import React, { useRef, useEffect } from 'react'; +import { MessageLoop } from '@lumino/messaging'; import { Widget } from '@lumino/widgets'; type LuminoComponentProps = { widget: Widget; }; -export function LuminoComponent( - props: LuminoComponentProps -): React.ReactElement { +export function LuminoComponent({ + widget +}: LuminoComponentProps): React.ReactElement { const ref = useRef(null); useEffect(() => { - if (ref.current) { - Widget.attach(props.widget, ref.current); + if (!ref.current) { + return; + } + + try { + MessageLoop.sendMessage(widget, Widget.Msg.BeforeAttach); + ref.current.appendChild(widget.node); + MessageLoop.sendMessage(widget, Widget.Msg.AfterAttach); + } catch (e) { + console.warn('Exception while attaching Lumino widget:', e); } return () => { - // Detach the widget when the component unmounts - Widget.detach(props.widget); + try { + if (widget.isAttached || widget.node.isConnected) { + Widget.detach(widget); + } + } catch (e) { + console.warn('Exception while detaching Lumino widget:', e); + } }; - }, [props.widget]); + }, [widget]); return
; } diff --git a/packages/jupyter-ai/src/components/markdown-component.tsx b/packages/jupyter-ai/src/components/markdown-component.tsx index 7cc1ef6c2..56d0b4766 100644 --- a/packages/jupyter-ai/src/components/markdown-component.tsx +++ b/packages/jupyter-ai/src/components/markdown-component.tsx @@ -1,5 +1,4 @@ import React, { useRef, useEffect } from 'react'; - import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; import { Widget } from '@lumino/widgets'; import { LuminoComponent } from './lumino-component'; @@ -14,7 +13,7 @@ type MarkdownComponentProps = { rendermime: IRenderMimeRegistry; }; -export class MarkdownWidget extends Widget { +class MarkdownWidget extends Widget { private rendermime: IRenderMimeRegistry; private markdownString: string; @@ -22,10 +21,6 @@ export class MarkdownWidget extends Widget { super(); this.rendermime = props.rendermime; this.markdownString = props.markdownString; - this.onAfterAttach = this.onAfterAttach.bind(this); - } - - onAfterAttach(): void { this.initializeMarkdownRendering(); } @@ -38,35 +33,23 @@ export class MarkdownWidget extends Widget { await renderer.renderModel(model); this.node.appendChild(renderer.node); } - - async updateMarkdown(markdownString: string): Promise { - this.markdownString = markdownString; - // Clear the existing content - while (this.node.firstChild) { - this.node.removeChild(this.node.firstChild); - } - // Reinitialize rendering with the new markdown string - await this.initializeMarkdownRendering(); - } } -export function MarkdownComponent({ - markdownString, - rendermime -}: MarkdownComponentProps): React.ReactElement | null { +export function MarkdownComponent( + props: MarkdownComponentProps +): React.ReactElement | null { + const { markdownString, rendermime } = props; const widgetRef = useRef(null); useEffect(() => { if (!widgetRef.current) { widgetRef.current = new MarkdownWidget({ rendermime, markdownString }); - } else { - widgetRef.current.updateMarkdown(markdownString); } return () => { widgetRef.current?.dispose(); }; - }, [markdownString, rendermime]); + }, []); // Empty dependency array if props are not expected to change return widgetRef.current ? ( diff --git a/packages/jupyter-ai/src/index.ts b/packages/jupyter-ai/src/index.ts index e48e2b211..5c55c5e25 100644 --- a/packages/jupyter-ai/src/index.ts +++ b/packages/jupyter-ai/src/index.ts @@ -12,6 +12,7 @@ import { buildChatSidebar } from './widgets/chat-sidebar'; import { SelectionWatcher } from './selection-watcher'; import { ChatHandler } from './chat_handler'; import { buildErrorWidget } from './widgets/chat-error'; +import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; export type DocumentTracker = IWidgetTracker; @@ -25,7 +26,8 @@ const plugin: JupyterFrontEndPlugin = { activate: async ( app: JupyterFrontEnd, globalAwareness: Awareness | null, - restorer: ILayoutRestorer | null + restorer: ILayoutRestorer | null, + renderMimeRegistry: IRenderMimeRegistry ) => { /** * Initialize selection watcher singleton @@ -43,7 +45,8 @@ const plugin: JupyterFrontEndPlugin = { chatWidget = buildChatSidebar( selectionWatcher, chatHandler, - globalAwareness + globalAwareness, + renderMimeRegistry ); } catch (e) { chatWidget = buildErrorWidget(); diff --git a/packages/jupyter-ai/src/widgets/chat-sidebar.tsx b/packages/jupyter-ai/src/widgets/chat-sidebar.tsx index 8bc7df12c..6a2452cfa 100644 --- a/packages/jupyter-ai/src/widgets/chat-sidebar.tsx +++ b/packages/jupyter-ai/src/widgets/chat-sidebar.tsx @@ -6,17 +6,20 @@ import { Chat } from '../components/chat'; import { chatIcon } from '../icons'; import { SelectionWatcher } from '../selection-watcher'; import { ChatHandler } from '../chat_handler'; +import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; export function buildChatSidebar( selectionWatcher: SelectionWatcher, chatHandler: ChatHandler, - globalAwareness: Awareness | null + globalAwareness: Awareness | null, + renderMimeRegistry: IRenderMimeRegistry ): ReactWidget { const ChatWidget = ReactWidget.create( ); ChatWidget.id = 'jupyter-ai::chat';