@jupyter/chat 0.19.0 → 0.20.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,7 +40,6 @@ export function ChatBody(props) {
40
40
  (_a = model === null || model === void 0 ? void 0 : model.writersChanged) === null || _a === void 0 ? void 0 : _a.disconnect(updateWriters);
41
41
  };
42
42
  }, [model]);
43
- // const horizontalPadding = props.area === 'main' ? 8 : 4;
44
43
  const horizontalPadding = 4;
45
44
  const contextValue = {
46
45
  ...props,
@@ -17,6 +17,9 @@ export function MessageFooterComponent(props) {
17
17
  return null;
18
18
  }
19
19
  const footer = messageFooterRegistry.getFooter();
20
+ if (!footer.left && !footer.center && !footer.right) {
21
+ return null;
22
+ }
20
23
  return (React.createElement(Box, { sx: { display: 'flex', justifyContent: 'space-between' } },
21
24
  ((_a = footer.left) === null || _a === void 0 ? void 0 : _a.component) ? (React.createElement(footer.left.component, { message: message, model: model })) : (React.createElement("div", null)),
22
25
  ((_b = footer.center) === null || _b === void 0 ? void 0 : _b.component) ? (React.createElement(footer.center.component, { message: message, model: model })) : (React.createElement("div", null)),
@@ -1,21 +1,18 @@
1
1
  import { PromiseDelegate } from '@lumino/coreutils';
2
2
  import React from 'react';
3
+ import { IMessageContent } from '../../types';
3
4
  /**
4
5
  * The type of the props for the MessageRenderer component.
5
6
  */
6
7
  type MessageRendererProps = {
7
8
  /**
8
- * The string to render.
9
+ * The string or rendermime bundle to render.
9
10
  */
10
- markdownStr: string;
11
+ message: IMessageContent;
11
12
  /**
12
13
  * The promise to resolve when the message is rendered.
13
14
  */
14
15
  rendered: PromiseDelegate<void>;
15
- /**
16
- * Whether to append the content to the existing content or not.
17
- */
18
- appendContent?: boolean;
19
16
  /**
20
17
  * The function to call to edit a message.
21
18
  */
@@ -2,51 +2,116 @@
2
2
  * Copyright (c) Jupyter Development Team.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
+ import { MessageLoop } from '@lumino/messaging';
6
+ import { Widget } from '@lumino/widgets';
5
7
  import React, { useState, useEffect } from 'react';
6
8
  import { createPortal } from 'react-dom';
7
9
  import { MessageToolbar } from './toolbar';
8
10
  import { CodeToolbar } from '../code-blocks/code-toolbar';
9
11
  import { useChatContext } from '../../context';
10
- import { MarkdownRenderer, MD_RENDERED_CLASS } from '../../markdown-renderer';
12
+ import { replaceMentionToSpan } from '../../utils';
13
+ const RENDERED_CLASS = 'jp-chat-rendered-message';
14
+ const DEFAULT_MIME_TYPE = 'text/markdown';
11
15
  /**
12
16
  * The message renderer base component.
13
17
  */
14
18
  function MessageRendererBase(props) {
15
- const { markdownStr } = props;
19
+ const { message } = props;
16
20
  const { model, rmRegistry } = useChatContext();
17
- const appendContent = props.appendContent || false;
21
+ // The rendered content, return by the mime renderer.
18
22
  const [renderedContent, setRenderedContent] = useState(null);
19
- // each element is a two-tuple with the structure [codeToolbarRoot, codeToolbarProps].
23
+ // Allow edition only on text messages.
24
+ const [canEdit, setCanEdit] = useState(false);
25
+ // Each element is a two-tuple with the structure [codeToolbarRoot, codeToolbarProps].
20
26
  const [codeToolbarDefns, setCodeToolbarDefns] = useState([]);
21
27
  useEffect(() => {
22
28
  const renderContent = async () => {
23
- const renderer = await MarkdownRenderer.renderContent({
24
- content: markdownStr,
25
- rmRegistry
26
- });
27
- const newCodeToolbarDefns = [];
28
- // Attach CodeToolbar root element to each <pre> block
29
- const preBlocks = renderer.node.querySelectorAll('pre');
30
- preBlocks.forEach(preBlock => {
31
- var _a;
32
- const codeToolbarRoot = document.createElement('div');
33
- (_a = preBlock.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(codeToolbarRoot, preBlock.nextSibling);
34
- newCodeToolbarDefns.push([
35
- codeToolbarRoot,
36
- { model: model, content: preBlock.textContent || '' }
37
- ]);
38
- });
39
- setCodeToolbarDefns(newCodeToolbarDefns);
29
+ var _a, _b;
30
+ let isMarkdownRenderer = true;
31
+ let renderer;
32
+ let mimeModel;
33
+ // Create the renderer and the mime model.
34
+ if (typeof message.body === 'string') {
35
+ // Allow editing content for text messages.
36
+ setCanEdit(true);
37
+ // Improve users display in markdown content.
38
+ let mdStr = message.body;
39
+ (_a = message.mentions) === null || _a === void 0 ? void 0 : _a.forEach(user => {
40
+ mdStr = replaceMentionToSpan(mdStr, user);
41
+ });
42
+ // Body is a string, use the markdown renderer.
43
+ renderer = rmRegistry.createRenderer(DEFAULT_MIME_TYPE);
44
+ mimeModel = rmRegistry.createModel({
45
+ data: { [DEFAULT_MIME_TYPE]: mdStr }
46
+ });
47
+ }
48
+ else {
49
+ setCanEdit(false);
50
+ // This is a mime bundle.
51
+ let mimeContent = message.body;
52
+ let preferred = rmRegistry.preferredMimeType(mimeContent.data, 'ensure' // Should be changed with 'prefer' if we can handle trusted content.
53
+ );
54
+ if (!preferred) {
55
+ preferred = DEFAULT_MIME_TYPE;
56
+ mimeContent = {
57
+ data: {
58
+ [DEFAULT_MIME_TYPE]: `_No renderer found for [**${Object.keys(mimeContent.data).join(', ')}**] mimetype(s)_`
59
+ }
60
+ };
61
+ }
62
+ renderer = rmRegistry.createRenderer(preferred);
63
+ // Improve users display in markdown content.
64
+ if (preferred === DEFAULT_MIME_TYPE) {
65
+ let mdStr = mimeContent.data[DEFAULT_MIME_TYPE];
66
+ if (mdStr) {
67
+ (_b = message.mentions) === null || _b === void 0 ? void 0 : _b.forEach(user => {
68
+ mdStr = replaceMentionToSpan(mdStr, user);
69
+ });
70
+ mimeContent = {
71
+ ...mimeContent,
72
+ data: {
73
+ ...mimeContent.data,
74
+ [DEFAULT_MIME_TYPE]: mdStr
75
+ }
76
+ };
77
+ }
78
+ }
79
+ else {
80
+ isMarkdownRenderer = false;
81
+ }
82
+ mimeModel = rmRegistry.createModel(mimeContent);
83
+ }
84
+ await renderer.renderModel(mimeModel);
85
+ // Manually trigger the onAfterAttach of the renderer, because the widget will
86
+ // never been attached, only the node.
87
+ // This is necessary to render latex.
88
+ MessageLoop.sendMessage(renderer, Widget.Msg.AfterAttach);
89
+ // Add code toolbar if markdown has been rendered.
90
+ if (isMarkdownRenderer) {
91
+ const newCodeToolbarDefns = [];
92
+ // Attach CodeToolbar root element to each <pre> block
93
+ const preBlocks = renderer.node.querySelectorAll('pre');
94
+ preBlocks.forEach(preBlock => {
95
+ var _a;
96
+ const codeToolbarRoot = document.createElement('div');
97
+ (_a = preBlock.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(codeToolbarRoot, preBlock.nextSibling);
98
+ newCodeToolbarDefns.push([
99
+ codeToolbarRoot,
100
+ { model: model, content: preBlock.textContent || '' }
101
+ ]);
102
+ });
103
+ setCodeToolbarDefns(newCodeToolbarDefns);
104
+ }
105
+ // Update the content.
40
106
  setRenderedContent(renderer.node);
41
107
  // Resolve the rendered promise.
42
108
  props.rendered.resolve();
43
109
  };
44
110
  renderContent();
45
- }, [markdownStr, rmRegistry]);
46
- return (React.createElement("div", { className: MD_RENDERED_CLASS },
47
- renderedContent &&
48
- (appendContent ? (React.createElement("div", { ref: node => node && node.appendChild(renderedContent) })) : (React.createElement("div", { ref: node => node && node.replaceChildren(renderedContent) }))),
49
- React.createElement(MessageToolbar, { edit: props.edit, delete: props.delete }),
111
+ }, [message.body, message.mentions, rmRegistry]);
112
+ return (React.createElement(React.Fragment, null,
113
+ renderedContent && (React.createElement("div", { className: RENDERED_CLASS, ref: node => node && node.replaceChildren(renderedContent) })),
114
+ React.createElement(MessageToolbar, { edit: canEdit ? props.edit : undefined, delete: props.delete }),
50
115
  // Render a `CodeToolbar` element underneath each code block.
51
116
  // We use ReactDOM.createPortal() so each `CodeToolbar` element is able
52
117
  // to use the context in the main React tree.
@@ -9,6 +9,7 @@ import { ChatInput } from '../input';
9
9
  import { useChatContext } from '../../context';
10
10
  import { InputModel } from '../../input-model';
11
11
  import { replaceSpanToMention } from '../../utils';
12
+ const MESSAGE_CONTAINER_CLASS = 'jp-chat-message-container';
12
13
  /**
13
14
  * The message component body.
14
15
  */
@@ -56,8 +57,8 @@ export const ChatMessage = forwardRef((props, ref) => {
56
57
  }, [props.message]);
57
58
  // Create an input model only if the message is edited.
58
59
  const startEdition = () => {
59
- var _a;
60
- if (!canEdit) {
60
+ var _a, _b;
61
+ if (!canEdit || !(typeof message.body === 'string')) {
61
62
  return;
62
63
  }
63
64
  let body = message.body;
@@ -75,7 +76,7 @@ export const ChatMessage = forwardRef((props, ref) => {
75
76
  config: {
76
77
  sendWithShiftEnter: model.config.sendWithShiftEnter
77
78
  },
78
- attachments: message.attachments,
79
+ attachments: structuredClone((_b = message.attachments) !== null && _b !== void 0 ? _b : []),
79
80
  mentions: message.mentions
80
81
  });
81
82
  model.addEditionModel(message.id, inputModel);
@@ -110,8 +111,8 @@ export const ChatMessage = forwardRef((props, ref) => {
110
111
  model.deleteMessage(id);
111
112
  };
112
113
  // Empty if the message has been deleted.
113
- return deleted ? (React.createElement("div", { ref: ref, "data-index": props.index })) : (React.createElement("div", { ref: ref, "data-index": props.index },
114
- edit && canEdit && model.getEditionModel(message.id) ? (React.createElement(ChatInput, { onCancel: () => cancelEdition(), model: model.getEditionModel(message.id), edit: true })) : (React.createElement(MessageRenderer, { markdownStr: message.body, edit: canEdit ? startEdition : undefined, delete: canDelete ? () => deleteMessage(message.id) : undefined, rendered: props.renderedPromise })),
114
+ return deleted ? (React.createElement("div", { ref: ref, "data-index": props.index })) : (React.createElement("div", { ref: ref, "data-index": props.index, className: MESSAGE_CONTAINER_CLASS },
115
+ edit && canEdit && model.getEditionModel(message.id) ? (React.createElement(ChatInput, { onCancel: () => cancelEdition(), model: model.getEditionModel(message.id), edit: true })) : (React.createElement(MessageRenderer, { message: message, edit: canEdit ? startEdition : undefined, delete: canDelete ? () => deleteMessage(message.id) : undefined, rendered: props.renderedPromise })),
115
116
  message.attachments && !edit && (
116
117
  // Display the attachments only if message is not edited, otherwise the
117
118
  // input component display them.
@@ -14,7 +14,7 @@ const TOOLBAR_CLASS = 'jp-chat-toolbar';
14
14
  export function MessageToolbar(props) {
15
15
  const buttons = [];
16
16
  if (props.edit !== undefined) {
17
- const editButton = (React.createElement(TooltippedIconButton, { tooltip: 'edit', onClick: props.edit, "aria-label": 'Edit', inputToolbar: false },
17
+ const editButton = (React.createElement(TooltippedIconButton, { tooltip: 'Edit', onClick: props.edit, "aria-label": 'Edit', inputToolbar: false },
18
18
  React.createElement(EditIcon, null)));
19
19
  buttons.push(editButton);
20
20
  }
@@ -2,11 +2,12 @@
2
2
  * Copyright (c) Jupyter Development Team.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
- import { classes } from '@jupyterlab/ui-components';
5
+ import { MessageLoop } from '@lumino/messaging';
6
+ import { Widget } from '@lumino/widgets';
6
7
  import React, { useEffect, useRef } from 'react';
7
8
  import { useChatContext } from '../../context';
8
- import { MarkdownRenderer, MD_RENDERED_CLASS } from '../../markdown-renderer';
9
9
  const WELCOME_MESSAGE_CLASS = 'jp-chat-welcome-message';
10
+ const MD_MIME_TYPE = 'text/markdown';
10
11
  /**
11
12
  * The welcome message component.
12
13
  * This message is displayed on top of the chat messages, and is rendered using a
@@ -17,26 +18,35 @@ export function WelcomeMessage(props) {
17
18
  const content = props.content + '\n----\n';
18
19
  // ref that tracks the content container to store the rendermime node in
19
20
  const renderingContainer = useRef(null);
20
- // ref that tracks whether the rendermime node has already been inserted
21
- const renderingInserted = useRef(false);
22
21
  /**
23
22
  * Effect: use Rendermime to render `props.markdownStr` into an HTML element,
24
23
  * and insert it into `renderingContainer` if not yet inserted.
25
24
  */
26
25
  useEffect(() => {
26
+ let node = null;
27
27
  const renderContent = async () => {
28
- const renderer = await MarkdownRenderer.renderContent({
29
- content,
30
- rmRegistry
28
+ var _a;
29
+ // Render the welcome message using markdown renderer.
30
+ const renderer = rmRegistry.createRenderer(MD_MIME_TYPE);
31
+ const mimeModel = rmRegistry.createModel({
32
+ data: { [MD_MIME_TYPE]: content }
31
33
  });
32
- // insert the rendering into renderingContainer if not yet inserted
33
- if (renderingContainer.current !== null && !renderingInserted.current) {
34
- renderingContainer.current.appendChild(renderer.node);
35
- renderingInserted.current = true;
36
- }
34
+ await renderer.renderModel(mimeModel);
35
+ // Manually trigger the onAfterAttach of the renderer, because the widget will
36
+ // never been attached, only the node.
37
+ // This is necessary to render latex.
38
+ MessageLoop.sendMessage(renderer, Widget.Msg.AfterAttach);
39
+ node = renderer.node;
40
+ (_a = renderingContainer.current) === null || _a === void 0 ? void 0 : _a.append(node);
37
41
  };
38
42
  renderContent();
43
+ return () => {
44
+ var _a;
45
+ if (node && ((_a = renderingContainer.current) === null || _a === void 0 ? void 0 : _a.contains(node))) {
46
+ renderingContainer.current.removeChild(node);
47
+ }
48
+ node = null;
49
+ };
39
50
  }, [content]);
40
- return (React.createElement("div", { className: classes(MD_RENDERED_CLASS, WELCOME_MESSAGE_CLASS) },
41
- React.createElement("div", { ref: renderingContainer })));
51
+ return React.createElement("div", { className: WELCOME_MESSAGE_CLASS, ref: renderingContainer });
42
52
  }
package/lib/index.d.ts CHANGED
@@ -2,7 +2,6 @@ export * from './active-cell-manager';
2
2
  export * from './components';
3
3
  export * from './icons';
4
4
  export * from './input-model';
5
- export * from './markdown-renderer';
6
5
  export * from './model';
7
6
  export * from './registers';
8
7
  export * from './selection-watcher';
package/lib/index.js CHANGED
@@ -6,7 +6,6 @@ export * from './active-cell-manager';
6
6
  export * from './components';
7
7
  export * from './icons';
8
8
  export * from './input-model';
9
- export * from './markdown-renderer';
10
9
  export * from './model';
11
10
  export * from './registers';
12
11
  export * from './selection-watcher';
package/lib/message.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { IRenderMime } from '@jupyterlab/rendermime';
1
2
  import { ISignal } from '@lumino/signaling';
2
3
  import { IAttachment, IMessageContent, IMessage, IUser } from './types';
3
4
  /**
@@ -18,7 +19,7 @@ export declare class Message implements IMessage {
18
19
  * Getters for each attribute individually.
19
20
  */
20
21
  get type(): string;
21
- get body(): string;
22
+ get body(): string | (Partial<IRenderMime.IMimeModel> & Pick<IRenderMime.IMimeModel, 'data'>);
22
23
  get id(): string;
23
24
  get time(): number;
24
25
  get sender(): IUser;
package/lib/model.d.ts CHANGED
@@ -341,6 +341,7 @@ export declare abstract class AbstractChatModel implements IChatModel {
341
341
  private _notify;
342
342
  private _messages;
343
343
  private _unreadMessages;
344
+ private _lastRead;
344
345
  private _messagesInViewport;
345
346
  private _id;
346
347
  private _name;
package/lib/model.js CHANGED
@@ -7,7 +7,6 @@ import { PromiseDelegate } from '@lumino/coreutils';
7
7
  import { Signal } from '@lumino/signaling';
8
8
  import { InputModel } from './input-model';
9
9
  import { Message } from './message';
10
- import { replaceMentionToSpan } from './utils';
11
10
  /**
12
11
  * An abstract implementation of IChatModel.
13
12
  *
@@ -22,6 +21,7 @@ export class AbstractChatModel {
22
21
  var _a, _b, _c, _d;
23
22
  this._messages = [];
24
23
  this._unreadMessages = [];
24
+ this._lastRead = 0;
25
25
  this._messagesInViewport = [];
26
26
  this._name = '';
27
27
  this._readyDelegate = new PromiseDelegate();
@@ -72,6 +72,12 @@ export class AbstractChatModel {
72
72
  }
73
73
  set id(value) {
74
74
  this._id = value;
75
+ // Update the last read message.
76
+ const storage = JSON.parse(localStorage.getItem(`@jupyter/chat:${this._id}`) || '{}');
77
+ if (typeof storage.lastRead === 'number' &&
78
+ storage.lastRead > this._lastRead) {
79
+ this._lastRead = storage.lastRead;
80
+ }
75
81
  }
76
82
  /**
77
83
  * The chat model name.
@@ -125,17 +131,14 @@ export class AbstractChatModel {
125
131
  * Timestamp of the last read message in local storage.
126
132
  */
127
133
  get lastRead() {
128
- var _a;
129
- if (this._id === undefined) {
130
- return 0;
131
- }
132
- const storage = JSON.parse(localStorage.getItem(`@jupyter/chat:${this._id}`) || '{}');
133
- return (_a = storage.lastRead) !== null && _a !== void 0 ? _a : 0;
134
+ return this._lastRead;
134
135
  }
135
136
  set lastRead(value) {
137
+ this._lastRead = value;
136
138
  if (this._id === undefined) {
137
139
  return;
138
140
  }
141
+ // Save the last read message to the local storage, for persistence across reload.
139
142
  const storage = JSON.parse(localStorage.getItem(`@jupyter/chat:${this._id}`) || '{}');
140
143
  storage.lastRead = value;
141
144
  localStorage.setItem(`@jupyter/chat:${this._id}`, JSON.stringify(storage));
@@ -193,17 +196,16 @@ export class AbstractChatModel {
193
196
  return this._unreadMessages;
194
197
  }
195
198
  set unreadMessages(unread) {
196
- var _a;
197
199
  const recentlyRead = this._unreadMessages.filter(elem => !unread.includes(elem));
198
200
  const unreadCountDiff = unread.length - this._unreadMessages.length;
199
201
  this._unreadMessages = unread;
200
202
  this._unreadChanged.emit(this._unreadMessages);
201
203
  // Notify the change.
202
204
  this._notify(unread.length, unreadCountDiff > 0);
203
- // Save the last read to the local storage.
204
- if (this._id !== undefined && recentlyRead.length) {
205
+ // Save the last read.
206
+ if (recentlyRead.length) {
205
207
  let lastReadChanged = false;
206
- let lastRead = (_a = this.lastRead) !== null && _a !== void 0 ? _a : this.messages[recentlyRead[0]].time;
208
+ let lastRead = this.lastRead;
207
209
  recentlyRead.forEach(index => {
208
210
  if (this.messages[index].time > lastRead) {
209
211
  lastRead = this.messages[index].time;
@@ -289,10 +291,6 @@ export class AbstractChatModel {
289
291
  * Can be useful if some actions are required on the message.
290
292
  */
291
293
  formatChatMessage(message) {
292
- var _a;
293
- (_a = message.mentions) === null || _a === void 0 ? void 0 : _a.forEach(user => {
294
- message.body = replaceMentionToSpan(message.body, user);
295
- });
296
294
  return message;
297
295
  }
298
296
  /**
@@ -323,15 +321,13 @@ export class AbstractChatModel {
323
321
  * @param messages - the messages list.
324
322
  */
325
323
  messagesInserted(index, messages) {
326
- var _a;
327
324
  const formattedMessages = [];
328
325
  const unreadIndexes = [];
329
- const lastRead = (_a = this.lastRead) !== null && _a !== void 0 ? _a : 0;
330
326
  // Format the messages.
331
327
  messages.forEach((message, idx) => {
332
328
  const formattedMessage = this.formatChatMessage(message);
333
329
  formattedMessages.push(new Message(formattedMessage));
334
- if (message.time > lastRead) {
330
+ if (message.time > this.lastRead) {
335
331
  unreadIndexes.push(index + idx);
336
332
  }
337
333
  });
@@ -15,8 +15,9 @@ export async function pollUntilReady() {
15
15
  }
16
16
  export async function getJupyterLabTheme() {
17
17
  await pollUntilReady();
18
- const light = document.body.getAttribute('data-jp-theme-light');
18
+ const light = document.body.getAttribute('data-jp-theme-light') === 'true';
19
19
  return createTheme({
20
+ cssVariables: true,
20
21
  spacing: 4,
21
22
  components: {
22
23
  MuiButton: {
@@ -40,12 +41,12 @@ export async function getJupyterLabTheme() {
40
41
  // The default style for input toolbar button variant.
41
42
  props: { variant: 'input-toolbar' },
42
43
  style: {
43
- backgroundColor: 'var(--jp-brand-color1)',
44
- color: 'white',
44
+ backgroundColor: `var(--jp-brand-color${light ? '1' : '2'})`,
45
+ color: 'var(--jp-ui-inverse-font-color1)',
45
46
  borderRadius: '4px',
46
47
  boxShadow: 'none',
47
48
  '&:hover': {
48
- backgroundColor: 'var(--jp-brand-color0)',
49
+ backgroundColor: `var(--jp-brand-color${light ? '0' : '1'})`,
49
50
  boxShadow: 'none'
50
51
  },
51
52
  '&:disabled': {
@@ -97,12 +98,12 @@ export async function getJupyterLabTheme() {
97
98
  // The default style for input toolbar button variant.
98
99
  props: { variant: 'input-toolbar' },
99
100
  style: {
100
- backgroundColor: 'var(--jp-brand-color1)',
101
- color: 'white',
101
+ backgroundColor: `var(--jp-brand-color${light ? '1' : '2'})`,
102
+ color: 'var(--jp-ui-inverse-font-color1)',
102
103
  borderRadius: '4px',
103
104
  boxShadow: 'none',
104
105
  '&:hover': {
105
- backgroundColor: 'var(--jp-brand-color0)',
106
+ backgroundColor: `var(--jp-brand-color${light ? '0' : '1'})`,
106
107
  boxShadow: 'none'
107
108
  },
108
109
  '&:disabled': {
@@ -162,9 +163,9 @@ export async function getJupyterLabTheme() {
162
163
  paper: getCSSVariable('--jp-layout-color1'),
163
164
  default: getCSSVariable('--jp-layout-color1')
164
165
  },
165
- mode: light === 'true' ? 'light' : 'dark',
166
+ mode: light ? 'light' : 'dark',
166
167
  primary: {
167
- main: getCSSVariable('--jp-brand-color1'),
168
+ main: getCSSVariable(`--jp-brand-color${light ? '1' : '2'}`),
168
169
  light: getCSSVariable('--jp-brand-color2'),
169
170
  dark: getCSSVariable('--jp-brand-color0')
170
171
  },
package/lib/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ISignal } from '@lumino/signaling';
2
+ import { IRenderMime } from '@jupyterlab/rendermime';
2
3
  /**
3
4
  * The user description.
4
5
  */
@@ -57,7 +58,7 @@ export interface IConfig {
57
58
  */
58
59
  export type IMessageContent<T = IUser, U = IAttachment> = {
59
60
  type: string;
60
- body: string;
61
+ body: string | (Partial<IRenderMime.IMimeModel> & Pick<IRenderMime.IMimeModel, 'data'>);
61
62
  id: string;
62
63
  time: number;
63
64
  sender: T;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jupyter/chat",
3
- "version": "0.19.0",
3
+ "version": "0.20.0-alpha.1",
4
4
  "description": "A package that provides UI components that can be used to create a chat in a Jupyterlab extension.",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -60,7 +60,6 @@ export function ChatBody(props: Chat.IChatProps): JSX.Element {
60
60
  };
61
61
  }, [model]);
62
62
 
63
- // const horizontalPadding = props.area === 'main' ? 8 : 4;
64
63
  const horizontalPadding = 4;
65
64
 
66
65
  const contextValue: Chat.IChatProps = {
@@ -33,6 +33,10 @@ export function MessageFooterComponent(
33
33
  }
34
34
  const footer = messageFooterRegistry.getFooter();
35
35
 
36
+ if (!footer.left && !footer.center && !footer.right) {
37
+ return null;
38
+ }
39
+
36
40
  return (
37
41
  <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
38
42
  {footer.left?.component ? (