Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/components/chat-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React, { useEffect, useRef } from 'react';
import { ReactWidget } from '@jupyterlab/apputils';
import { buildChatSidebar, IChatModel } from '@jupyter/chat';
import { NewChatButton } from './new-chat-button';
import { jupyternautLiteIcon } from '../icons';

export function buildSidebarWithHeader(
options: Parameters<typeof buildChatSidebar>[0],
newChat: () => void,
model: IChatModel
): ReactWidget {
const sidebar = buildChatSidebar(options);

const ChatSidebarWithHeader = () => {
const containerRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (containerRef.current) {
containerRef.current.innerHTML = '';
containerRef.current.appendChild(sidebar.node);
sidebar.node.style.flex = '1';
sidebar.node.style.height = '100%';
sidebar.update();
}
}, []);

return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<div
style={{
padding: '8px',
borderBottom: '1px solid var(--jp-border-color2)'
}}
>
<NewChatButton newChat={newChat} model={model.input} />
</div>
<div style={{ flex: 1, overflow: 'auto' }} ref={containerRef}></div>
</div>
);
};

const widget = ReactWidget.create(<ChatSidebarWithHeader />);
widget.title.icon = jupyternautLiteIcon;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I use this Icon here because creating chatsidebar with header changes the Jupyterlite icon. So, I use this already created JupyterLite AI chat icon after successfully creating a chatsidebar with a header.

@jtpio and @brichet you have any other way to get back the old icon after rendering chatsidebar? Please share the idea.

return widget;
}
68 changes: 68 additions & 0 deletions src/components/new-chat-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import AddIcon from '@mui/icons-material/Add';
import React from 'react';

import { InputToolbarRegistry, TooltippedButton } from '@jupyter/chat';

/**
* Props for the New Chat button.
*/
export interface INewChatButtonProps
extends InputToolbarRegistry.IToolbarItemProps {
newChat: () => void;
}

/**
* The new chat button component.
*/
export function NewChatButton(props: INewChatButtonProps): JSX.Element {
const tooltip = 'Start a new chat';
return (
<TooltippedButton
onClick={props.newChat}
tooltip={tooltip}
sx={{
color: 'var(--jp-ui-font-color1)',
textTransform: 'none',
fontWeight: 'normal',
padding: '2px 4px',
fontSize: '0.75rem',
'&:hover': {
backgroundColor: 'var(--jp-layout-color2)'
}
}}
buttonProps={{
size: 'small',
variant: 'text',
title: tooltip
}}
>
<span
style={{
display: 'flex',
alignItems: 'center',
gap: '1px',
fontSize: '1rem'
}}
>
<AddIcon fontSize="small" sx={{ color: 'var(--jp-ui-font-color2)' }} />
Chat
</span>
</TooltippedButton>
);
}

/**
* Factory to create the toolbar item for new chat.
*/
export function newChatItem(
newChat: () => void
): InputToolbarRegistry.IToolbarItem {
return {
element: (props: InputToolbarRegistry.IToolbarItemProps) => {
const newProps: INewChatButtonProps = { ...props, newChat };
return NewChatButton(newProps);
},
position: 2000,
hidden: false
};
}
56 changes: 47 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import {
JupyterFrontEndPlugin,
ILayoutRestorer
} from '@jupyterlab/application';
import { ReactWidget, IThemeManager } from '@jupyterlab/apputils';
import {
ReactWidget,
IThemeManager,
MainAreaWidget
} from '@jupyterlab/apputils';
import { ICompletionProviderManager } from '@jupyterlab/completer';
import { INotebookTracker } from '@jupyterlab/notebook';
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
Expand All @@ -28,6 +32,12 @@ import { AIProviderRegistry } from './provider';
import { aiSettingsRenderer, textArea } from './settings';
import { IAIProviderRegistry, PLUGIN_IDS } from './tokens';
import { stopItem } from './components/stop-button';
import { buildSidebarWithHeader } from './components/chat-header';

namespace Private {
// eslint-disable-next-line prefer-const
export let id = 0;
}

const chatCommandRegistryPlugin: JupyterFrontEndPlugin<IChatCommandRegistry> = {
id: PLUGIN_IDS.chatCommandRegistry,
Expand Down Expand Up @@ -127,15 +137,43 @@ const chatPlugin: JupyterFrontEndPlugin<void> = {
}
});

let chatCount = 0;

try {
chatWidget = buildChatSidebar({
model: chatHandler,
themeManager,
rmRegistry,
chatCommandRegistry,
inputToolbarRegistry,
welcomeMessage: welcomeMessage(providerRegistry.providers)
});
chatWidget = buildSidebarWithHeader(
{
model: chatHandler,
themeManager,
rmRegistry,
chatCommandRegistry,
inputToolbarRegistry,
welcomeMessage: welcomeMessage(providerRegistry.providers)
},
() => {
const handler = new ChatHandler({ providerRegistry });
const content = buildChatSidebar({
model: handler,
themeManager,
rmRegistry,
chatCommandRegistry,
inputToolbarRegistry,
welcomeMessage: welcomeMessage(providerRegistry.providers)
});

const label = chatCount === 0 ? 'New Chat' : `New Chat-${chatCount}`;
chatCount++;

content.title.label = label;
content.title.closable = true;

const widget = new MainAreaWidget({ content });
widget.id = `chat-panel-${Private.id++}`;

app.shell.add(widget, 'main');
app.shell.activateById(widget.id);
},
chatHandler
);
} catch (e) {
chatWidget = buildErrorWidget(themeManager);
}
Expand Down