@jupyter/chat 0.13.0 → 0.15.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 (96) hide show
  1. package/lib/active-cell-manager.d.ts +2 -0
  2. package/lib/active-cell-manager.js +7 -2
  3. package/lib/components/avatar.d.ts +20 -0
  4. package/lib/components/avatar.js +29 -0
  5. package/lib/components/chat.d.ts +1 -3
  6. package/lib/components/chat.js +2 -3
  7. package/lib/components/index.d.ts +2 -3
  8. package/lib/components/index.js +2 -3
  9. package/lib/components/input/buttons/send-button.js +15 -5
  10. package/lib/components/{chat-input.d.ts → input/chat-input.d.ts} +3 -3
  11. package/lib/components/{chat-input.js → input/chat-input.js} +8 -5
  12. package/lib/components/input/index.d.ts +1 -0
  13. package/lib/components/input/index.js +1 -0
  14. package/lib/components/input/toolbar-registry.d.ts +6 -0
  15. package/lib/components/input/use-chat-commands.d.ts +1 -1
  16. package/lib/components/input/use-chat-commands.js +32 -13
  17. package/lib/components/messages/footer.d.ts +2 -2
  18. package/lib/components/messages/footer.js +1 -1
  19. package/lib/components/messages/header.d.ts +16 -0
  20. package/lib/components/messages/header.js +85 -0
  21. package/lib/components/messages/index.d.ts +9 -0
  22. package/lib/components/messages/index.js +13 -0
  23. package/lib/components/messages/message-renderer.js +1 -1
  24. package/lib/components/messages/message.d.ts +21 -0
  25. package/lib/components/messages/message.js +102 -0
  26. package/lib/components/messages/messages.d.ts +38 -0
  27. package/lib/components/messages/messages.js +139 -0
  28. package/lib/components/messages/navigation.d.ts +20 -0
  29. package/lib/components/messages/navigation.js +98 -0
  30. package/lib/components/messages/writers.d.ts +16 -0
  31. package/lib/components/messages/writers.js +39 -0
  32. package/lib/context.d.ts +1 -1
  33. package/lib/index.d.ts +2 -6
  34. package/lib/index.js +2 -6
  35. package/lib/input-model.js +30 -3
  36. package/lib/{registry.d.ts → registers/attachment-openers.d.ts} +1 -1
  37. package/lib/registers/chat-commands.d.ts +108 -0
  38. package/lib/{chat-commands/registry.js → registers/chat-commands.js} +8 -8
  39. package/lib/{footers/registry.d.ts → registers/footers.d.ts} +30 -5
  40. package/lib/registers/index.d.ts +3 -0
  41. package/lib/{footers → registers}/index.js +3 -2
  42. package/lib/selection-watcher.d.ts +11 -1
  43. package/lib/selection-watcher.js +10 -4
  44. package/lib/types.d.ts +7 -2
  45. package/lib/utils.js +8 -6
  46. package/lib/widgets/index.d.ts +3 -0
  47. package/lib/{chat-commands → widgets}/index.js +3 -2
  48. package/package.json +3 -1
  49. package/src/active-cell-manager.ts +10 -1
  50. package/src/components/avatar.tsx +68 -0
  51. package/src/components/chat.tsx +11 -6
  52. package/src/components/index.ts +2 -3
  53. package/src/components/input/buttons/send-button.tsx +17 -5
  54. package/src/components/{chat-input.tsx → input/chat-input.tsx} +13 -8
  55. package/src/components/input/index.ts +1 -0
  56. package/src/components/input/toolbar-registry.tsx +6 -0
  57. package/src/components/input/use-chat-commands.tsx +39 -16
  58. package/src/components/messages/footer.tsx +5 -2
  59. package/src/components/messages/header.tsx +133 -0
  60. package/src/components/messages/index.ts +14 -0
  61. package/src/components/messages/message-renderer.tsx +1 -1
  62. package/src/components/messages/message.tsx +156 -0
  63. package/src/components/messages/messages.tsx +218 -0
  64. package/src/components/messages/navigation.tsx +167 -0
  65. package/src/components/messages/welcome.tsx +1 -0
  66. package/src/components/messages/writers.tsx +81 -0
  67. package/src/context.ts +1 -1
  68. package/src/index.ts +2 -6
  69. package/src/input-model.ts +33 -4
  70. package/src/{registry.ts → registers/attachment-openers.ts} +2 -1
  71. package/src/registers/chat-commands.ts +142 -0
  72. package/src/{footers/registry.ts → registers/footers.ts} +35 -8
  73. package/src/{footers → registers}/index.ts +3 -2
  74. package/src/selection-watcher.ts +28 -5
  75. package/src/types.ts +7 -2
  76. package/src/utils.ts +8 -6
  77. package/src/{chat-commands → widgets}/index.ts +3 -2
  78. package/style/chat.css +82 -0
  79. package/lib/chat-commands/index.d.ts +0 -2
  80. package/lib/chat-commands/registry.d.ts +0 -28
  81. package/lib/chat-commands/types.d.ts +0 -52
  82. package/lib/chat-commands/types.js +0 -5
  83. package/lib/components/chat-messages.d.ts +0 -119
  84. package/lib/components/chat-messages.js +0 -446
  85. package/lib/footers/index.d.ts +0 -2
  86. package/lib/footers/types.d.ts +0 -26
  87. package/lib/footers/types.js +0 -5
  88. package/src/chat-commands/registry.ts +0 -60
  89. package/src/chat-commands/types.ts +0 -67
  90. package/src/components/chat-messages.tsx +0 -739
  91. package/src/footers/types.ts +0 -33
  92. package/lib/components/{toolbar.d.ts → messages/toolbar.d.ts} +0 -0
  93. package/lib/components/{toolbar.js → messages/toolbar.js} +0 -0
  94. package/lib/{registry.js → registers/attachment-openers.js} +0 -0
  95. package/lib/{footers/registry.js → registers/footers.js} +4 -4
  96. /package/src/components/{toolbar.tsx → messages/toolbar.tsx} +0 -0
@@ -3,6 +3,11 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
  import { Token } from '@lumino/coreutils';
6
+ /**
7
+ * The token for the chat command registry, which can be provided by an extension
8
+ * using @jupyter/chat package.
9
+ */
10
+ export const IChatCommandRegistry = new Token('@jupyter/chat:IChatCommandRegistry');
6
11
  /**
7
12
  * Default chat command registry implementation.
8
13
  */
@@ -16,14 +21,9 @@ export class ChatCommandRegistry {
16
21
  getProviders() {
17
22
  return Array.from(this._providers.values());
18
23
  }
19
- handleChatCommand(command, inputModel) {
20
- const provider = this._providers.get(command.providerId);
21
- if (!provider) {
22
- console.error('Error in handling chat command: No command provider has an ID of ' +
23
- command.providerId);
24
- return;
24
+ async onSubmit(inputModel) {
25
+ for (const provider of this._providers.values()) {
26
+ await provider.onSubmit(inputModel);
25
27
  }
26
- provider.handleChatCommand(command, inputModel);
27
28
  }
28
29
  }
29
- export const IChatCommandRegistry = new Token('@jupyter/chat:IChatCommandRegistry');
@@ -1,5 +1,11 @@
1
+ /// <reference types="react" />
1
2
  import { Token } from '@lumino/coreutils';
2
- import { MessageFooter, MessageFooterSection } from './types';
3
+ import { IChatModel } from '../model';
4
+ import { IChatMessage } from '../types';
5
+ /**
6
+ * The token providing the chat footer registry.
7
+ */
8
+ export declare const IMessageFooterRegistry: Token<IMessageFooterRegistry>;
3
9
  /**
4
10
  * The interface of a registry to provide chat footer.
5
11
  */
@@ -15,6 +21,29 @@ export interface IMessageFooterRegistry {
15
21
  */
16
22
  addSection(section: MessageFooterSection): void;
17
23
  }
24
+ /**
25
+ * The props sent passed to each `MessageFooterSection` React component.
26
+ */
27
+ export type MessageFooterSectionProps = {
28
+ model: IChatModel;
29
+ message: IChatMessage;
30
+ };
31
+ /**
32
+ * A message footer section which can be added to the footer registry.
33
+ */
34
+ export type MessageFooterSection = {
35
+ component: React.FC<MessageFooterSectionProps>;
36
+ position: 'left' | 'center' | 'right';
37
+ };
38
+ /**
39
+ * The message footer returned by the registry, composed of 'left', 'center',
40
+ * and 'right' sections.
41
+ */
42
+ export type MessageFooter = {
43
+ left?: MessageFooterSection;
44
+ center?: MessageFooterSection;
45
+ right?: MessageFooterSection;
46
+ };
18
47
  /**
19
48
  * The default implementation of the message footer registry.
20
49
  */
@@ -30,7 +59,3 @@ export declare class MessageFooterRegistry implements IMessageFooterRegistry {
30
59
  addSection(footer: MessageFooterSection): void;
31
60
  private _footers;
32
61
  }
33
- /**
34
- * The token providing the chat footer registry.
35
- */
36
- export declare const IMessageFooterRegistry: Token<IMessageFooterRegistry>;
@@ -0,0 +1,3 @@
1
+ export * from './attachment-openers';
2
+ export * from './chat-commands';
3
+ export * from './footers';
@@ -2,5 +2,6 @@
2
2
  * Copyright (c) Jupyter Development Team.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
- export * from './registry';
6
- export * from './types';
5
+ export * from './attachment-openers';
6
+ export * from './chat-commands';
7
+ export * from './footers';
@@ -1,5 +1,6 @@
1
1
  import { JupyterFrontEnd } from '@jupyterlab/application';
2
2
  import { CodeEditor } from '@jupyterlab/codeeditor';
3
+ import { IEditorLanguageRegistry } from '@jupyterlab/codemirror';
3
4
  import { Widget } from '@lumino/widgets';
4
5
  import { ISignal, Signal } from '@lumino/signaling';
5
6
  /**
@@ -14,6 +15,10 @@ export declare namespace SelectionWatcher {
14
15
  * The current shell of the application.
15
16
  */
16
17
  shell: JupyterFrontEnd.IShell;
18
+ /**
19
+ * Editor language registry.
20
+ */
21
+ languages?: IEditorLanguageRegistry;
17
22
  }
18
23
  /**
19
24
  * The selection type.
@@ -31,6 +36,10 @@ export declare namespace SelectionWatcher {
31
36
  * The ID of the document widget in which the selection was made.
32
37
  */
33
38
  widgetId: string;
39
+ /**
40
+ * The language of the selection.
41
+ */
42
+ language?: string;
34
43
  /**
35
44
  * The ID of the cell in which the selection was made, if the original widget
36
45
  * was a notebook.
@@ -54,9 +63,10 @@ export declare class SelectionWatcher {
54
63
  get selection(): SelectionWatcher.Selection | null;
55
64
  get selectionChanged(): ISignal<this, SelectionWatcher.Selection | null>;
56
65
  replaceSelection(selection: SelectionWatcher.Selection): void;
57
- protected _poll(): void;
66
+ protected _poll(): Promise<void>;
58
67
  protected _shell: JupyterFrontEnd.IShell;
59
68
  protected _mainAreaDocumentWidget: Widget | null;
60
69
  protected _selection: SelectionWatcher.Selection | null;
61
70
  protected _selectionChanged: Signal<this, SelectionWatcher.Selection | null>;
71
+ private _languages;
62
72
  }
@@ -3,6 +3,7 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
  import { DocumentWidget } from '@jupyterlab/docregistry';
6
+ import { EditorLanguageRegistry } from '@jupyterlab/codemirror';
6
7
  import { Notebook } from '@jupyterlab/notebook';
7
8
  import { find } from '@lumino/algorithm';
8
9
  import { Signal } from '@lumino/signaling';
@@ -17,6 +18,7 @@ export class SelectionWatcher {
17
18
  this._selection = null;
18
19
  this._selectionChanged = new Signal(this);
19
20
  this._shell = options.shell;
21
+ this._languages = options.languages || new EditorLanguageRegistry();
20
22
  (_a = this._shell.currentChanged) === null || _a === void 0 ? void 0 : _a.connect((sender, args) => {
21
23
  var _a;
22
24
  // Do not change the main area widget if the new one has no editor, for example
@@ -76,13 +78,13 @@ export class SelectionWatcher {
76
78
  const newPosition = editor.getPositionAt(editor.getOffsetAt(selection.start) + selection.text.length);
77
79
  editor.setSelection({ start: newPosition, end: newPosition });
78
80
  }
79
- _poll() {
81
+ async _poll() {
80
82
  var _a;
81
83
  let currSelection = null;
82
84
  const prevSelection = this._selection;
83
85
  // Do not return selected text if the main area widget is hidden.
84
86
  if ((_a = this._mainAreaDocumentWidget) === null || _a === void 0 ? void 0 : _a.isVisible) {
85
- currSelection = getTextSelection(this._mainAreaDocumentWidget);
87
+ currSelection = await getTextSelection(this._mainAreaDocumentWidget, this._languages);
86
88
  }
87
89
  if ((prevSelection === null || prevSelection === void 0 ? void 0 : prevSelection.text) !== (currSelection === null || currSelection === void 0 ? void 0 : currSelection.text)) {
88
90
  this._selection = currSelection;
@@ -93,8 +95,8 @@ export class SelectionWatcher {
93
95
  /**
94
96
  * Gets a Selection object from a document widget. Returns `null` if unable.
95
97
  */
96
- function getTextSelection(widget) {
97
- var _a;
98
+ async function getTextSelection(widget, languages) {
99
+ var _a, _b;
98
100
  const editor = getEditor(widget);
99
101
  // widget type check is redundant but hints the type to TypeScript
100
102
  if (!editor || !(widget instanceof DocumentWidget)) {
@@ -120,6 +122,7 @@ function getTextSelection(widget) {
120
122
  if (startOffset > endOffset) {
121
123
  [start, end] = [end, start];
122
124
  }
125
+ const language = (_b = (await languages.getLanguage(editor === null || editor === void 0 ? void 0 : editor.model.mimeType))) === null || _b === void 0 ? void 0 : _b.name;
123
126
  return {
124
127
  ...selectionObj,
125
128
  start,
@@ -127,6 +130,9 @@ function getTextSelection(widget) {
127
130
  text,
128
131
  numLines: text.split('\n').length,
129
132
  widgetId: widget.id,
133
+ ...(language && {
134
+ language
135
+ }),
130
136
  ...(cellId && {
131
137
  cellId
132
138
  })
package/lib/types.d.ts CHANGED
@@ -9,9 +9,14 @@ export interface IUser {
9
9
  color?: string;
10
10
  avatar_url?: string;
11
11
  /**
12
- * The string to use to mention a user in the chat.
12
+ * The string to use to mention a user in the chat. This is computed via the
13
+ * following procedure:
14
+ *
15
+ * 1. Let `mention_name = user.display_name || user.name || user.username`.
16
+ *
17
+ * 2. Replace each ' ' character with '-' in `mention_name`.
13
18
  */
14
- mention_name?: string;
19
+ mention_name: string;
15
20
  /**
16
21
  * Boolean identifying if user is a bot.
17
22
  */
package/lib/utils.js CHANGED
@@ -46,9 +46,10 @@ export function replaceMentionToSpan(content, user) {
46
46
  if (!user.mention_name) {
47
47
  return content;
48
48
  }
49
- const regex = new RegExp(user.mention_name, 'g');
50
- const mention = `<span class="${MENTION_CLASS}">${user.mention_name}</span>`;
51
- return content.replace(regex, mention);
49
+ const mention = '@' + user.mention_name;
50
+ const regex = new RegExp(mention, 'g');
51
+ const mentionEl = `<span class="${MENTION_CLASS}">${mention}</span>`;
52
+ return content.replace(regex, mentionEl);
52
53
  }
53
54
  /**
54
55
  * Replace a span to a mentioned to user string (@someone).
@@ -60,7 +61,8 @@ export function replaceSpanToMention(content, user) {
60
61
  if (!user.mention_name) {
61
62
  return content;
62
63
  }
63
- const span = `<span class="${MENTION_CLASS}">${user.mention_name}</span>`;
64
- const regex = new RegExp(span, 'g');
65
- return content.replace(regex, user.mention_name);
64
+ const mention = '@' + user.mention_name;
65
+ const mentionEl = `<span class="${MENTION_CLASS}">${mention}</span>`;
66
+ const regex = new RegExp(mentionEl, 'g');
67
+ return content.replace(regex, mention);
66
68
  }
@@ -0,0 +1,3 @@
1
+ export * from './chat-error';
2
+ export * from './chat-sidebar';
3
+ export * from './chat-widget';
@@ -2,5 +2,6 @@
2
2
  * Copyright (c) Jupyter Development Team.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
- export * from './types';
6
- export * from './registry';
5
+ export * from './chat-error';
6
+ export * from './chat-sidebar';
7
+ export * from './chat-widget';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jupyter/chat",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
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",
@@ -48,6 +48,8 @@
48
48
  "@jupyter/react-components": "^0.15.2",
49
49
  "@jupyterlab/application": "^4.2.0",
50
50
  "@jupyterlab/apputils": "^4.3.0",
51
+ "@jupyterlab/codeeditor": "^4.2.0",
52
+ "@jupyterlab/codemirror": "^4.2.0",
51
53
  "@jupyterlab/docmanager": "^4.2.0",
52
54
  "@jupyterlab/filebrowser": "^4.2.0",
53
55
  "@jupyterlab/fileeditor": "^4.2.0",
@@ -13,11 +13,13 @@ import { ISignal, Signal } from '@lumino/signaling';
13
13
  type CellContent = {
14
14
  type: string;
15
15
  source: string;
16
+ language?: string;
16
17
  };
17
18
 
18
19
  type CellWithErrorContent = {
19
20
  type: 'code';
20
21
  source: string;
22
+ language?: string;
21
23
  error: {
22
24
  name: string;
23
25
  value: string;
@@ -148,6 +150,11 @@ export class ActiveCellManager implements IActiveCellManager {
148
150
  getContent(withError: true): CellWithErrorContent | null;
149
151
  getContent(withError = false): CellContent | CellWithErrorContent | null {
150
152
  const sharedModel = this._notebookTracker.activeCell?.model.sharedModel;
153
+ const language =
154
+ sharedModel?.cell_type === 'code'
155
+ ? this._notebookTracker.currentWidget?.model?.defaultKernelLanguage
156
+ : undefined;
157
+
151
158
  if (!sharedModel) {
152
159
  return null;
153
160
  }
@@ -156,7 +163,8 @@ export class ActiveCellManager implements IActiveCellManager {
156
163
  if (!withError) {
157
164
  return {
158
165
  type: sharedModel.cell_type,
159
- source: sharedModel.getSource()
166
+ source: sharedModel.getSource(),
167
+ language
160
168
  };
161
169
  }
162
170
 
@@ -166,6 +174,7 @@ export class ActiveCellManager implements IActiveCellManager {
166
174
  return {
167
175
  type: 'code',
168
176
  source: sharedModel.getSource(),
177
+ language,
169
178
  error: {
170
179
  name: error.ename,
171
180
  value: error.evalue,
@@ -0,0 +1,68 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+
6
+ import { Avatar as MuiAvatar, Typography } from '@mui/material';
7
+ import type { SxProps, Theme } from '@mui/material';
8
+ import React from 'react';
9
+
10
+ import { IUser } from '../types';
11
+
12
+ /**
13
+ * The avatar props.
14
+ */
15
+ type AvatarProps = {
16
+ /**
17
+ * The user to display an avatar.
18
+ */
19
+ user: IUser;
20
+ /**
21
+ * Whether the avatar should be small.
22
+ */
23
+ small?: boolean;
24
+ };
25
+
26
+ /**
27
+ * The avatar component.
28
+ */
29
+ export function Avatar(props: AvatarProps): JSX.Element | null {
30
+ const { user } = props;
31
+
32
+ const sharedStyles: SxProps<Theme> = {
33
+ height: `${props.small ? '16' : '24'}px`,
34
+ width: `${props.small ? '16' : '24'}px`,
35
+ bgcolor: user.color,
36
+ fontSize: `var(--jp-ui-font-size${props.small ? '0' : '1'})`
37
+ };
38
+
39
+ const name =
40
+ user.display_name ?? user.name ?? (user.username || 'User undefined');
41
+ return user.avatar_url ? (
42
+ <MuiAvatar
43
+ sx={{
44
+ ...sharedStyles
45
+ }}
46
+ src={user.avatar_url}
47
+ alt={name}
48
+ title={name}
49
+ ></MuiAvatar>
50
+ ) : user.initials ? (
51
+ <MuiAvatar
52
+ sx={{
53
+ ...sharedStyles
54
+ }}
55
+ alt={name}
56
+ title={name}
57
+ >
58
+ <Typography
59
+ sx={{
60
+ fontSize: `var(--jp-ui-font-size${props.small ? '0' : '1'})`,
61
+ color: 'var(--jp-ui-inverse-font-color1)'
62
+ }}
63
+ >
64
+ {user.initials}
65
+ </Typography>
66
+ </MuiAvatar>
67
+ ) : null;
68
+ }
@@ -11,15 +11,20 @@ import { IconButton } from '@mui/material';
11
11
  import { Box } from '@mui/system';
12
12
  import React, { useState } from 'react';
13
13
 
14
+ import {
15
+ ChatInput,
16
+ IInputToolbarRegistry,
17
+ InputToolbarRegistry
18
+ } from './input';
14
19
  import { JlThemeProvider } from './jl-theme-provider';
15
- import { IChatCommandRegistry } from '../chat-commands';
16
- import { ChatMessages } from './chat-messages';
17
- import { ChatInput } from './chat-input';
18
- import { IInputToolbarRegistry, InputToolbarRegistry } from './input';
20
+ import { ChatMessages } from './messages';
19
21
  import { AttachmentOpenerContext } from '../context';
20
- import { IMessageFooterRegistry } from '../footers';
21
22
  import { IChatModel } from '../model';
22
- import { IAttachmentOpenerRegistry } from '../registry';
23
+ import {
24
+ IAttachmentOpenerRegistry,
25
+ IChatCommandRegistry,
26
+ IMessageFooterRegistry
27
+ } from '../registers';
23
28
 
24
29
  export function ChatBody(props: Chat.IChatBodyProps): JSX.Element {
25
30
  const { model } = props;
@@ -3,12 +3,11 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
 
6
+ export * from './avatar';
6
7
  export * from './chat';
7
- export * from './chat-input';
8
- export * from './chat-messages';
9
8
  export * from './code-blocks';
10
9
  export * from './input';
11
10
  export * from './jl-theme-provider';
11
+ export * from './messages';
12
12
  export * from './mui-extras';
13
13
  export * from './scroll-container';
14
- export * from './toolbar';
@@ -23,7 +23,7 @@ const SEND_INCLUDE_LI_CLASS = 'jp-chat-send-include';
23
23
  export function SendButton(
24
24
  props: InputToolbarRegistry.IToolbarItemProps
25
25
  ): JSX.Element {
26
- const { model } = props;
26
+ const { model, chatCommandRegistry } = props;
27
27
  const { activeCellManager, selectionWatcher } = model;
28
28
  const hideIncludeSelection = !activeCellManager || !selectionWatcher;
29
29
 
@@ -98,21 +98,33 @@ export function SendButton(
98
98
  };
99
99
  }, [activeCellManager, selectionWatcher]);
100
100
 
101
- function sendWithSelection() {
101
+ async function send() {
102
+ await chatCommandRegistry?.onSubmit(model);
103
+ model.send(model.value);
104
+ }
105
+
106
+ async function sendWithSelection() {
102
107
  let source = '';
103
108
 
109
+ // Run all chat command providers
110
+ await chatCommandRegistry?.onSubmit(model);
111
+
112
+ let language: string | undefined;
104
113
  if (selectionWatcher?.selection) {
105
114
  // Append the selected text if exists.
106
115
  source = selectionWatcher.selection.text;
116
+ language = selectionWatcher.selection.language;
107
117
  } else if (activeCellManager?.available) {
108
118
  // Append the active cell content if exists.
109
- source = activeCellManager.getContent(false)!.source;
119
+ const content = activeCellManager.getContent(false);
120
+ source = content!.source;
121
+ language = content?.language;
110
122
  }
111
123
  let content = model.value;
112
124
  if (source) {
113
125
  content += `
114
126
 
115
- \`\`\`
127
+ \`\`\`${language ?? ''}
116
128
  ${source}
117
129
  \`\`\`
118
130
  `;
@@ -125,7 +137,7 @@ ${source}
125
137
  return (
126
138
  <>
127
139
  <TooltippedButton
128
- onClick={() => model.send(model.value)}
140
+ onClick={send}
129
141
  disabled={disabled}
130
142
  tooltip={tooltip}
131
143
  buttonProps={{
@@ -15,15 +15,15 @@ import {
15
15
  import clsx from 'clsx';
16
16
  import React, { useEffect, useRef, useState } from 'react';
17
17
 
18
- import { AttachmentPreviewList } from './attachments';
18
+ import { AttachmentPreviewList } from '../attachments';
19
19
  import {
20
20
  IInputToolbarRegistry,
21
21
  InputToolbarRegistry,
22
22
  useChatCommands
23
- } from './input';
24
- import { IInputModel, InputModel } from '../input-model';
25
- import { IChatCommandRegistry } from '../chat-commands';
26
- import { IAttachment } from '../types';
23
+ } from '.';
24
+ import { IInputModel, InputModel } from '../../input-model';
25
+ import { IChatCommandRegistry } from '../../registers';
26
+ import { IAttachment } from '../../types';
27
27
 
28
28
  const INPUT_BOX_CLASS = 'jp-chat-input-container';
29
29
  const INPUT_TOOLBAR_CLASS = 'jp-chat-input-toolbar';
@@ -105,7 +105,7 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
105
105
  * "Enter". This also handles many of the edge cases in the MUI Autocomplete
106
106
  * component.
107
107
  */
108
- function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
108
+ async function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
109
109
  /**
110
110
  * IMPORTANT: This statement ensures that arrow keys can be used to navigate
111
111
  * the multiline input when the chat commands menu is closed.
@@ -157,7 +157,9 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
157
157
  (sendWithShiftEnter && event.shiftKey) ||
158
158
  (!sendWithShiftEnter && !event.shiftKey)
159
159
  ) {
160
- model.send(input);
160
+ // Run all command providers
161
+ await props.chatCommandRegistry?.onSubmit(model);
162
+ model.send(model.value);
161
163
  event.stopPropagation();
162
164
  event.preventDefault();
163
165
  }
@@ -218,7 +220,10 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
218
220
  endAdornment: (
219
221
  <InputAdornment position="end" className={INPUT_TOOLBAR_CLASS}>
220
222
  {toolbarElements.map(item => (
221
- <item.element model={model} />
223
+ <item.element
224
+ model={model}
225
+ chatCommandRegistry={props.chatCommandRegistry}
226
+ />
222
227
  ))}
223
228
  </InputAdornment>
224
229
  )
@@ -4,5 +4,6 @@
4
4
  */
5
5
 
6
6
  export * from './buttons';
7
+ export * from './chat-input';
7
8
  export * from './toolbar-registry';
8
9
  export * from './use-chat-commands';
@@ -7,6 +7,7 @@ import * as React from 'react';
7
7
  import { AttachButton, CancelButton, SendButton } from './buttons';
8
8
  import { IInputModel } from '../../input-model';
9
9
  import { ISignal, Signal } from '@lumino/signaling';
10
+ import { IChatCommandRegistry } from '../../registers';
10
11
 
11
12
  /**
12
13
  * The toolbar registry interface.
@@ -137,6 +138,11 @@ export namespace InputToolbarRegistry {
137
138
  * The input model of the input component including the button.
138
139
  */
139
140
  model: IInputModel;
141
+ /**
142
+ * Chat command registry. Should be used by the "Send" button to run
143
+ * `onSubmit()` on all command providers before sending the message.
144
+ */
145
+ chatCommandRegistry?: IChatCommandRegistry;
140
146
  }
141
147
 
142
148
  /**