@jupyter/chat 0.2.0 → 0.3.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.
- package/lib/active-cell-manager.d.ts +151 -0
- package/lib/active-cell-manager.js +201 -0
- package/lib/components/chat-input.d.ts +5 -4
- package/lib/components/chat-input.js +11 -4
- package/lib/components/chat-messages.js +2 -3
- package/lib/components/chat.js +1 -2
- package/lib/components/code-blocks/code-toolbar.d.ts +13 -0
- package/lib/components/code-blocks/code-toolbar.js +70 -0
- package/lib/components/{copy-button.d.ts → code-blocks/copy-button.d.ts} +1 -0
- package/lib/components/code-blocks/copy-button.js +43 -0
- package/lib/components/mui-extras/contrasting-tooltip.d.ts +6 -0
- package/lib/components/mui-extras/contrasting-tooltip.js +21 -0
- package/lib/components/mui-extras/tooltipped-icon-button.d.ts +35 -0
- package/lib/components/mui-extras/tooltipped-icon-button.js +36 -0
- package/lib/components/rendermime-markdown.d.ts +2 -0
- package/lib/components/rendermime-markdown.js +29 -15
- package/lib/icons.d.ts +1 -0
- package/lib/icons.js +5 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/model.d.ts +20 -0
- package/lib/model.js +14 -1
- package/lib/types.d.ts +4 -0
- package/package.json +6 -4
- package/src/active-cell-manager.ts +318 -0
- package/src/components/chat-input.tsx +17 -8
- package/src/components/chat-messages.tsx +3 -2
- package/src/components/chat.tsx +1 -1
- package/src/components/code-blocks/code-toolbar.tsx +143 -0
- package/src/components/code-blocks/copy-button.tsx +68 -0
- package/src/components/mui-extras/contrasting-tooltip.tsx +27 -0
- package/src/components/mui-extras/tooltipped-icon-button.tsx +84 -0
- package/src/components/rendermime-markdown.tsx +44 -20
- package/src/icons.ts +6 -0
- package/src/index.ts +1 -0
- package/src/model.ts +33 -0
- package/src/types.ts +4 -0
- package/style/icons/replace-cell.svg +8 -0
- package/lib/components/copy-button.js +0 -35
- package/src/components/copy-button.tsx +0 -55
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { JupyterFrontEnd } from '@jupyterlab/application';
|
|
2
|
+
import { INotebookTracker } from '@jupyterlab/notebook';
|
|
3
|
+
import { IError as CellError } from '@jupyterlab/nbformat';
|
|
4
|
+
import { ISignal } from '@lumino/signaling';
|
|
5
|
+
type CellContent = {
|
|
6
|
+
type: string;
|
|
7
|
+
source: string;
|
|
8
|
+
};
|
|
9
|
+
type CellWithErrorContent = {
|
|
10
|
+
type: 'code';
|
|
11
|
+
source: string;
|
|
12
|
+
error: {
|
|
13
|
+
name: string;
|
|
14
|
+
value: string;
|
|
15
|
+
traceback: string[];
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export interface IActiveCellManager {
|
|
19
|
+
/**
|
|
20
|
+
* Whether the notebook is available and an active cell exists.
|
|
21
|
+
*/
|
|
22
|
+
readonly available: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* The `CellError` output within the active cell, if any.
|
|
25
|
+
*/
|
|
26
|
+
readonly activeCellError: CellError | null;
|
|
27
|
+
/**
|
|
28
|
+
* A signal emitting when the active cell changed.
|
|
29
|
+
*/
|
|
30
|
+
readonly availabilityChanged: ISignal<this, boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* A signal emitting when the error state of the active cell changed.
|
|
33
|
+
*/
|
|
34
|
+
readonly activeCellErrorChanged: ISignal<this, CellError | null>;
|
|
35
|
+
/**
|
|
36
|
+
* Returns an `ActiveCellContent` object that describes the current active
|
|
37
|
+
* cell. If no active cell exists, this method returns `null`.
|
|
38
|
+
*
|
|
39
|
+
* When called with `withError = true`, this method returns `null` if the
|
|
40
|
+
* active cell does not have an error output. Otherwise it returns an
|
|
41
|
+
* `ActiveCellContentWithError` object that describes both the active cell and
|
|
42
|
+
* the error output.
|
|
43
|
+
*/
|
|
44
|
+
getContent(withError: boolean): CellContent | CellWithErrorContent | null;
|
|
45
|
+
/**
|
|
46
|
+
* Inserts `content` in a new cell above the active cell.
|
|
47
|
+
*/
|
|
48
|
+
insertAbove(content: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* Inserts `content` in a new cell below the active cell.
|
|
51
|
+
*/
|
|
52
|
+
insertBelow(content: string): void;
|
|
53
|
+
/**
|
|
54
|
+
* Replaces the contents of the active cell.
|
|
55
|
+
*/
|
|
56
|
+
replace(content: string): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The active cell manager namespace.
|
|
60
|
+
*/
|
|
61
|
+
export declare namespace ActiveCellManager {
|
|
62
|
+
/**
|
|
63
|
+
* The constructor options.
|
|
64
|
+
*/
|
|
65
|
+
interface IOptions {
|
|
66
|
+
/**
|
|
67
|
+
* The notebook tracker.
|
|
68
|
+
*/
|
|
69
|
+
tracker: INotebookTracker;
|
|
70
|
+
/**
|
|
71
|
+
* The current shell of the application.
|
|
72
|
+
*/
|
|
73
|
+
shell: JupyterFrontEnd.IShell;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A manager that maintains a reference to the current active notebook cell in
|
|
78
|
+
* the main panel (if any), and provides methods for inserting or appending
|
|
79
|
+
* content to the active cell.
|
|
80
|
+
*
|
|
81
|
+
* The current active cell should be obtained by listening to the
|
|
82
|
+
* `activeCellChanged` signal.
|
|
83
|
+
*/
|
|
84
|
+
export declare class ActiveCellManager implements IActiveCellManager {
|
|
85
|
+
constructor(options: ActiveCellManager.IOptions);
|
|
86
|
+
/**
|
|
87
|
+
* Whether the notebook is available and an active cell exists.
|
|
88
|
+
*/
|
|
89
|
+
get available(): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* The `CellError` output within the active cell, if any.
|
|
92
|
+
*/
|
|
93
|
+
get activeCellError(): CellError | null;
|
|
94
|
+
/**
|
|
95
|
+
* A signal emitting when the active cell changed.
|
|
96
|
+
*/
|
|
97
|
+
get availabilityChanged(): ISignal<this, boolean>;
|
|
98
|
+
/**
|
|
99
|
+
* A signal emitting when the error state of the active cell changed.
|
|
100
|
+
*/
|
|
101
|
+
get activeCellErrorChanged(): ISignal<this, CellError | null>;
|
|
102
|
+
/**
|
|
103
|
+
* Returns an `ActiveCellContent` object that describes the current active
|
|
104
|
+
* cell. If no active cell exists, this method returns `null`.
|
|
105
|
+
*
|
|
106
|
+
* When called with `withError = true`, this method returns `null` if the
|
|
107
|
+
* active cell does not have an error output. Otherwise it returns an
|
|
108
|
+
* `ActiveCellContentWithError` object that describes both the active cell and
|
|
109
|
+
* the error output.
|
|
110
|
+
*/
|
|
111
|
+
getContent(withError: false): CellContent | null;
|
|
112
|
+
getContent(withError: true): CellWithErrorContent | null;
|
|
113
|
+
/**
|
|
114
|
+
* Inserts `content` in a new cell above the active cell.
|
|
115
|
+
*/
|
|
116
|
+
insertAbove(content: string): void;
|
|
117
|
+
/**
|
|
118
|
+
* Inserts `content` in a new cell below the active cell.
|
|
119
|
+
*/
|
|
120
|
+
insertBelow(content: string): void;
|
|
121
|
+
/**
|
|
122
|
+
* Replaces the contents of the active cell.
|
|
123
|
+
*/
|
|
124
|
+
replace(content: string): Promise<void>;
|
|
125
|
+
private _onMainAreaChanged;
|
|
126
|
+
/**
|
|
127
|
+
* Handle the change of active notebook cell.
|
|
128
|
+
*/
|
|
129
|
+
private _onActiveCellChanged;
|
|
130
|
+
/**
|
|
131
|
+
* Handle the change of the active cell state.
|
|
132
|
+
*/
|
|
133
|
+
private _cellStateChange;
|
|
134
|
+
/**
|
|
135
|
+
* The notebook tracker.
|
|
136
|
+
*/
|
|
137
|
+
private _notebookTracker;
|
|
138
|
+
/**
|
|
139
|
+
* Whether the current notebook panel is visible or not.
|
|
140
|
+
*/
|
|
141
|
+
private _notebookVisible;
|
|
142
|
+
/**
|
|
143
|
+
* The active cell.
|
|
144
|
+
*/
|
|
145
|
+
private _activeCell;
|
|
146
|
+
private _available;
|
|
147
|
+
private _activeCellError;
|
|
148
|
+
private _availabilityChanged;
|
|
149
|
+
private _activeCellErrorChanged;
|
|
150
|
+
}
|
|
151
|
+
export {};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { LabShell } from '@jupyterlab/application';
|
|
6
|
+
import { NotebookActions } from '@jupyterlab/notebook';
|
|
7
|
+
import { Signal } from '@lumino/signaling';
|
|
8
|
+
/**
|
|
9
|
+
* A manager that maintains a reference to the current active notebook cell in
|
|
10
|
+
* the main panel (if any), and provides methods for inserting or appending
|
|
11
|
+
* content to the active cell.
|
|
12
|
+
*
|
|
13
|
+
* The current active cell should be obtained by listening to the
|
|
14
|
+
* `activeCellChanged` signal.
|
|
15
|
+
*/
|
|
16
|
+
export class ActiveCellManager {
|
|
17
|
+
constructor(options) {
|
|
18
|
+
var _a, _b;
|
|
19
|
+
this._onMainAreaChanged = () => {
|
|
20
|
+
var _a, _b;
|
|
21
|
+
const value = (_b = (_a = this._notebookTracker.currentWidget) === null || _a === void 0 ? void 0 : _a.isVisible) !== null && _b !== void 0 ? _b : false;
|
|
22
|
+
if (value !== this._notebookVisible) {
|
|
23
|
+
this._notebookVisible = value;
|
|
24
|
+
this._available = !!this._activeCell && this._notebookVisible;
|
|
25
|
+
this._availabilityChanged.emit(this._available);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Handle the change of active notebook cell.
|
|
30
|
+
*/
|
|
31
|
+
this._onActiveCellChanged = (_, activeCell) => {
|
|
32
|
+
var _a;
|
|
33
|
+
if (this._activeCell !== activeCell) {
|
|
34
|
+
(_a = this._activeCell) === null || _a === void 0 ? void 0 : _a.model.stateChanged.disconnect(this._cellStateChange);
|
|
35
|
+
this._activeCell = activeCell;
|
|
36
|
+
activeCell === null || activeCell === void 0 ? void 0 : activeCell.ready.then(() => {
|
|
37
|
+
var _a, _b;
|
|
38
|
+
(_a = this._activeCell) === null || _a === void 0 ? void 0 : _a.model.stateChanged.connect(this._cellStateChange);
|
|
39
|
+
this._available = !!this._activeCell && this._notebookVisible;
|
|
40
|
+
this._availabilityChanged.emit(this._available);
|
|
41
|
+
(_b = this._activeCell) === null || _b === void 0 ? void 0 : _b.disposed.connect(() => {
|
|
42
|
+
this._activeCell = null;
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Handle the change of the active cell state.
|
|
49
|
+
*/
|
|
50
|
+
this._cellStateChange = (_, change) => {
|
|
51
|
+
var _a;
|
|
52
|
+
if (change.name === 'executionCount') {
|
|
53
|
+
const currSharedModel = (_a = this._activeCell) === null || _a === void 0 ? void 0 : _a.model.sharedModel;
|
|
54
|
+
const prevActiveCellError = this._activeCellError;
|
|
55
|
+
let currActiveCellError = null;
|
|
56
|
+
if (currSharedModel && 'outputs' in currSharedModel) {
|
|
57
|
+
currActiveCellError =
|
|
58
|
+
currSharedModel.outputs.find((output) => output.output_type === 'error') || null;
|
|
59
|
+
}
|
|
60
|
+
// for some reason, the `CellError` object is not referentially stable,
|
|
61
|
+
// meaning that this condition always evaluates to `true` and the
|
|
62
|
+
// `activeCellErrorChanged` signal is emitted every 200ms, even when the
|
|
63
|
+
// error output is unchanged. this is why we have to rely on
|
|
64
|
+
// `execution_count` to track changes to the error output.
|
|
65
|
+
if (prevActiveCellError !== currActiveCellError) {
|
|
66
|
+
this._activeCellError = currActiveCellError;
|
|
67
|
+
this._activeCellErrorChanged.emit(this._activeCellError);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Whether the current notebook panel is visible or not.
|
|
73
|
+
*/
|
|
74
|
+
this._notebookVisible = false;
|
|
75
|
+
/**
|
|
76
|
+
* The active cell.
|
|
77
|
+
*/
|
|
78
|
+
this._activeCell = null;
|
|
79
|
+
this._available = false;
|
|
80
|
+
this._activeCellError = null;
|
|
81
|
+
this._availabilityChanged = new Signal(this);
|
|
82
|
+
this._activeCellErrorChanged = new Signal(this);
|
|
83
|
+
this._notebookTracker = options.tracker;
|
|
84
|
+
this._notebookTracker.activeCellChanged.connect(this._onActiveCellChanged);
|
|
85
|
+
(_a = options.shell.currentChanged) === null || _a === void 0 ? void 0 : _a.connect(this._onMainAreaChanged);
|
|
86
|
+
if (options.shell instanceof LabShell) {
|
|
87
|
+
(_b = options.shell.layoutModified) === null || _b === void 0 ? void 0 : _b.connect(this._onMainAreaChanged);
|
|
88
|
+
}
|
|
89
|
+
this._onMainAreaChanged();
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Whether the notebook is available and an active cell exists.
|
|
93
|
+
*/
|
|
94
|
+
get available() {
|
|
95
|
+
return this._available;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The `CellError` output within the active cell, if any.
|
|
99
|
+
*/
|
|
100
|
+
get activeCellError() {
|
|
101
|
+
return this._activeCellError;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* A signal emitting when the active cell changed.
|
|
105
|
+
*/
|
|
106
|
+
get availabilityChanged() {
|
|
107
|
+
return this._availabilityChanged;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* A signal emitting when the error state of the active cell changed.
|
|
111
|
+
*/
|
|
112
|
+
get activeCellErrorChanged() {
|
|
113
|
+
return this._activeCellErrorChanged;
|
|
114
|
+
}
|
|
115
|
+
getContent(withError = false) {
|
|
116
|
+
var _a;
|
|
117
|
+
const sharedModel = (_a = this._notebookTracker.activeCell) === null || _a === void 0 ? void 0 : _a.model.sharedModel;
|
|
118
|
+
if (!sharedModel) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
// case where withError = false
|
|
122
|
+
if (!withError) {
|
|
123
|
+
return {
|
|
124
|
+
type: sharedModel.cell_type,
|
|
125
|
+
source: sharedModel.getSource()
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// case where withError = true
|
|
129
|
+
const error = this._activeCellError;
|
|
130
|
+
if (error) {
|
|
131
|
+
return {
|
|
132
|
+
type: 'code',
|
|
133
|
+
source: sharedModel.getSource(),
|
|
134
|
+
error: {
|
|
135
|
+
name: error.ename,
|
|
136
|
+
value: error.evalue,
|
|
137
|
+
traceback: error.traceback
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Inserts `content` in a new cell above the active cell.
|
|
145
|
+
*/
|
|
146
|
+
insertAbove(content) {
|
|
147
|
+
const notebookPanel = this._notebookTracker.currentWidget;
|
|
148
|
+
if (!notebookPanel || !notebookPanel.isVisible) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// create a new cell above the active cell and mark new cell as active
|
|
152
|
+
NotebookActions.insertAbove(notebookPanel.content);
|
|
153
|
+
// replace content of this new active cell
|
|
154
|
+
this.replace(content);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Inserts `content` in a new cell below the active cell.
|
|
158
|
+
*/
|
|
159
|
+
insertBelow(content) {
|
|
160
|
+
const notebookPanel = this._notebookTracker.currentWidget;
|
|
161
|
+
if (!notebookPanel || !notebookPanel.isVisible) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
// create a new cell below the active cell and mark new cell as active
|
|
165
|
+
NotebookActions.insertBelow(notebookPanel.content);
|
|
166
|
+
// replace content of this new active cell
|
|
167
|
+
this.replace(content);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Replaces the contents of the active cell.
|
|
171
|
+
*/
|
|
172
|
+
async replace(content) {
|
|
173
|
+
var _a;
|
|
174
|
+
const notebookPanel = this._notebookTracker.currentWidget;
|
|
175
|
+
if (!notebookPanel || !notebookPanel.isVisible) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// get reference to active cell directly from Notebook API. this avoids the
|
|
179
|
+
// possibility of acting on an out-of-date reference.
|
|
180
|
+
const activeCell = this._notebookTracker.activeCell;
|
|
181
|
+
if (!activeCell) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// wait for editor to be ready
|
|
185
|
+
await activeCell.ready;
|
|
186
|
+
// replace the content of the active cell
|
|
187
|
+
/**
|
|
188
|
+
* NOTE: calling this method sometimes emits an error to the browser console:
|
|
189
|
+
*
|
|
190
|
+
* ```
|
|
191
|
+
* Error: Calls to EditorView.update are not allowed while an update is in progress
|
|
192
|
+
* ```
|
|
193
|
+
*
|
|
194
|
+
* However, there seems to be no impact on the behavior/stability of the
|
|
195
|
+
* JupyterLab application after this error is logged. Furthermore, this is
|
|
196
|
+
* the official API for setting the content of a cell in JupyterLab 4,
|
|
197
|
+
* meaning that this is likely unavoidable.
|
|
198
|
+
*/
|
|
199
|
+
(_a = activeCell.editor) === null || _a === void 0 ? void 0 : _a.model.sharedModel.setSource(content);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { SxProps, Theme } from '@mui/material';
|
|
3
|
+
import { IChatModel } from '../model';
|
|
3
4
|
import { IAutocompletionRegistry } from '../registry';
|
|
4
5
|
export declare function ChatInput(props: ChatInput.IProps): JSX.Element;
|
|
5
6
|
/**
|
|
@@ -10,6 +11,10 @@ export declare namespace ChatInput {
|
|
|
10
11
|
* The properties of the react element.
|
|
11
12
|
*/
|
|
12
13
|
interface IProps {
|
|
14
|
+
/**
|
|
15
|
+
* The chat model.
|
|
16
|
+
*/
|
|
17
|
+
model: IChatModel;
|
|
13
18
|
/**
|
|
14
19
|
* The initial value of the input (default to '')
|
|
15
20
|
*/
|
|
@@ -22,10 +27,6 @@ export declare namespace ChatInput {
|
|
|
22
27
|
* The function to be called to cancel editing.
|
|
23
28
|
*/
|
|
24
29
|
onCancel?: () => unknown;
|
|
25
|
-
/**
|
|
26
|
-
* Whether using shift+enter to send the message.
|
|
27
|
-
*/
|
|
28
|
-
sendWithShiftEnter: boolean;
|
|
29
30
|
/**
|
|
30
31
|
* Custom mui/material styles.
|
|
31
32
|
*/
|
|
@@ -10,10 +10,17 @@ const INPUT_BOX_CLASS = 'jp-chat-input-container';
|
|
|
10
10
|
const SEND_BUTTON_CLASS = 'jp-chat-send-button';
|
|
11
11
|
const CANCEL_BUTTON_CLASS = 'jp-chat-cancel-button';
|
|
12
12
|
export function ChatInput(props) {
|
|
13
|
-
var _a;
|
|
14
|
-
const { autocompletionName, autocompletionRegistry,
|
|
13
|
+
var _a, _b;
|
|
14
|
+
const { autocompletionName, autocompletionRegistry, model } = props;
|
|
15
15
|
const autocompletion = useRef();
|
|
16
16
|
const [input, setInput] = useState(props.value || '');
|
|
17
|
+
const [sendWithShiftEnter, setSendWithShiftEnter] = useState((_a = model.config.sendWithShiftEnter) !== null && _a !== void 0 ? _a : false);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
model.configChanged.connect((_, config) => {
|
|
20
|
+
var _a;
|
|
21
|
+
setSendWithShiftEnter((_a = config.sendWithShiftEnter) !== null && _a !== void 0 ? _a : false);
|
|
22
|
+
});
|
|
23
|
+
}, [model]);
|
|
17
24
|
// The autocomplete commands options.
|
|
18
25
|
const [commandOptions, setCommandOptions] = useState([]);
|
|
19
26
|
// whether any option is highlighted in the slash command autocomplete
|
|
@@ -95,7 +102,7 @@ export function ChatInput(props) {
|
|
|
95
102
|
props.onCancel();
|
|
96
103
|
}
|
|
97
104
|
// Set the helper text based on whether Shift+Enter is used for sending.
|
|
98
|
-
const helperText =
|
|
105
|
+
const helperText = sendWithShiftEnter ? (React.createElement("span", null,
|
|
99
106
|
"Press ",
|
|
100
107
|
React.createElement("b", null, "Shift"),
|
|
101
108
|
"+",
|
|
@@ -135,7 +142,7 @@ export function ChatInput(props) {
|
|
|
135
142
|
React.createElement(Send, null))))
|
|
136
143
|
}, FormHelperTextProps: {
|
|
137
144
|
sx: { marginLeft: 'auto', marginRight: 0 }
|
|
138
|
-
}, helperText: input.length > 2 ? helperText : ' ' })), ...(
|
|
145
|
+
}, helperText: input.length > 2 ? helperText : ' ' })), ...(_b = autocompletion.current) === null || _b === void 0 ? void 0 : _b.props, inputValue: input, onInputChange: (_, newValue) => {
|
|
139
146
|
setInput(newValue);
|
|
140
147
|
}, onHighlightChange:
|
|
141
148
|
/**
|
|
@@ -48,7 +48,7 @@ export function ChatMessages(props) {
|
|
|
48
48
|
* Effect: listen to chat messages.
|
|
49
49
|
*/
|
|
50
50
|
useEffect(() => {
|
|
51
|
-
function handleChatEvents(
|
|
51
|
+
function handleChatEvents() {
|
|
52
52
|
setMessages([...model.messages]);
|
|
53
53
|
}
|
|
54
54
|
model.messagesUpdated.connect(handleChatEvents);
|
|
@@ -198,7 +198,6 @@ export function ChatMessageHeader(props) {
|
|
|
198
198
|
* The message component body.
|
|
199
199
|
*/
|
|
200
200
|
export function ChatMessage(props) {
|
|
201
|
-
var _a;
|
|
202
201
|
const { message, model, rmRegistry } = props;
|
|
203
202
|
const elementRef = useRef(null);
|
|
204
203
|
const [edit, setEdit] = useState(false);
|
|
@@ -258,7 +257,7 @@ export function ChatMessage(props) {
|
|
|
258
257
|
model.deleteMessage(id);
|
|
259
258
|
};
|
|
260
259
|
// Empty if the message has been deleted.
|
|
261
|
-
return deleted ? (React.createElement("div", { ref: elementRef, "data-index": props.index })) : (React.createElement("div", { ref: elementRef, "data-index": props.index }, edit && canEdit ? (React.createElement(ChatInput, { value: message.body, onSend: (input) => updateMessage(message.id, input), onCancel: () => cancelEdition(),
|
|
260
|
+
return deleted ? (React.createElement("div", { ref: elementRef, "data-index": props.index })) : (React.createElement("div", { ref: elementRef, "data-index": props.index }, edit && canEdit ? (React.createElement(ChatInput, { value: message.body, onSend: (input) => updateMessage(message.id, input), onCancel: () => cancelEdition(), model: model })) : (React.createElement(RendermimeMarkdown, { rmRegistry: rmRegistry, markdownStr: message.body, model: model, edit: canEdit ? () => setEdit(true) : undefined, delete: canDelete ? () => deleteMessage(message.id) : undefined }))));
|
|
262
261
|
}
|
|
263
262
|
/**
|
|
264
263
|
* The navigation component, to navigate to unread messages.
|
package/lib/components/chat.js
CHANGED
|
@@ -11,7 +11,6 @@ import { JlThemeProvider } from './jl-theme-provider';
|
|
|
11
11
|
import { ChatMessages } from './chat-messages';
|
|
12
12
|
import { ChatInput } from './chat-input';
|
|
13
13
|
export function ChatBody(props) {
|
|
14
|
-
var _a;
|
|
15
14
|
const { model, rmRegistry: renderMimeRegistry, autocompletionRegistry } = props;
|
|
16
15
|
// no need to append to messageGroups imperatively here. all of that is
|
|
17
16
|
// handled by the listeners registered in the effect hooks above.
|
|
@@ -27,7 +26,7 @@ export function ChatBody(props) {
|
|
|
27
26
|
paddingTop: 3.5,
|
|
28
27
|
paddingBottom: 0,
|
|
29
28
|
borderTop: '1px solid var(--jp-border-color1)'
|
|
30
|
-
},
|
|
29
|
+
}, model: model, autocompletionRegistry: autocompletionRegistry })));
|
|
31
30
|
}
|
|
32
31
|
export function Chat(props) {
|
|
33
32
|
var _a;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { IChatModel } from '../../model';
|
|
3
|
+
export type CodeToolbarProps = {
|
|
4
|
+
/**
|
|
5
|
+
* The chat model.
|
|
6
|
+
*/
|
|
7
|
+
model: IChatModel;
|
|
8
|
+
/**
|
|
9
|
+
* The content of the Markdown code block this component is attached to.
|
|
10
|
+
*/
|
|
11
|
+
content: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function CodeToolbar(props: CodeToolbarProps): JSX.Element;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { addAboveIcon, addBelowIcon } from '@jupyterlab/ui-components';
|
|
6
|
+
import { Box } from '@mui/material';
|
|
7
|
+
import React, { useEffect, useState } from 'react';
|
|
8
|
+
import { CopyButton } from './copy-button';
|
|
9
|
+
import { TooltippedIconButton } from '../mui-extras/tooltipped-icon-button';
|
|
10
|
+
import { replaceCellIcon } from '../../icons';
|
|
11
|
+
const CODE_TOOLBAR_CLASS = 'jp-chat-code-toolbar';
|
|
12
|
+
const CODE_TOOLBAR_ITEM_CLASS = 'jp-chat-code-toolbar-item';
|
|
13
|
+
export function CodeToolbar(props) {
|
|
14
|
+
var _a, _b;
|
|
15
|
+
const { content, model } = props;
|
|
16
|
+
const [toolbarEnable, setToolbarEnable] = useState((_a = model.config.enableCodeToolbar) !== null && _a !== void 0 ? _a : true);
|
|
17
|
+
const activeCellManager = model.activeCellManager;
|
|
18
|
+
const [toolbarBtnProps, setToolbarBtnProps] = useState({
|
|
19
|
+
content: content,
|
|
20
|
+
activeCellManager: activeCellManager,
|
|
21
|
+
activeCellAvailable: (_b = activeCellManager === null || activeCellManager === void 0 ? void 0 : activeCellManager.available) !== null && _b !== void 0 ? _b : false
|
|
22
|
+
});
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
activeCellManager === null || activeCellManager === void 0 ? void 0 : activeCellManager.availabilityChanged.connect(() => {
|
|
25
|
+
setToolbarBtnProps({
|
|
26
|
+
content,
|
|
27
|
+
activeCellManager: activeCellManager,
|
|
28
|
+
activeCellAvailable: activeCellManager.available
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
model.configChanged.connect((_, config) => {
|
|
32
|
+
var _a;
|
|
33
|
+
setToolbarEnable((_a = config.enableCodeToolbar) !== null && _a !== void 0 ? _a : true);
|
|
34
|
+
});
|
|
35
|
+
}, [model]);
|
|
36
|
+
return activeCellManager === null || !toolbarEnable ? (React.createElement(React.Fragment, null)) : (React.createElement(Box, { sx: {
|
|
37
|
+
display: 'flex',
|
|
38
|
+
justifyContent: 'flex-end',
|
|
39
|
+
alignItems: 'center',
|
|
40
|
+
padding: '6px 2px',
|
|
41
|
+
marginBottom: '1em',
|
|
42
|
+
border: '1px solid var(--jp-cell-editor-border-color)',
|
|
43
|
+
borderTop: 'none'
|
|
44
|
+
}, className: CODE_TOOLBAR_CLASS },
|
|
45
|
+
React.createElement(InsertAboveButton, { ...toolbarBtnProps, className: CODE_TOOLBAR_ITEM_CLASS }),
|
|
46
|
+
React.createElement(InsertBelowButton, { ...toolbarBtnProps, className: CODE_TOOLBAR_ITEM_CLASS }),
|
|
47
|
+
React.createElement(ReplaceButton, { ...toolbarBtnProps, className: CODE_TOOLBAR_ITEM_CLASS }),
|
|
48
|
+
React.createElement(CopyButton, { value: content, className: CODE_TOOLBAR_ITEM_CLASS })));
|
|
49
|
+
}
|
|
50
|
+
function InsertAboveButton(props) {
|
|
51
|
+
const tooltip = props.activeCellAvailable
|
|
52
|
+
? 'Insert above active cell'
|
|
53
|
+
: 'Insert above active cell (no active cell)';
|
|
54
|
+
return (React.createElement(TooltippedIconButton, { className: props.className, tooltip: tooltip, onClick: () => { var _a; return (_a = props.activeCellManager) === null || _a === void 0 ? void 0 : _a.insertAbove(props.content); }, disabled: !props.activeCellAvailable },
|
|
55
|
+
React.createElement(addAboveIcon.react, { height: "16px", width: "16px" })));
|
|
56
|
+
}
|
|
57
|
+
function InsertBelowButton(props) {
|
|
58
|
+
const tooltip = props.activeCellAvailable
|
|
59
|
+
? 'Insert below active cell'
|
|
60
|
+
: 'Insert below active cell (no active cell)';
|
|
61
|
+
return (React.createElement(TooltippedIconButton, { className: props.className, tooltip: tooltip, disabled: !props.activeCellAvailable, onClick: () => { var _a; return (_a = props.activeCellManager) === null || _a === void 0 ? void 0 : _a.insertBelow(props.content); } },
|
|
62
|
+
React.createElement(addBelowIcon.react, { height: "16px", width: "16px" })));
|
|
63
|
+
}
|
|
64
|
+
function ReplaceButton(props) {
|
|
65
|
+
const tooltip = props.activeCellAvailable
|
|
66
|
+
? 'Replace active cell'
|
|
67
|
+
: 'Replace active cell (no active cell)';
|
|
68
|
+
return (React.createElement(TooltippedIconButton, { className: props.className, tooltip: tooltip, disabled: !props.activeCellAvailable, onClick: () => { var _a; return (_a = props.activeCellManager) === null || _a === void 0 ? void 0 : _a.replace(props.content); } },
|
|
69
|
+
React.createElement(replaceCellIcon.react, { height: "16px", width: "16px" })));
|
|
70
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import React, { useState, useCallback, useRef } from 'react';
|
|
6
|
+
import { copyIcon } from '@jupyterlab/ui-components';
|
|
7
|
+
import { TooltippedIconButton } from '../mui-extras/tooltipped-icon-button';
|
|
8
|
+
var CopyStatus;
|
|
9
|
+
(function (CopyStatus) {
|
|
10
|
+
CopyStatus[CopyStatus["None"] = 0] = "None";
|
|
11
|
+
CopyStatus[CopyStatus["Copying"] = 1] = "Copying";
|
|
12
|
+
CopyStatus[CopyStatus["Copied"] = 2] = "Copied";
|
|
13
|
+
})(CopyStatus || (CopyStatus = {}));
|
|
14
|
+
const COPYBTN_TEXT_BY_STATUS = {
|
|
15
|
+
[CopyStatus.None]: 'Copy to clipboard',
|
|
16
|
+
[CopyStatus.Copying]: 'Copying…',
|
|
17
|
+
[CopyStatus.Copied]: 'Copied!'
|
|
18
|
+
};
|
|
19
|
+
export function CopyButton(props) {
|
|
20
|
+
const [copyStatus, setCopyStatus] = useState(CopyStatus.None);
|
|
21
|
+
const timeoutId = useRef(null);
|
|
22
|
+
const copy = useCallback(async () => {
|
|
23
|
+
// ignore if we are already copying
|
|
24
|
+
if (copyStatus === CopyStatus.Copying) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
await navigator.clipboard.writeText(props.value);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
console.error('Failed to copy text: ', err);
|
|
32
|
+
setCopyStatus(CopyStatus.None);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
setCopyStatus(CopyStatus.Copied);
|
|
36
|
+
if (timeoutId.current) {
|
|
37
|
+
clearTimeout(timeoutId.current);
|
|
38
|
+
}
|
|
39
|
+
timeoutId.current = window.setTimeout(() => setCopyStatus(CopyStatus.None), 1000);
|
|
40
|
+
}, [copyStatus, props.value]);
|
|
41
|
+
return (React.createElement(TooltippedIconButton, { className: props.className, tooltip: COPYBTN_TEXT_BY_STATUS[copyStatus], placement: "top", onClick: copy, "aria-label": "Copy to clipboard" },
|
|
42
|
+
React.createElement(copyIcon.react, { height: "16px", width: "16px" })));
|
|
43
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { TooltipProps } from '@mui/material';
|
|
2
|
+
/**
|
|
3
|
+
* A restyled MUI tooltip component that is dark by default to improve contrast
|
|
4
|
+
* against JupyterLab's default light theme. TODO: support dark themes.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ContrastingTooltip: import("@emotion/styled").StyledComponent<TooltipProps & import("@mui/system").MUIStyledCommonProps<import("@mui/material").Theme>, {}, {}>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { styled, Tooltip, tooltipClasses } from '@mui/material';
|
|
7
|
+
/**
|
|
8
|
+
* A restyled MUI tooltip component that is dark by default to improve contrast
|
|
9
|
+
* against JupyterLab's default light theme. TODO: support dark themes.
|
|
10
|
+
*/
|
|
11
|
+
export const ContrastingTooltip = styled(({ className, ...props }) => (React.createElement(Tooltip, { ...props, arrow: true, classes: { popper: className } })))(({ theme }) => ({
|
|
12
|
+
[`& .${tooltipClasses.tooltip}`]: {
|
|
13
|
+
backgroundColor: theme.palette.common.black,
|
|
14
|
+
color: theme.palette.common.white,
|
|
15
|
+
boxShadow: theme.shadows[1],
|
|
16
|
+
fontSize: 11
|
|
17
|
+
},
|
|
18
|
+
[`& .${tooltipClasses.arrow}`]: {
|
|
19
|
+
color: theme.palette.common.black
|
|
20
|
+
}
|
|
21
|
+
}));
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { IconButtonProps, TooltipProps } from '@mui/material';
|
|
3
|
+
export type TooltippedIconButtonProps = {
|
|
4
|
+
onClick: () => unknown;
|
|
5
|
+
tooltip: string;
|
|
6
|
+
children: JSX.Element;
|
|
7
|
+
className?: string;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
placement?: TooltipProps['placement'];
|
|
10
|
+
/**
|
|
11
|
+
* The offset of the tooltip popup.
|
|
12
|
+
*
|
|
13
|
+
* The expected syntax is defined by the Popper library:
|
|
14
|
+
* https://popper.js.org/docs/v2/modifiers/offset/
|
|
15
|
+
*/
|
|
16
|
+
offset?: [number, number];
|
|
17
|
+
'aria-label'?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Props passed directly to the MUI `IconButton` component.
|
|
20
|
+
*/
|
|
21
|
+
iconButtonProps?: IconButtonProps;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* A component that renders an MUI `IconButton` with a high-contrast tooltip
|
|
25
|
+
* provided by `ContrastingTooltip`. This component differs from the MUI
|
|
26
|
+
* defaults in the following ways:
|
|
27
|
+
*
|
|
28
|
+
* - Shows the tooltip on hover even if disabled.
|
|
29
|
+
* - Renders the tooltip above the button by default.
|
|
30
|
+
* - Renders the tooltip closer to the button by default.
|
|
31
|
+
* - Lowers the opacity of the IconButton when disabled.
|
|
32
|
+
* - Renders the IconButton with `line-height: 0` to avoid showing extra
|
|
33
|
+
* vertical space in SVG icons.
|
|
34
|
+
*/
|
|
35
|
+
export declare function TooltippedIconButton(props: TooltippedIconButtonProps): JSX.Element;
|