@jupyter/chat 0.20.0-alpha.3 → 0.21.0

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.
Files changed (64) hide show
  1. package/lib/__tests__/model.spec.js +49 -0
  2. package/lib/components/attachments.js +11 -16
  3. package/lib/components/chat.d.ts +5 -0
  4. package/lib/components/code-blocks/code-toolbar.js +12 -8
  5. package/lib/components/code-blocks/copy-button.js +9 -7
  6. package/lib/components/input/buttons/attach-button.js +4 -2
  7. package/lib/components/input/buttons/cancel-button.js +3 -1
  8. package/lib/components/input/buttons/save-edit-button.js +3 -1
  9. package/lib/components/input/buttons/send-button.js +4 -2
  10. package/lib/components/input/buttons/stop-button.js +3 -1
  11. package/lib/components/input/chat-input.js +3 -2
  12. package/lib/components/messages/header.js +7 -3
  13. package/lib/components/messages/message-renderer.js +17 -17
  14. package/lib/components/messages/navigation.js +5 -4
  15. package/lib/components/messages/toolbar.js +4 -2
  16. package/lib/components/writing-indicator.js +11 -6
  17. package/lib/context.d.ts +7 -0
  18. package/lib/context.js +12 -0
  19. package/lib/index.d.ts +1 -0
  20. package/lib/index.js +1 -0
  21. package/lib/message.d.ts +3 -3
  22. package/lib/message.js +3 -0
  23. package/lib/model.d.ts +16 -0
  24. package/lib/model.js +28 -8
  25. package/lib/theme-provider.js +0 -4
  26. package/lib/tokens.d.ts +8 -1
  27. package/lib/types.d.ts +50 -1
  28. package/lib/widgets/chat-error.d.ts +2 -1
  29. package/lib/widgets/chat-error.js +6 -3
  30. package/lib/widgets/chat-selector-popup.d.ts +6 -0
  31. package/lib/widgets/chat-selector-popup.js +8 -5
  32. package/lib/widgets/chat-sidebar.js +5 -1
  33. package/lib/widgets/chat-widget.js +6 -1
  34. package/lib/widgets/multichat-panel.d.ts +6 -0
  35. package/lib/widgets/multichat-panel.js +21 -13
  36. package/package.json +2 -1
  37. package/src/__tests__/model.spec.ts +58 -0
  38. package/src/components/attachments.tsx +18 -25
  39. package/src/components/chat.tsx +5 -0
  40. package/src/components/code-blocks/code-toolbar.tsx +14 -7
  41. package/src/components/code-blocks/copy-button.tsx +12 -8
  42. package/src/components/input/buttons/attach-button.tsx +4 -2
  43. package/src/components/input/buttons/cancel-button.tsx +4 -1
  44. package/src/components/input/buttons/save-edit-button.tsx +3 -1
  45. package/src/components/input/buttons/send-button.tsx +4 -2
  46. package/src/components/input/buttons/stop-button.tsx +3 -1
  47. package/src/components/input/chat-input.tsx +3 -2
  48. package/src/components/messages/header.tsx +9 -3
  49. package/src/components/messages/message-renderer.tsx +17 -17
  50. package/src/components/messages/navigation.tsx +5 -4
  51. package/src/components/messages/toolbar.tsx +6 -4
  52. package/src/components/writing-indicator.tsx +17 -6
  53. package/src/context.ts +13 -0
  54. package/src/index.ts +1 -0
  55. package/src/message.ts +6 -5
  56. package/src/model.ts +52 -4
  57. package/src/theme-provider.ts +0 -4
  58. package/src/tokens.ts +9 -3
  59. package/src/types.ts +52 -4
  60. package/src/widgets/chat-error.tsx +9 -5
  61. package/src/widgets/chat-selector-popup.tsx +21 -3
  62. package/src/widgets/chat-sidebar.tsx +5 -1
  63. package/src/widgets/chat-widget.tsx +7 -1
  64. package/src/widgets/multichat-panel.tsx +32 -12
package/src/message.ts CHANGED
@@ -3,14 +3,14 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
 
6
- import { IRenderMime } from '@jupyterlab/rendermime';
7
6
  import { ISignal, Signal } from '@lumino/signaling';
8
7
  import {
9
8
  IAttachment,
10
9
  IMessageContent,
11
10
  IMessage,
12
11
  IMessageMetadata,
13
- IUser
12
+ IUser,
13
+ IMimeModelBody
14
14
  } from './types';
15
15
 
16
16
  /**
@@ -39,9 +39,7 @@ export class Message implements IMessage {
39
39
  get type(): string {
40
40
  return this._content.type;
41
41
  }
42
- get body():
43
- | string
44
- | (Partial<IRenderMime.IMimeModel> & Pick<IRenderMime.IMimeModel, 'data'>) {
42
+ get body(): string {
45
43
  return this._content.body;
46
44
  }
47
45
  get id(): string {
@@ -74,6 +72,9 @@ export class Message implements IMessage {
74
72
  get metadata(): IMessageMetadata | undefined {
75
73
  return this._content.metadata;
76
74
  }
75
+ get mime_model(): IMimeModelBody | undefined {
76
+ return this._content.mime_model;
77
+ }
77
78
 
78
79
  /**
79
80
  * A signal emitting when the message has been updated.
package/src/model.ts CHANGED
@@ -4,6 +4,11 @@
4
4
  */
5
5
 
6
6
  import { IDocumentManager } from '@jupyterlab/docmanager';
7
+ import {
8
+ ITranslator,
9
+ nullTranslator,
10
+ TranslationBundle
11
+ } from '@jupyterlab/translation';
7
12
  import { ArrayExt } from '@lumino/algorithm';
8
13
  import { CommandRegistry } from '@lumino/commands';
9
14
  import { PromiseDelegate } from '@lumino/coreutils';
@@ -11,6 +16,7 @@ import { IDisposable } from '@lumino/disposable';
11
16
  import { ISignal, Signal } from '@lumino/signaling';
12
17
 
13
18
  import { IActiveCellManager } from './active-cell-manager';
19
+ import { TRANSLATION_DOMAIN } from './context';
14
20
  import { IInputModel, InputModel } from './input-model';
15
21
  import { Message } from './message';
16
22
  import { ISelectionWatcher } from './selection-watcher';
@@ -112,6 +118,11 @@ export interface IChatModel extends IDisposable {
112
118
  */
113
119
  readonly writersChanged?: ISignal<IChatModel, IChatModel.IWriter[]>;
114
120
 
121
+ /**
122
+ * A signal emitting when a message has been updated.
123
+ */
124
+ readonly messageChanged: ISignal<IChatModel, IMessage>;
125
+
115
126
  /**
116
127
  * A signal emitting when the message edition input changed change.
117
128
  */
@@ -237,6 +248,10 @@ export abstract class AbstractChatModel implements IChatModel {
237
248
  ...config
238
249
  };
239
250
 
251
+ this._trans = (options.translator ?? nullTranslator).load(
252
+ TRANSLATION_DOMAIN
253
+ );
254
+
240
255
  this._inputModel = new InputModel({
241
256
  activeCellManager: options.activeCellManager,
242
257
  selectionWatcher: options.selectionWatcher,
@@ -493,6 +508,13 @@ export abstract class AbstractChatModel implements IChatModel {
493
508
  return this._writersChanged;
494
509
  }
495
510
 
511
+ /**
512
+ * A signal emitting when a message has been updated.
513
+ */
514
+ get messageChanged(): ISignal<IChatModel, IMessage> {
515
+ return this._messageChanged;
516
+ }
517
+
496
518
  /**
497
519
  * A signal emitting when the message edition input changed change.
498
520
  */
@@ -528,6 +550,7 @@ export abstract class AbstractChatModel implements IChatModel {
528
550
  }
529
551
  this._isDisposed = true;
530
552
  this._disposed.emit();
553
+ Signal.clearData(this);
531
554
  }
532
555
 
533
556
  /**
@@ -580,7 +603,9 @@ export abstract class AbstractChatModel implements IChatModel {
580
603
  // Format the messages.
581
604
  messages.forEach((message, idx) => {
582
605
  const formattedMessage = this.formatChatMessage(message);
583
- formattedMessages.push(new Message(formattedMessage));
606
+ const msg = new Message(formattedMessage);
607
+ msg.changed.connect(this._onMessageChanged, this);
608
+ formattedMessages.push(msg);
584
609
  if (message.time > this.lastRead) {
585
610
  unreadIndexes.push(index + idx);
586
611
  }
@@ -613,7 +638,9 @@ export abstract class AbstractChatModel implements IChatModel {
613
638
  * @param count - the number of messages to delete.
614
639
  */
615
640
  messagesDeleted(index: number, count: number): void {
616
- this._messages.splice(index, count);
641
+ this._messages.splice(index, count).forEach(msg => {
642
+ msg.changed.disconnect(this._onMessageChanged, this);
643
+ });
617
644
  this._messagesUpdated.emit();
618
645
  }
619
646
 
@@ -695,14 +722,24 @@ export abstract class AbstractChatModel implements IChatModel {
695
722
  this._commands
696
723
  .execute('apputils:update-notification', {
697
724
  id: this._notificationId,
698
- message: `${unreadCount} incoming message(s) ${this._name ? 'in ' + this._name : ''}`
725
+ message: this._name
726
+ ? this._trans.__(
727
+ '%1 incoming message(s) in %2',
728
+ unreadCount,
729
+ this._name
730
+ )
731
+ : this._trans.__('%1 incoming message(s)', unreadCount)
699
732
  })
700
733
  .then(success => {
701
734
  // Create a new notification only if messages are added.
702
735
  if (!success && canCreate) {
703
736
  this._commands!.execute('apputils:notify', {
704
737
  type: 'info',
705
- message: `${unreadCount} incoming message(s) in ${this._name}`
738
+ message: this._trans.__(
739
+ '%1 incoming message(s) in %2',
740
+ unreadCount,
741
+ this._name
742
+ )
706
743
  }).then(id => {
707
744
  this._notificationId = id;
708
745
  });
@@ -718,6 +755,10 @@ export abstract class AbstractChatModel implements IChatModel {
718
755
  }
719
756
  }
720
757
 
758
+ private _onMessageChanged(msg: IMessage): void {
759
+ this._messageChanged.emit(msg);
760
+ }
761
+
721
762
  private _messages: IMessage[] = [];
722
763
  private _unreadMessages: number[] = [];
723
764
  private _lastRead: number = 0;
@@ -725,6 +766,7 @@ export abstract class AbstractChatModel implements IChatModel {
725
766
  private _id: string | undefined;
726
767
  private _name: string = '';
727
768
  private _config: IConfig;
769
+ protected _trans: TranslationBundle;
728
770
  private _readyDelegate = new PromiseDelegate<void>();
729
771
  private _inputModel: IInputModel;
730
772
  private _disposed = new Signal<this, void>(this);
@@ -737,6 +779,7 @@ export abstract class AbstractChatModel implements IChatModel {
737
779
  private _writers: IChatModel.IWriter[] = [];
738
780
  private _messageEditions = new Map<string, IInputModel>();
739
781
  private _messagesUpdated = new Signal<IChatModel, void>(this);
782
+ private _messageChanged = new Signal<IChatModel, IMessage>(this);
740
783
  private _configChanged = new Signal<IChatModel, IConfig>(this);
741
784
  private _unreadChanged = new Signal<IChatModel, number[]>(this);
742
785
  private _viewportChanged = new Signal<IChatModel, number[]>(this);
@@ -770,6 +813,11 @@ export namespace IChatModel {
770
813
  */
771
814
  commands?: CommandRegistry;
772
815
 
816
+ /**
817
+ * The translator for internationalization.
818
+ */
819
+ translator?: ITranslator;
820
+
773
821
  /**
774
822
  * Active cell manager.
775
823
  */
@@ -184,10 +184,6 @@ export async function getJupyterLabTheme(): Promise<Theme> {
184
184
  }
185
185
  },
186
186
  palette: {
187
- background: {
188
- paper: getCSSVariable('--jp-layout-color1'),
189
- default: getCSSVariable('--jp-layout-color1')
190
- },
191
187
  mode: light ? 'light' : 'dark',
192
188
  primary: {
193
189
  main: getCSSVariable(`--jp-brand-color${light ? '1' : '2'}`),
package/src/tokens.ts CHANGED
@@ -7,13 +7,19 @@ import { IWidgetTracker, MainAreaWidget } from '@jupyterlab/apputils';
7
7
  import { Token } from '@lumino/coreutils';
8
8
 
9
9
  import { ChatWidget } from './widgets';
10
+ import { IChatModel } from './model';
11
+
12
+ /**
13
+ * The main area chat widget type.
14
+ */
15
+ export type MainAreaChat = MainAreaWidget<ChatWidget> & {
16
+ model: IChatModel;
17
+ };
10
18
 
11
19
  /**
12
20
  * the chat tracker type.
13
21
  */
14
- export type IChatTracker = IWidgetTracker<
15
- ChatWidget | MainAreaWidget<ChatWidget>
16
- >;
22
+ export type IChatTracker = IWidgetTracker<ChatWidget | MainAreaChat>;
17
23
 
18
24
  /**
19
25
  * A chat tracker token.
package/src/types.ts CHANGED
@@ -61,6 +61,12 @@ export interface IConfig {
61
61
  showDeleted?: boolean;
62
62
  }
63
63
 
64
+ /**
65
+ * Mime model body type, a partial mime bundle model containing at least the data.
66
+ */
67
+ export type IMimeModelBody = Partial<IRenderMime.IMimeModel> &
68
+ Pick<IRenderMime.IMimeModel, 'data'>;
69
+
64
70
  /**
65
71
  * An empty interface to describe optional metadata attached to a chat message.
66
72
  * Extensions can augment this interface to add custom fields:
@@ -79,21 +85,59 @@ export interface IMessageMetadata {} /* eslint-disable-line @typescript-eslint/n
79
85
  * The chat message description.
80
86
  */
81
87
  export type IMessageContent<T = IUser, U = IAttachment> = {
88
+ /**
89
+ * The type of the message, usually 'msg' for a regular message.
90
+ */
82
91
  type: string;
83
- body:
84
- | string
85
- // Should contain at least the data of the mime model.
86
- | (Partial<IRenderMime.IMimeModel> & Pick<IRenderMime.IMimeModel, 'data'>);
92
+ /**
93
+ * The body of the message, markdown formatted.
94
+ */
95
+ body: string;
96
+ /**
97
+ * The message id (should be unique).
98
+ */
87
99
  id: string;
100
+ /**
101
+ * The message timestamp (seconds since epoch).
102
+ */
88
103
  time: number;
104
+ /**
105
+ * The sender of the message, default to IUser type.
106
+ */
89
107
  sender: T;
108
+ /**
109
+ * The attachment list, default to IAttachment type.
110
+ */
90
111
  attachments?: U[];
112
+ /**
113
+ * Optional, list of users mentioned in the message.
114
+ */
91
115
  mentions?: T[];
116
+ /**
117
+ * Optional, whether the message time has been verified.
118
+ */
92
119
  raw_time?: boolean;
120
+ /**
121
+ * Optional, whether the message has been deleted.
122
+ */
93
123
  deleted?: boolean;
124
+ /**
125
+ * Optional, whether the message has been edited.
126
+ */
94
127
  edited?: boolean;
128
+ /**
129
+ * Optional, whether the message should be stacked to the previous one.
130
+ */
95
131
  stacked?: boolean;
132
+ /**
133
+ * Optional, the metadata of the message.
134
+ */
96
135
  metadata?: IMessageMetadata;
136
+ /**
137
+ * Optional, a mime bundle.
138
+ * If provided, the body won't be displayed in favor of the mime bundle.
139
+ */
140
+ mime_model?: IMimeModelBody;
97
141
  };
98
142
 
99
143
  export interface IMessage extends IMessageContent {
@@ -187,6 +231,10 @@ export interface INotebookAttachment {
187
231
  * The local path of the notebook, relative to `ContentsManager.root_dir`.
188
232
  */
189
233
  value: string;
234
+ /**
235
+ * (optional) The MIME type of the attachment.
236
+ */
237
+ mimetype?: string;
190
238
  /**
191
239
  * (optional) A list of cells in the notebook.
192
240
  */
@@ -4,15 +4,19 @@
4
4
  */
5
5
 
6
6
  import { IThemeManager, ReactWidget } from '@jupyterlab/apputils';
7
+ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
7
8
  import { Alert, Box } from '@mui/material';
8
9
  import React from 'react';
9
10
 
10
11
  import { JlThemeProvider } from '../components/jl-theme-provider';
12
+ import { TRANSLATION_DOMAIN } from '../context';
11
13
  import { chatIcon } from '../icons';
12
14
 
13
15
  export function buildErrorWidget(
14
- themeManager: IThemeManager | null
16
+ themeManager: IThemeManager | null,
17
+ translator?: ITranslator
15
18
  ): ReactWidget {
19
+ const trans = (translator ?? nullTranslator).load(TRANSLATION_DOMAIN);
16
20
  const ErrorWidget = ReactWidget.create(
17
21
  <JlThemeProvider themeManager={themeManager}>
18
22
  <Box
@@ -27,9 +31,9 @@ export function buildErrorWidget(
27
31
  >
28
32
  <Box sx={{ padding: 4 }}>
29
33
  <Alert severity="error">
30
- There seems to be a problem with the Chat backend, please look at
31
- the JupyterLab server logs or contact your administrator to correct
32
- this problem.
34
+ {trans.__(
35
+ 'There seems to be a problem with the Chat backend, please look at the JupyterLab server logs or contact your administrator to correct this problem.'
36
+ )}
33
37
  </Alert>
34
38
  </Box>
35
39
  </Box>
@@ -37,7 +41,7 @@ export function buildErrorWidget(
37
41
  );
38
42
  ErrorWidget.id = 'jupyter-chat::chat';
39
43
  ErrorWidget.title.icon = chatIcon;
40
- ErrorWidget.title.caption = 'Jupyter Chat'; // TODO: i18n
44
+ ErrorWidget.title.caption = trans.__('Jupyter Chat');
41
45
 
42
46
  return ErrorWidget;
43
47
  }
@@ -4,10 +4,17 @@
4
4
  */
5
5
 
6
6
  import { Button } from '@jupyter/react-components';
7
+ import {
8
+ ITranslator,
9
+ nullTranslator,
10
+ TranslationBundle
11
+ } from '@jupyterlab/translation';
7
12
  import { closeIcon, ReactWidget } from '@jupyterlab/ui-components';
8
13
  import { Message } from '@lumino/messaging';
9
14
  import React, { useEffect, useRef } from 'react';
10
15
 
16
+ import { TRANSLATION_DOMAIN } from '../context';
17
+
11
18
  const POPUP_CLASS = 'jp-chat-selector-popup';
12
19
  const POPUP_LIST_CLASS = 'jp-chat-selector-popup-list';
13
20
  const POPUP_ITEM_CLASS = 'jp-chat-selector-popup-item';
@@ -26,6 +33,9 @@ export class ChatSelectorPopup extends ReactWidget {
26
33
  this._onSelect = options.onSelect;
27
34
  this._onClose = options.onClose;
28
35
  this._anchor = options.anchor ?? null;
36
+ this._trans = (options.translator ?? nullTranslator).load(
37
+ TRANSLATION_DOMAIN
38
+ );
29
39
 
30
40
  // Start hidden
31
41
  this.hide();
@@ -181,6 +191,7 @@ export class ChatSelectorPopup extends ReactWidget {
181
191
  onSelect={this._handleItemClick}
182
192
  onUpdateSelectedName={this._handleUpdateSelectedName}
183
193
  onClose={this._handleClose}
194
+ trans={this._trans}
184
195
  />
185
196
  );
186
197
  }
@@ -319,6 +330,7 @@ export class ChatSelectorPopup extends ReactWidget {
319
330
  private _onClose?: (name: string) => void;
320
331
  private _anchor: HTMLElement | null = null;
321
332
  private _anchorRect: DOMRect | null = null;
333
+ private _trans: TranslationBundle;
322
334
  }
323
335
 
324
336
  /**
@@ -342,6 +354,10 @@ export namespace ChatSelectorPopup {
342
354
  * The element to anchor the popup to.
343
355
  */
344
356
  anchor?: HTMLElement;
357
+ /**
358
+ * The translator for internationalization.
359
+ */
360
+ translator?: ITranslator;
345
361
  }
346
362
  }
347
363
 
@@ -355,6 +371,7 @@ interface IChatSelectorListProps {
355
371
  onSelect: (name: string) => void;
356
372
  onUpdateSelectedName: (name: string) => void;
357
373
  onClose: (names: string) => void;
374
+ trans: TranslationBundle;
358
375
  }
359
376
 
360
377
  /**
@@ -366,7 +383,8 @@ function ChatSelectorList({
366
383
  loadedModels,
367
384
  onSelect,
368
385
  onUpdateSelectedName,
369
- onClose
386
+ onClose,
387
+ trans
370
388
  }: IChatSelectorListProps): JSX.Element {
371
389
  const listRef = useRef<HTMLUListElement>(null);
372
390
 
@@ -388,7 +406,7 @@ function ChatSelectorList({
388
406
  };
389
407
 
390
408
  if (names.length === 0) {
391
- return <div className={POPUP_EMPTY_CLASS}>No chats found</div>;
409
+ return <div className={POPUP_EMPTY_CLASS}>{trans.__('No chat found')}</div>;
392
410
  }
393
411
 
394
412
  return (
@@ -425,7 +443,7 @@ function ChatSelectorList({
425
443
  <Button
426
444
  onClick={e => handleCloseClick(e, name)}
427
445
  appearance="stealth"
428
- title="Close and dispose this chat"
446
+ title={trans.__('Close and dispose this chat')}
429
447
  className="jp-chat-selector-popup-item-close"
430
448
  >
431
449
  <closeIcon.react tag={null} />
@@ -3,14 +3,18 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
 
6
+ import { nullTranslator } from '@jupyterlab/translation';
7
+
6
8
  import { Chat } from '../components/chat';
9
+ import { TRANSLATION_DOMAIN } from '../context';
7
10
  import { chatIcon } from '../icons';
8
11
  import { ChatWidget } from './chat-widget';
9
12
 
10
13
  export function buildChatSidebar(options: Chat.IOptions): ChatWidget {
11
14
  const widget = new ChatWidget(options);
15
+ const trans = (options.translator ?? nullTranslator).load(TRANSLATION_DOMAIN);
12
16
  widget.id = 'jupyter-chat::side-panel';
13
17
  widget.title.icon = chatIcon;
14
- widget.title.caption = 'Jupyter Chat'; // TODO: i18n
18
+ widget.title.caption = trans.__('Jupyter Chat');
15
19
  return widget;
16
20
  }
@@ -8,12 +8,14 @@ import { Cell } from '@jupyterlab/cells';
8
8
  import { DirListing } from '@jupyterlab/filebrowser';
9
9
  import { DocumentWidget } from '@jupyterlab/docregistry';
10
10
  import { ICell, isCode, isMarkdown, isRaw } from '@jupyterlab/nbformat';
11
+ import { nullTranslator } from '@jupyterlab/translation';
11
12
  import React from 'react';
12
13
  import { Message } from '@lumino/messaging';
13
14
  import { Drag } from '@lumino/dragdrop';
14
15
  import { Widget } from '@lumino/widgets';
15
16
 
16
17
  import { Chat, IInputToolbarRegistry, MESSAGE_CLASS } from '../components';
18
+ import { TRANSLATION_DOMAIN } from '../context';
17
19
  import { chatIcon } from '../icons';
18
20
  import { IChatModel } from '../model';
19
21
  import {
@@ -42,8 +44,11 @@ export class ChatWidget extends ReactWidget {
42
44
  constructor(options: Chat.IOptions) {
43
45
  super();
44
46
 
47
+ const trans = (options.translator ?? nullTranslator).load(
48
+ TRANSLATION_DOMAIN
49
+ );
45
50
  this.title.icon = chatIcon;
46
- this.title.caption = 'Jupyter Chat'; // TODO: i18n
51
+ this.title.caption = trans.__('Jupyter Chat');
47
52
 
48
53
  this._chatOptions = options;
49
54
  this.id = `jupyter-chat::widget::${options.model.name}`;
@@ -334,6 +339,7 @@ export class ChatWidget extends ReactWidget {
334
339
  const attachment: INotebookAttachment = {
335
340
  type: 'notebook',
336
341
  value: notebookPath,
342
+ mimetype: 'application/x-ipynb+json',
337
343
  cells: validCells
338
344
  };
339
345
  const inputModel = this._getInputFromEvent(event);
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import { InputDialog } from '@jupyterlab/apputils';
12
+ import { nullTranslator, TranslationBundle } from '@jupyterlab/translation';
12
13
  import {
13
14
  addIcon,
14
15
  closeIcon,
@@ -32,6 +33,7 @@ import {
32
33
  IInputToolbarRegistry,
33
34
  IInputToolbarRegistryFactory
34
35
  } from '../components';
36
+ import { TRANSLATION_DOMAIN } from '../context';
35
37
  import { chatIcon, readIcon } from '../icons';
36
38
  import { IChatModel } from '../model';
37
39
 
@@ -52,7 +54,11 @@ export class MultiChatPanel extends PanelWithToolbar {
52
54
  super(options);
53
55
  this.id = 'jupyter-chat::multi-chat-panel';
54
56
  this.title.icon = chatIcon;
55
- this.title.caption = 'Jupyter Chat'; // TODO: i18n/
57
+
58
+ const translator = options.translator ?? nullTranslator;
59
+ this._trans = translator.load(TRANSLATION_DOMAIN);
60
+
61
+ this.title.caption = this._trans.__('Jupyter Chat');
56
62
 
57
63
  this.addClass(SIDEPANEL_CLASS);
58
64
 
@@ -72,7 +78,7 @@ export class MultiChatPanel extends PanelWithToolbar {
72
78
  this.open(addChatArgs);
73
79
  },
74
80
  icon: addIcon,
75
- tooltip: 'Create a new chat'
81
+ tooltip: this._trans.__('Create a new chat')
76
82
  });
77
83
  addChat.addClass(ADD_BUTTON_CLASS);
78
84
  this.toolbar.addItem('createChat', addChat);
@@ -85,6 +91,7 @@ export class MultiChatPanel extends PanelWithToolbar {
85
91
  selectChat={this._onSelectChat}
86
92
  getPopup={() => this._chatSelectorPopup}
87
93
  chatOpened={this._chatOpened}
94
+ trans={this._trans}
88
95
  />
89
96
  );
90
97
  this._openChatWidget.addClass(OPEN_SELECT_CLASS);
@@ -96,7 +103,8 @@ export class MultiChatPanel extends PanelWithToolbar {
96
103
  onSelect: this._onSelectChat,
97
104
  onClose: (name: string) => {
98
105
  this.disposeLoadedModel(name);
99
- }
106
+ },
107
+ translator
100
108
  });
101
109
  }
102
110
 
@@ -234,7 +242,8 @@ export class MultiChatPanel extends PanelWithToolbar {
234
242
  renameChat: this._renameChat,
235
243
  onClose: (name: string) => {
236
244
  this.disposeLoadedModel(name);
237
- }
245
+ },
246
+ trans: this._trans
238
247
  });
239
248
 
240
249
  // Add to content panel
@@ -389,6 +398,7 @@ export class MultiChatPanel extends PanelWithToolbar {
389
398
  private _currentWidget?: SidePanelWidget;
390
399
  private _chatNames: { [name: string]: string } = {};
391
400
  private _visibilityChanged = new Signal<MultiChatPanel, boolean>(this);
401
+ private _trans: TranslationBundle;
392
402
  }
393
403
 
394
404
  /**
@@ -457,6 +467,7 @@ class SidePanelWidget extends PanelWithToolbar {
457
467
  super();
458
468
  this._chatWidget = options.widget;
459
469
  this._displayName = options.displayName ?? options.widget.model.name;
470
+ const trans = options.trans;
460
471
 
461
472
  this.addClass(SIDEPANEL_WIDGET_CLASS);
462
473
  this.toolbar.addClass(TOOLBAR_CLASS);
@@ -477,7 +488,7 @@ class SidePanelWidget extends PanelWithToolbar {
477
488
  // Add toolbar buttons
478
489
  this._markAsRead = new ToolbarButton({
479
490
  icon: readIcon,
480
- iconLabel: 'Mark chat as read',
491
+ iconLabel: trans.__('Mark chat as read'),
481
492
  className: 'jp-mod-styled',
482
493
  onClick: () => {
483
494
  if (this.model) {
@@ -490,7 +501,7 @@ class SidePanelWidget extends PanelWithToolbar {
490
501
  if (options.renameChat) {
491
502
  const renameButton = new ToolbarButton({
492
503
  iconClass: 'jp-EditIcon',
493
- iconLabel: 'Rename chat',
504
+ iconLabel: trans.__('Rename chat'),
494
505
  className: 'jp-mod-styled',
495
506
  onClick: async () => {
496
507
  if (!options.renameChat) {
@@ -500,9 +511,9 @@ class SidePanelWidget extends PanelWithToolbar {
500
511
  if (options.renameChat === true) {
501
512
  // If rename chat is true, let's provide a input to select new name.
502
513
  const result = await InputDialog.getText({
503
- title: 'Rename Chat',
514
+ title: trans.__('Rename Chat'),
504
515
  text: this.model.name,
505
- placeholder: 'new-name'
516
+ placeholder: trans.__('new-name')
506
517
  });
507
518
  if (!result.button.accept && result.value) {
508
519
  return;
@@ -526,7 +537,7 @@ class SidePanelWidget extends PanelWithToolbar {
526
537
  if (options.openInMain) {
527
538
  const moveToMain = new ToolbarButton({
528
539
  icon: launchIcon,
529
- iconLabel: 'Move the chat to the main area',
540
+ iconLabel: trans.__('Move the chat to the main area'),
530
541
  className: 'jp-mod-styled',
531
542
  onClick: async () => {
532
543
  const name = this.model.name;
@@ -540,7 +551,7 @@ class SidePanelWidget extends PanelWithToolbar {
540
551
 
541
552
  const closeButton = new ToolbarButton({
542
553
  icon: closeIcon,
543
- iconLabel: 'Close the chat',
554
+ iconLabel: trans.__('Close the chat'),
544
555
  className: 'jp-mod-styled',
545
556
  onClick: () => {
546
557
  options.onClose(this._displayName);
@@ -668,6 +679,10 @@ namespace SidePanelWidget {
668
679
  * The callback to rename the chat.
669
680
  */
670
681
  renameChat?: boolean | ((oldName: string) => Promise<string | null>);
682
+ /**
683
+ * The translation bundle.
684
+ */
685
+ trans: TranslationBundle;
671
686
  }
672
687
  }
673
688
 
@@ -684,6 +699,10 @@ type ChatSearchInputProps = {
684
699
  * Signal emitting when a chat is opened.
685
700
  */
686
701
  chatOpened: ISignal<MultiChatPanel, ChatWidget>;
702
+ /**
703
+ * The translation bundle.
704
+ */
705
+ trans: TranslationBundle;
687
706
  };
688
707
 
689
708
  /**
@@ -692,7 +711,8 @@ type ChatSearchInputProps = {
692
711
  function ChatSearchInput({
693
712
  selectChat,
694
713
  getPopup,
695
- chatOpened
714
+ chatOpened,
715
+ trans
696
716
  }: ChatSearchInputProps): JSX.Element {
697
717
  const [query, setQuery] = useState<string>('');
698
718
  const inputRef = useRef<HTMLInputElement>(null);
@@ -777,7 +797,7 @@ function ChatSearchInput({
777
797
  <input
778
798
  ref={inputRef}
779
799
  type="text"
780
- placeholder="Select a chat"
800
+ placeholder={trans.__('Select a chat')}
781
801
  value={query}
782
802
  onChange={handleInputChange}
783
803
  onFocus={handleInputFocus}