@jupyter/chat 0.20.0 → 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 (57) hide show
  1. package/lib/__tests__/model.spec.js +49 -0
  2. package/lib/components/attachments.js +3 -3
  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/message.d.ts +2 -1
  20. package/lib/message.js +3 -0
  21. package/lib/model.d.ts +16 -0
  22. package/lib/model.js +28 -8
  23. package/lib/types.d.ts +46 -1
  24. package/lib/widgets/chat-error.d.ts +2 -1
  25. package/lib/widgets/chat-error.js +6 -3
  26. package/lib/widgets/chat-selector-popup.d.ts +6 -0
  27. package/lib/widgets/chat-selector-popup.js +8 -5
  28. package/lib/widgets/chat-sidebar.js +5 -1
  29. package/lib/widgets/chat-widget.js +6 -1
  30. package/lib/widgets/multichat-panel.d.ts +6 -0
  31. package/lib/widgets/multichat-panel.js +21 -13
  32. package/package.json +2 -1
  33. package/src/__tests__/model.spec.ts +58 -0
  34. package/src/components/attachments.tsx +3 -3
  35. package/src/components/chat.tsx +5 -0
  36. package/src/components/code-blocks/code-toolbar.tsx +14 -7
  37. package/src/components/code-blocks/copy-button.tsx +12 -8
  38. package/src/components/input/buttons/attach-button.tsx +4 -2
  39. package/src/components/input/buttons/cancel-button.tsx +4 -1
  40. package/src/components/input/buttons/save-edit-button.tsx +3 -1
  41. package/src/components/input/buttons/send-button.tsx +4 -2
  42. package/src/components/input/buttons/stop-button.tsx +3 -1
  43. package/src/components/input/chat-input.tsx +3 -2
  44. package/src/components/messages/header.tsx +9 -3
  45. package/src/components/messages/message-renderer.tsx +17 -17
  46. package/src/components/messages/navigation.tsx +5 -4
  47. package/src/components/messages/toolbar.tsx +6 -4
  48. package/src/components/writing-indicator.tsx +17 -6
  49. package/src/context.ts +13 -0
  50. package/src/message.ts +4 -1
  51. package/src/model.ts +52 -4
  52. package/src/types.ts +46 -1
  53. package/src/widgets/chat-error.tsx +9 -5
  54. package/src/widgets/chat-selector-popup.tsx +21 -3
  55. package/src/widgets/chat-sidebar.tsx +5 -1
  56. package/src/widgets/chat-widget.tsx +7 -1
  57. package/src/widgets/multichat-panel.tsx +32 -12
@@ -10,7 +10,7 @@ import { PathExt } from '@jupyterlab/coreutils';
10
10
  import { UUID } from '@lumino/coreutils';
11
11
 
12
12
  import { TooltippedIconButton } from './mui-extras';
13
- import { useChatContext } from '../context';
13
+ import { useChatContext, useTranslator } from '../context';
14
14
  import { IAttachment } from '../types';
15
15
 
16
16
  const ATTACHMENT_CLASS = 'jp-chat-attachment';
@@ -87,7 +87,7 @@ export type AttachmentProps = AttachmentsProps & {
87
87
  * The Attachment component.
88
88
  */
89
89
  export function AttachmentPreview(props: AttachmentProps): JSX.Element {
90
- const remove_tooltip = 'Remove attachment';
90
+ const trans = useTranslator();
91
91
  const { attachmentOpenerRegistry } = useChatContext();
92
92
  const isClickable = !!attachmentOpenerRegistry?.get(props.attachment.type);
93
93
 
@@ -133,7 +133,7 @@ export function AttachmentPreview(props: AttachmentProps): JSX.Element {
133
133
  </Tooltip>
134
134
  {props.onRemove && (
135
135
  <TooltippedIconButton
136
- tooltip={remove_tooltip}
136
+ tooltip={trans.__('Remove attachment')}
137
137
  onClick={() => props.onRemove!(props.attachment)}
138
138
  className={REMOVE_BUTTON_CLASS}
139
139
  inputToolbar={false}
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { IThemeManager } from '@jupyterlab/apputils';
7
7
  import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
8
+ import { ITranslator } from '@jupyterlab/translation';
8
9
  import ArrowBackIcon from '@mui/icons-material/ArrowBack';
9
10
  import SettingsIcon from '@mui/icons-material/Settings';
10
11
  import { IconButton } from '@mui/material';
@@ -181,6 +182,10 @@ export namespace Chat {
181
182
  * The area where the chat is displayed.
182
183
  */
183
184
  area?: ChatArea;
185
+ /**
186
+ * The translator for internationalization.
187
+ */
188
+ translator?: ITranslator;
184
189
  }
185
190
 
186
191
  /**
@@ -9,6 +9,7 @@ import React, { useEffect, useState } from 'react';
9
9
 
10
10
  import { CopyButton } from './copy-button';
11
11
  import { TooltippedIconButton } from '../mui-extras';
12
+ import { useTranslator } from '../../context';
12
13
  import { IActiveCellManager } from '../../active-cell-manager';
13
14
  import { replaceCellIcon } from '../../icons';
14
15
  import { IChatModel } from '../../model';
@@ -110,9 +111,10 @@ type ToolbarButtonProps = {
110
111
  };
111
112
 
112
113
  function InsertAboveButton(props: ToolbarButtonProps) {
114
+ const trans = useTranslator();
113
115
  const tooltip = props.activeCellAvailable
114
- ? 'Insert above active cell'
115
- : 'Insert above active cell (no active cell)';
116
+ ? trans.__('Insert above active cell')
117
+ : trans.__('Insert above active cell (no active cell)');
116
118
 
117
119
  return (
118
120
  <TooltippedIconButton
@@ -128,9 +130,10 @@ function InsertAboveButton(props: ToolbarButtonProps) {
128
130
  }
129
131
 
130
132
  function InsertBelowButton(props: ToolbarButtonProps) {
133
+ const trans = useTranslator();
131
134
  const tooltip = props.activeCellAvailable
132
- ? 'Insert below active cell'
133
- : 'Insert below active cell (no active cell)';
135
+ ? trans.__('Insert below active cell')
136
+ : trans.__('Insert below active cell (no active cell)');
134
137
 
135
138
  return (
136
139
  <TooltippedIconButton
@@ -146,11 +149,15 @@ function InsertBelowButton(props: ToolbarButtonProps) {
146
149
  }
147
150
 
148
151
  function ReplaceButton(props: ToolbarButtonProps) {
152
+ const trans = useTranslator();
149
153
  const tooltip = props.selectionExists
150
- ? `Replace selection (${props.selectionWatcher?.selection?.numLines} line(s))`
154
+ ? trans.__(
155
+ 'Replace selection (%1 line(s))',
156
+ props.selectionWatcher?.selection?.numLines ?? 0
157
+ )
151
158
  : props.activeCellAvailable
152
- ? 'Replace selection (active cell)'
153
- : 'Replace selection (no selection)';
159
+ ? trans.__('Replace selection (active cell)')
160
+ : trans.__('Replace selection (no selection)');
154
161
 
155
162
  const disabled = !props.activeCellAvailable && !props.selectionExists;
156
163
 
@@ -7,6 +7,7 @@ import { copyIcon } from '@jupyterlab/ui-components';
7
7
  import React, { useState, useCallback, useRef } from 'react';
8
8
 
9
9
  import { TooltippedIconButton } from '../mui-extras';
10
+ import { useTranslator } from '../../context';
10
11
 
11
12
  enum CopyStatus {
12
13
  None,
@@ -15,19 +16,13 @@ enum CopyStatus {
15
16
  Disabled
16
17
  }
17
18
 
18
- const COPYBTN_TEXT_BY_STATUS: Record<CopyStatus, string> = {
19
- [CopyStatus.None]: 'Copy to clipboard',
20
- [CopyStatus.Copying]: 'Copying…',
21
- [CopyStatus.Copied]: 'Copied!',
22
- [CopyStatus.Disabled]: 'Copy to clipboard disabled in insecure context'
23
- };
24
-
25
19
  type CopyButtonProps = {
26
20
  value: string;
27
21
  className?: string;
28
22
  };
29
23
 
30
24
  export function CopyButton(props: CopyButtonProps): JSX.Element {
25
+ const trans = useTranslator();
31
26
  const isCopyDisabled = navigator.clipboard === undefined;
32
27
  const [copyStatus, setCopyStatus] = useState<CopyStatus>(
33
28
  isCopyDisabled ? CopyStatus.Disabled : CopyStatus.None
@@ -58,6 +53,15 @@ export function CopyButton(props: CopyButtonProps): JSX.Element {
58
53
  );
59
54
  }, [copyStatus, props.value]);
60
55
 
56
+ const COPYBTN_TEXT_BY_STATUS: Record<CopyStatus, string> = {
57
+ [CopyStatus.None]: trans.__('Copy to clipboard'),
58
+ [CopyStatus.Copying]: trans.__('Copying…'),
59
+ [CopyStatus.Copied]: trans.__('Copied!'),
60
+ [CopyStatus.Disabled]: trans.__(
61
+ 'Copy to clipboard disabled in insecure context'
62
+ )
63
+ };
64
+
61
65
  const tooltip = COPYBTN_TEXT_BY_STATUS[copyStatus];
62
66
 
63
67
  return (
@@ -67,7 +71,7 @@ export function CopyButton(props: CopyButtonProps): JSX.Element {
67
71
  tooltip={tooltip}
68
72
  placement="top"
69
73
  onClick={copy}
70
- aria-label="Copy to clipboard"
74
+ aria-label={trans.__('Copy to clipboard')}
71
75
  inputToolbar={false}
72
76
  >
73
77
  <copyIcon.react height="16px" width="16px" />
@@ -9,6 +9,7 @@ import React from 'react';
9
9
 
10
10
  import { InputToolbarRegistry } from '../toolbar-registry';
11
11
  import { TooltippedIconButton } from '../../mui-extras';
12
+ import { useTranslator } from '../../../context';
12
13
 
13
14
  const ATTACH_BUTTON_CLASS = 'jp-chat-attach-button';
14
15
 
@@ -19,7 +20,8 @@ export function AttachButton(
19
20
  props: InputToolbarRegistry.IToolbarItemProps
20
21
  ): JSX.Element {
21
22
  const { model } = props;
22
- const tooltip = 'Add attachment';
23
+ const trans = useTranslator();
24
+ const tooltip = trans.__('Add attachment');
23
25
 
24
26
  if (!model.documentManager || !model.addAttachment) {
25
27
  return <></>;
@@ -31,7 +33,7 @@ export function AttachButton(
31
33
  }
32
34
  try {
33
35
  const files = await FileDialog.getOpenFiles({
34
- title: 'Select files to attach',
36
+ title: trans.__('Select files to attach'),
35
37
  manager: model.documentManager
36
38
  });
37
39
  if (files.value) {
@@ -8,6 +8,7 @@ import React from 'react';
8
8
 
9
9
  import { InputToolbarRegistry } from '../toolbar-registry';
10
10
  import { TooltippedIconButton } from '../../mui-extras';
11
+ import { useTranslator } from '../../../context';
11
12
 
12
13
  const CANCEL_BUTTON_CLASS = 'jp-chat-cancel-button';
13
14
 
@@ -17,10 +18,12 @@ const CANCEL_BUTTON_CLASS = 'jp-chat-cancel-button';
17
18
  export function CancelButton(
18
19
  props: InputToolbarRegistry.IToolbarItemProps
19
20
  ): JSX.Element {
21
+ const trans = useTranslator();
22
+
20
23
  if (!props.model.cancel) {
21
24
  return <></>;
22
25
  }
23
- const tooltip = 'Cancel editing';
26
+ const tooltip = trans.__('Cancel editing');
24
27
  return (
25
28
  <TooltippedIconButton
26
29
  onClick={props.model.cancel}
@@ -8,6 +8,7 @@ import React, { useEffect, useState } from 'react';
8
8
 
9
9
  import { InputToolbarRegistry } from '../toolbar-registry';
10
10
  import { TooltippedIconButton } from '../../mui-extras';
11
+ import { useTranslator } from '../../../context';
11
12
 
12
13
  const SAVE_EDIT_BUTTON_CLASS = 'jp-chat-save-edit-button';
13
14
 
@@ -18,6 +19,7 @@ export function SaveEditButton(
18
19
  props: InputToolbarRegistry.IToolbarItemProps
19
20
  ): JSX.Element {
20
21
  const { model, chatCommandRegistry, edit } = props;
22
+ const trans = useTranslator();
21
23
 
22
24
  // Don't show this button when not in edit mode
23
25
  if (!edit) {
@@ -25,7 +27,7 @@ export function SaveEditButton(
25
27
  }
26
28
 
27
29
  const [disabled, setDisabled] = useState(false);
28
- const tooltip = 'Save edits';
30
+ const tooltip = trans.__('Save edits');
29
31
 
30
32
  useEffect(() => {
31
33
  const inputChanged = () => {
@@ -8,6 +8,7 @@ import React, { useEffect, useState } from 'react';
8
8
 
9
9
  import { InputToolbarRegistry } from '../toolbar-registry';
10
10
  import { TooltippedIconButton } from '../../mui-extras';
11
+ import { useTranslator } from '../../../context';
11
12
  import { IInputModel, InputModel } from '../../../input-model';
12
13
 
13
14
  const SEND_BUTTON_CLASS = 'jp-chat-send-button';
@@ -19,6 +20,7 @@ export function SendButton(
19
20
  props: InputToolbarRegistry.IToolbarItemProps
20
21
  ): JSX.Element {
21
22
  const { model, chatCommandRegistry, edit } = props;
23
+ const trans = useTranslator();
22
24
 
23
25
  // Don't show this button when in edit mode
24
26
  if (edit) {
@@ -42,8 +44,8 @@ export function SendButton(
42
44
  const configChanged = (_: IInputModel, config: InputModel.IConfig) => {
43
45
  setTooltip(
44
46
  (config.sendWithShiftEnter ?? false)
45
- ? 'Send message (SHIFT+ENTER)'
46
- : 'Send message (ENTER)'
47
+ ? trans.__('Send message (SHIFT+ENTER)')
48
+ : trans.__('Send message (ENTER)')
47
49
  );
48
50
  };
49
51
  model.configChanged.connect(configChanged);
@@ -8,6 +8,7 @@ import React, { useEffect, useState } from 'react';
8
8
 
9
9
  import { InputToolbarRegistry } from '../toolbar-registry';
10
10
  import { TooltippedIconButton } from '../../mui-extras';
11
+ import { useTranslator } from '../../../context';
11
12
 
12
13
  const STOP_BUTTON_CLASS = 'jp-chat-stop-button';
13
14
 
@@ -19,7 +20,8 @@ export function StopButton(
19
20
  ): JSX.Element {
20
21
  const { chatModel } = props;
21
22
  const [disabled, setDisabled] = useState(true);
22
- const tooltip = 'Stop generating';
23
+ const trans = useTranslator();
24
+ const tooltip = trans.__('Stop generating');
23
25
 
24
26
  useEffect(() => {
25
27
  if (!chatModel) {
@@ -17,7 +17,7 @@ import React, { useEffect, useRef, useState } from 'react';
17
17
  import { InputToolbarRegistry } from './toolbar-registry';
18
18
  import { useChatCommands } from './use-chat-commands';
19
19
  import { AttachmentPreviewList } from '../attachments';
20
- import { useChatContext } from '../../context';
20
+ import { useChatContext, useTranslator } from '../../context';
21
21
  import { IInputModel, InputModel } from '../../input-model';
22
22
  import { IAttachment } from '../../types';
23
23
 
@@ -27,6 +27,7 @@ const INPUT_TOOLBAR_CLASS = 'jp-chat-input-toolbar';
27
27
 
28
28
  export function ChatInput(props: ChatInput.IProps): JSX.Element {
29
29
  const { model } = props;
30
+ const trans = useTranslator();
30
31
  const { area, chatCommandRegistry, inputToolbarRegistry } = useChatContext();
31
32
  const chatModel = useChatContext().model;
32
33
 
@@ -230,7 +231,7 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
230
231
  multiline
231
232
  maxRows={10}
232
233
  onKeyDown={handleKeyDown}
233
- placeholder="Type a chat message, @ to mention..."
234
+ placeholder={trans.__('Type a chat message, @ to mention...')}
234
235
  inputRef={inputRef}
235
236
  onSelect={() =>
236
237
  (model.cursorIndex = inputRef.current?.selectionStart ?? null)
@@ -7,6 +7,7 @@ import { Box, Typography } from '@mui/material';
7
7
  import React, { useEffect, useState } from 'react';
8
8
 
9
9
  import { Avatar } from '../avatar';
10
+ import { useTranslator } from '../../context';
10
11
  import { IMessageContent, IMessage } from '../../types';
11
12
 
12
13
  const MESSAGE_HEADER_CLASS = 'jp-chat-message-header';
@@ -30,6 +31,7 @@ type ChatMessageHeaderProps = {
30
31
  * The message header component.
31
32
  */
32
33
  export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
34
+ const trans = useTranslator();
33
35
  const [message, setMessage] = useState<IMessageContent>(
34
36
  props.message.content
35
37
  );
@@ -90,7 +92,9 @@ export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
90
92
  const avatar = message.stacked ? null : Avatar({ user: sender });
91
93
 
92
94
  const name =
93
- sender.display_name ?? sender.name ?? (sender.username || 'User undefined');
95
+ sender.display_name ??
96
+ sender.name ??
97
+ (sender.username || trans.__('User undefined'));
94
98
 
95
99
  // Don't render header for stacked messages not deleted or edited.
96
100
  return message.stacked && !message.deleted && !message.edited ? (
@@ -136,7 +140,9 @@ export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
136
140
  fontSize: 'var(--jp-content-font-size0)'
137
141
  }}
138
142
  >
139
- {message.deleted ? '(message deleted)' : '(edited)'}
143
+ {message.deleted
144
+ ? trans.__('(message deleted)')
145
+ : trans.__('(edited)')}
140
146
  </Typography>
141
147
  )}
142
148
  </Box>
@@ -148,7 +154,7 @@ export function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element {
148
154
  color: 'var(--jp-ui-font-color2)',
149
155
  fontWeight: 300
150
156
  }}
151
- title={message.raw_time ? 'Unverified time' : ''}
157
+ title={message.raw_time ? trans.__('Unverified time') : ''}
152
158
  >
153
159
  {`${datetime[message.time]}${message.raw_time ? '*' : ''}`}
154
160
  </Typography>
@@ -68,25 +68,10 @@ function MessageRendererBase(props: MessageRendererProps): JSX.Element {
68
68
  let mimeModel: IRenderMime.IMimeModel;
69
69
 
70
70
  // Create the renderer and the mime model.
71
- if (typeof message.body === 'string') {
72
- // Allow editing content for text messages.
73
- setCanEdit(true);
74
-
75
- // Improve users display in markdown content.
76
- let mdStr = message.body;
77
- message.mentions?.forEach(user => {
78
- mdStr = replaceMentionToSpan(mdStr, user);
79
- });
80
-
81
- // Body is a string, use the markdown renderer.
82
- renderer = rmRegistry.createRenderer(DEFAULT_MIME_TYPE);
83
- mimeModel = rmRegistry.createModel({
84
- data: { [DEFAULT_MIME_TYPE]: mdStr }
85
- });
86
- } else {
71
+ if (message.mime_model) {
87
72
  setCanEdit(false);
88
73
  // This is a mime bundle.
89
- let mimeContent = message.body;
74
+ let mimeContent = message.mime_model;
90
75
  let preferred = rmRegistry.preferredMimeType(
91
76
  mimeContent.data,
92
77
  'ensure' // Should be changed with 'prefer' if we can handle trusted content.
@@ -121,6 +106,21 @@ function MessageRendererBase(props: MessageRendererProps): JSX.Element {
121
106
  }
122
107
 
123
108
  mimeModel = rmRegistry.createModel(mimeContent);
109
+ } else {
110
+ // Allow editing content for text messages.
111
+ setCanEdit(true);
112
+
113
+ // Improve users display in markdown content.
114
+ let mdStr = message.body;
115
+ message.mentions?.forEach(user => {
116
+ mdStr = replaceMentionToSpan(mdStr, user);
117
+ });
118
+
119
+ // Body is a string, use the markdown renderer.
120
+ renderer = rmRegistry.createRenderer(DEFAULT_MIME_TYPE);
121
+ mimeModel = rmRegistry.createModel({
122
+ data: { [DEFAULT_MIME_TYPE]: mdStr }
123
+ });
124
124
  }
125
125
  await renderer.renderModel(mimeModel);
126
126
 
@@ -11,7 +11,7 @@ import {
11
11
  } from '@jupyterlab/ui-components';
12
12
  import React, { useEffect, useState } from 'react';
13
13
 
14
- import { useChatContext } from '../../context';
14
+ import { useChatContext, useTranslator } from '../../context';
15
15
  import { IChatModel } from '../../model';
16
16
 
17
17
  const NAVIGATION_BUTTON_CLASS = 'jp-chat-navigation';
@@ -38,6 +38,7 @@ type NavigationProps = {
38
38
  */
39
39
  export function Navigation(props: NavigationProps): JSX.Element {
40
40
  const { model } = useChatContext();
41
+ const trans = useTranslator();
41
42
  const [lastInViewport, setLastInViewport] = useState<boolean>(true);
42
43
  const [unreadBefore, setUnreadBefore] = useState<number | null>(null);
43
44
  const [unreadAfter, setUnreadAfter] = useState<number | null>(null);
@@ -132,7 +133,7 @@ export function Navigation(props: NavigationProps): JSX.Element {
132
133
  <Button
133
134
  className={`${NAVIGATION_BUTTON_CLASS} ${NAVIGATION_UNREAD_CLASS} ${NAVIGATION_TOP_CLASS}`}
134
135
  onClick={() => gotoMessage!(unreadBefore)}
135
- title={'Go to unread messages'}
136
+ title={trans.__('Go to unread messages')}
136
137
  >
137
138
  <LabIcon.resolveReact
138
139
  display={'flex'}
@@ -151,8 +152,8 @@ export function Navigation(props: NavigationProps): JSX.Element {
151
152
  }
152
153
  title={
153
154
  unreadAfter !== null
154
- ? 'Go to unread messages'
155
- : 'Go to last message'
155
+ ? trans.__('Go to unread messages')
156
+ : trans.__('Go to last message')
156
157
  }
157
158
  >
158
159
  <LabIcon.resolveReact
@@ -9,6 +9,7 @@ import { Box } from '@mui/material';
9
9
  import React from 'react';
10
10
 
11
11
  import { TooltippedIconButton } from '../mui-extras';
12
+ import { useTranslator } from '../../context';
12
13
 
13
14
  const TOOLBAR_CLASS = 'jp-chat-toolbar';
14
15
 
@@ -16,14 +17,15 @@ const TOOLBAR_CLASS = 'jp-chat-toolbar';
16
17
  * The toolbar attached to a message.
17
18
  */
18
19
  export function MessageToolbar(props: MessageToolbar.IProps): JSX.Element {
20
+ const trans = useTranslator();
19
21
  const buttons: JSX.Element[] = [];
20
22
 
21
23
  if (props.edit !== undefined) {
22
24
  const editButton = (
23
25
  <TooltippedIconButton
24
- tooltip={'Edit'}
26
+ tooltip={trans.__('Edit')}
25
27
  onClick={props.edit}
26
- aria-label={'Edit'}
28
+ aria-label={trans.__('Edit')}
27
29
  inputToolbar={false}
28
30
  >
29
31
  <EditIcon />
@@ -34,9 +36,9 @@ export function MessageToolbar(props: MessageToolbar.IProps): JSX.Element {
34
36
  if (props.delete !== undefined) {
35
37
  const deleteButton = (
36
38
  <TooltippedIconButton
37
- tooltip={'Delete'}
39
+ tooltip={trans.__('Delete')}
38
40
  onClick={props.delete}
39
- aria-label={'Delete'}
41
+ aria-label={trans.__('Delete')}
40
42
  inputToolbar={false}
41
43
  >
42
44
  <DeleteIcon />
@@ -3,9 +3,11 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
 
6
+ import { TranslationBundle } from '@jupyterlab/translation';
6
7
  import { Box, SxProps, Theme, Typography } from '@mui/material';
7
8
  import React from 'react';
8
9
 
10
+ import { useTranslator } from '../context';
9
11
  import { IChatModel } from '../model';
10
12
 
11
13
  /**
@@ -31,23 +33,30 @@ export interface IInputWritingIndicatorProps {
31
33
  * Format the writers list into a readable string.
32
34
  * Examples: "Alice is typing...", "Alice and Bob are typing...", "Alice, Bob, and Carol are typing..."
33
35
  */
34
- function formatWritersText(writers: IChatModel.IWriter[]): string {
36
+ function formatWritersText(
37
+ writers: IChatModel.IWriter[],
38
+ trans: TranslationBundle
39
+ ): string {
35
40
  if (writers.length === 0) {
36
41
  return '';
37
42
  }
38
43
 
39
44
  const names = writers.map(
40
- w => w.user.display_name ?? w.user.name ?? w.user.username ?? 'Unknown'
45
+ w =>
46
+ w.user.display_name ??
47
+ w.user.name ??
48
+ w.user.username ??
49
+ trans.__('Unknown')
41
50
  );
42
51
 
43
52
  if (names.length === 1) {
44
- return `${names[0]} is typing...`;
53
+ return trans.__('%1 is typing...', names[0]);
45
54
  } else if (names.length === 2) {
46
- return `${names[0]} and ${names[1]} are typing...`;
55
+ return trans.__('%1 and %2 are typing...', names[0], names[1]);
47
56
  } else {
48
57
  const allButLast = names.slice(0, -1).join(', ');
49
58
  const last = names[names.length - 1];
50
- return `${allButLast}, and ${last} are typing...`;
59
+ return trans.__('%1, and %2 are typing...', allButLast, last);
51
60
  }
52
61
  }
53
62
 
@@ -58,9 +67,11 @@ export function WritingIndicator(
58
67
  props: IInputWritingIndicatorProps
59
68
  ): JSX.Element {
60
69
  const { writers } = props;
70
+ const trans = useTranslator();
61
71
 
62
72
  // Always render the container to reserve space, even if no writers
63
- const writersText = writers.length > 0 ? formatWritersText(writers) : '';
73
+ const writersText =
74
+ writers.length > 0 ? formatWritersText(writers, trans) : '';
64
75
 
65
76
  return (
66
77
  <Box
package/src/context.ts CHANGED
@@ -3,10 +3,13 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
 
6
+ import { nullTranslator, TranslationBundle } from '@jupyterlab/translation';
6
7
  import { createContext, useContext } from 'react';
7
8
 
8
9
  import { Chat } from './components';
9
10
 
11
+ export const TRANSLATION_DOMAIN = 'jupyter-chat';
12
+
10
13
  export const ChatReactContext = createContext<Chat.IChatProps | undefined>(
11
14
  undefined
12
15
  );
@@ -18,3 +21,13 @@ export function useChatContext(): Chat.IChatProps {
18
21
  }
19
22
  return context;
20
23
  }
24
+
25
+ /**
26
+ * Hook to get the translation bundle for the chat.
27
+ * Must be used within a ChatReactContext.Provider.
28
+ */
29
+ export function useTranslator(): TranslationBundle {
30
+ const context = useContext(ChatReactContext);
31
+ const translator = context?.translator ?? nullTranslator;
32
+ return translator.load(TRANSLATION_DOMAIN);
33
+ }
package/src/message.ts CHANGED
@@ -39,7 +39,7 @@ export class Message implements IMessage {
39
39
  get type(): string {
40
40
  return this._content.type;
41
41
  }
42
- get body(): string | IMimeModelBody {
42
+ get body(): string {
43
43
  return this._content.body;
44
44
  }
45
45
  get id(): string {
@@ -72,6 +72,9 @@ export class Message implements IMessage {
72
72
  get metadata(): IMessageMetadata | undefined {
73
73
  return this._content.metadata;
74
74
  }
75
+ get mime_model(): IMimeModelBody | undefined {
76
+ return this._content.mime_model;
77
+ }
75
78
 
76
79
  /**
77
80
  * A signal emitting when the message has been updated.