diff --git a/packages/jupyter-ai/src/components/chat-messages.tsx b/packages/jupyter-ai/src/components/chat-messages.tsx
index ec2e0cf1a..86b6793d9 100644
--- a/packages/jupyter-ai/src/components/chat-messages.tsx
+++ b/packages/jupyter-ai/src/components/chat-messages.tsx
@@ -10,10 +10,12 @@ import { AiService } from '../handler';
import { RendermimeMarkdown } from './rendermime-markdown';
import { useCollaboratorsContext } from '../contexts/collaborators-context';
import { ChatMessageMenu } from './chat-messages/chat-message-menu';
+import { IJaiMessageFooter } from '../tokens';
type ChatMessagesProps = {
rmRegistry: IRenderMimeRegistry;
messages: AiService.ChatMessage[];
+ messageFooter: IJaiMessageFooter | null;
};
type ChatMessageHeaderProps = {
@@ -215,6 +217,9 @@ export function ChatMessages(props: ChatMessagesProps): JSX.Element {
message.type === 'agent-stream' ? !!message.complete : true
}
/>
+ {props.messageFooter && (
+
+ )}
);
})}
diff --git a/packages/jupyter-ai/src/components/chat.tsx b/packages/jupyter-ai/src/components/chat.tsx
index 09edfef5a..0915d6ca6 100644
--- a/packages/jupyter-ai/src/components/chat.tsx
+++ b/packages/jupyter-ai/src/components/chat.tsx
@@ -18,7 +18,7 @@ import { SelectionContextProvider } from '../contexts/selection-context';
import { SelectionWatcher } from '../selection-watcher';
import { ChatHandler } from '../chat_handler';
import { CollaboratorsContextProvider } from '../contexts/collaborators-context';
-import { IJaiCompletionProvider } from '../tokens';
+import { IJaiCompletionProvider, IJaiMessageFooter } from '../tokens';
import {
ActiveCellContextProvider,
ActiveCellManager
@@ -30,6 +30,7 @@ type ChatBodyProps = {
setChatView: (view: ChatView) => void;
rmRegistry: IRenderMimeRegistry;
focusInputSignal: ISignal;
+ messageFooter: IJaiMessageFooter | null;
};
/**
@@ -51,7 +52,8 @@ function ChatBody({
chatHandler,
focusInputSignal,
setChatView: chatViewHandler,
- rmRegistry: renderMimeRegistry
+ rmRegistry: renderMimeRegistry,
+ messageFooter
}: ChatBodyProps): JSX.Element {
const [messages, setMessages] = useState([
...chatHandler.history.messages
@@ -139,7 +141,11 @@ function ChatBody({
return (
<>
-
+
void;
activeCellManager: ActiveCellManager;
focusInputSignal: ISignal;
+ messageFooter: IJaiMessageFooter | null;
};
enum ChatView {
@@ -223,6 +230,7 @@ export function Chat(props: ChatProps): JSX.Element {
setChatView={setView}
rmRegistry={props.rmRegistry}
focusInputSignal={props.focusInputSignal}
+ messageFooter={props.messageFooter}
/>
)}
{view === ChatView.Settings && (
diff --git a/packages/jupyter-ai/src/index.ts b/packages/jupyter-ai/src/index.ts
index cd9d8b322..e42091980 100644
--- a/packages/jupyter-ai/src/index.ts
+++ b/packages/jupyter-ai/src/index.ts
@@ -18,7 +18,7 @@ import { ChatHandler } from './chat_handler';
import { buildErrorWidget } from './widgets/chat-error';
import { completionPlugin } from './completions';
import { statusItemPlugin } from './status';
-import { IJaiCompletionProvider } from './tokens';
+import { IJaiCompletionProvider, IJaiMessageFooter } from './tokens';
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import { ActiveCellManager } from './contexts/active-cell-context';
import { Signal } from '@lumino/signaling';
@@ -42,7 +42,8 @@ const plugin: JupyterFrontEndPlugin = {
IGlobalAwareness,
ILayoutRestorer,
IThemeManager,
- IJaiCompletionProvider
+ IJaiCompletionProvider,
+ IJaiMessageFooter
],
requires: [IRenderMimeRegistry],
activate: async (
@@ -51,7 +52,8 @@ const plugin: JupyterFrontEndPlugin = {
globalAwareness: Awareness | null,
restorer: ILayoutRestorer | null,
themeManager: IThemeManager | null,
- completionProvider: IJaiCompletionProvider | null
+ completionProvider: IJaiCompletionProvider | null,
+ messageFooter: IJaiMessageFooter | null
) => {
/**
* Initialize selection watcher singleton
@@ -88,7 +90,8 @@ const plugin: JupyterFrontEndPlugin = {
completionProvider,
openInlineCompleterSettings,
activeCellManager,
- focusInputSignal
+ focusInputSignal,
+ messageFooter
);
} catch (e) {
chatWidget = buildErrorWidget(themeManager);
diff --git a/packages/jupyter-ai/src/tokens.ts b/packages/jupyter-ai/src/tokens.ts
index f0f301982..efcada10f 100644
--- a/packages/jupyter-ai/src/tokens.ts
+++ b/packages/jupyter-ai/src/tokens.ts
@@ -1,6 +1,8 @@
+import React from 'react';
import { Token } from '@lumino/coreutils';
import { ISignal } from '@lumino/signaling';
import type { IRankedMenu } from '@jupyterlab/ui-components';
+import { AiService } from './handler';
export interface IJaiStatusItem {
addItem(item: IRankedMenu.IItemOptions): void;
@@ -26,3 +28,21 @@ export const IJaiCompletionProvider = new Token(
'jupyter_ai:IJaiCompletionProvider',
'The jupyter-ai inline completion provider API'
);
+
+export type IJaiMessageFooterProps = {
+ message: AiService.ChatMessage;
+};
+
+export interface IJaiMessageFooter {
+ component: React.FC;
+}
+
+/**
+ * The message footer provider token. Another extension should provide this
+ * token to add a footer to each message.
+ */
+
+export const IJaiMessageFooter = new Token(
+ 'jupyter_ai:IJaiMessageFooter',
+ 'Optional component that is used to render a footer on each Jupyter AI chat message, when provided.'
+);
diff --git a/packages/jupyter-ai/src/widgets/chat-sidebar.tsx b/packages/jupyter-ai/src/widgets/chat-sidebar.tsx
index 40cf5945f..e7eee11bd 100644
--- a/packages/jupyter-ai/src/widgets/chat-sidebar.tsx
+++ b/packages/jupyter-ai/src/widgets/chat-sidebar.tsx
@@ -8,7 +8,7 @@ import { Chat } from '../components/chat';
import { chatIcon } from '../icons';
import { SelectionWatcher } from '../selection-watcher';
import { ChatHandler } from '../chat_handler';
-import { IJaiCompletionProvider } from '../tokens';
+import { IJaiCompletionProvider, IJaiMessageFooter } from '../tokens';
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import type { ActiveCellManager } from '../contexts/active-cell-context';
@@ -21,7 +21,8 @@ export function buildChatSidebar(
completionProvider: IJaiCompletionProvider | null,
openInlineCompleterSettings: () => void,
activeCellManager: ActiveCellManager,
- focusInputSignal: ISignal
+ focusInputSignal: ISignal,
+ messageFooter: IJaiMessageFooter | null
): ReactWidget {
const ChatWidget = ReactWidget.create(
);
ChatWidget.id = 'jupyter-ai::chat';