@jupyter/chat 0.11.0 → 0.12.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/components/chat-messages.d.ts +4 -0
- package/lib/components/chat-messages.js +37 -32
- package/lib/components/chat.d.ts +4 -0
- package/lib/components/chat.js +1 -1
- package/lib/components/index.d.ts +0 -1
- package/lib/components/index.js +0 -1
- package/lib/components/{markdown-renderer.d.ts → messages/message-renderer.d.ts} +10 -4
- package/lib/components/{markdown-renderer.js → messages/message-renderer.js} +11 -34
- package/lib/components/messages/welcome.d.ts +8 -0
- package/lib/components/messages/welcome.js +41 -0
- package/lib/components/scroll-container.js +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/input-model.d.ts +9 -0
- package/lib/input-model.js +8 -0
- package/lib/markdown-renderer.d.ts +38 -0
- package/lib/markdown-renderer.js +54 -0
- package/lib/model.d.ts +66 -5
- package/lib/model.js +44 -1
- package/package.json +2 -1
- package/src/components/chat-messages.tsx +50 -35
- package/src/components/chat.tsx +5 -0
- package/src/components/index.ts +0 -1
- package/src/components/{markdown-renderer.tsx → messages/message-renderer.tsx} +16 -42
- package/src/components/messages/welcome.tsx +52 -0
- package/src/components/scroll-container.tsx +1 -1
- package/src/index.ts +1 -0
- package/src/input-model.ts +14 -0
- package/src/markdown-renderer.ts +78 -0
- package/src/model.ts +108 -7
- package/style/chat.css +4 -0
|
@@ -10,8 +10,9 @@ import clsx from 'clsx';
|
|
|
10
10
|
import React, { useEffect, useState, useRef, forwardRef } from 'react';
|
|
11
11
|
import { AttachmentPreviewList } from './attachments';
|
|
12
12
|
import { ChatInput } from './chat-input';
|
|
13
|
-
import { MarkdownRenderer } from './markdown-renderer';
|
|
14
13
|
import { MessageFooter } from './messages/footer';
|
|
14
|
+
import { MessageRenderer } from './messages/message-renderer';
|
|
15
|
+
import { WelcomeMessage } from './messages/welcome';
|
|
15
16
|
import { ScrollContainer } from './scroll-container';
|
|
16
17
|
import { InputModel } from '../input-model';
|
|
17
18
|
import { replaceSpanToMention } from '../utils';
|
|
@@ -62,7 +63,7 @@ export function ChatMessages(props) {
|
|
|
62
63
|
setMessages([...model.messages]);
|
|
63
64
|
}
|
|
64
65
|
function handleWritersChange(_, writers) {
|
|
65
|
-
setCurrentWriters(writers);
|
|
66
|
+
setCurrentWriters(writers.map(writer => writer.user));
|
|
66
67
|
}
|
|
67
68
|
model.messagesUpdated.connect(handleChatEvents);
|
|
68
69
|
(_a = model.writersChanged) === null || _a === void 0 ? void 0 : _a.connect(handleWritersChange);
|
|
@@ -133,6 +134,7 @@ export function ChatMessages(props) {
|
|
|
133
134
|
}, [messages, allRendered]);
|
|
134
135
|
return (React.createElement(React.Fragment, null,
|
|
135
136
|
React.createElement(ScrollContainer, { sx: { flexGrow: 1 } },
|
|
137
|
+
props.welcomeMessage && (React.createElement(WelcomeMessage, { rmRegistry: props.rmRegistry, content: props.welcomeMessage })),
|
|
136
138
|
React.createElement(Box, { ref: refMsgBox, className: clsx(MESSAGES_BOX_CLASS) }, messages.map((message, i) => {
|
|
137
139
|
renderedPromise.current[i] = new PromiseDelegate();
|
|
138
140
|
return (
|
|
@@ -230,10 +232,10 @@ export const ChatMessage = forwardRef((props, ref) => {
|
|
|
230
232
|
const [deleted, setDeleted] = useState(false);
|
|
231
233
|
const [canEdit, setCanEdit] = useState(false);
|
|
232
234
|
const [canDelete, setCanDelete] = useState(false);
|
|
233
|
-
const [inputModel, setInputModel] = useState(null);
|
|
234
235
|
// Look if the message can be deleted or edited.
|
|
235
236
|
useEffect(() => {
|
|
236
237
|
var _a;
|
|
238
|
+
// Init canDelete and canEdit state.
|
|
237
239
|
setDeleted((_a = message.deleted) !== null && _a !== void 0 ? _a : false);
|
|
238
240
|
if (model.user !== undefined && !message.deleted) {
|
|
239
241
|
if (model.user.username === message.sender.username) {
|
|
@@ -251,40 +253,41 @@ export const ChatMessage = forwardRef((props, ref) => {
|
|
|
251
253
|
}
|
|
252
254
|
}, [model, message]);
|
|
253
255
|
// Create an input model only if the message is edited.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
let body = message.body;
|
|
259
|
-
(_a = message.mentions) === null || _a === void 0 ? void 0 : _a.forEach(user => {
|
|
260
|
-
body = replaceSpanToMention(body, user);
|
|
261
|
-
});
|
|
262
|
-
return new InputModel({
|
|
263
|
-
chatContext: model.createChatContext(),
|
|
264
|
-
onSend: (input, model) => updateMessage(message.id, input, model),
|
|
265
|
-
onCancel: () => cancelEdition(),
|
|
266
|
-
value: body,
|
|
267
|
-
activeCellManager: model.activeCellManager,
|
|
268
|
-
selectionWatcher: model.selectionWatcher,
|
|
269
|
-
documentManager: model.documentManager,
|
|
270
|
-
config: {
|
|
271
|
-
sendWithShiftEnter: model.config.sendWithShiftEnter
|
|
272
|
-
},
|
|
273
|
-
attachments: message.attachments,
|
|
274
|
-
mentions: message.mentions
|
|
275
|
-
});
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
else {
|
|
279
|
-
setInputModel(null);
|
|
256
|
+
const startEdition = () => {
|
|
257
|
+
var _a;
|
|
258
|
+
if (!canEdit) {
|
|
259
|
+
return;
|
|
280
260
|
}
|
|
281
|
-
|
|
261
|
+
let body = message.body;
|
|
262
|
+
(_a = message.mentions) === null || _a === void 0 ? void 0 : _a.forEach(user => {
|
|
263
|
+
body = replaceSpanToMention(body, user);
|
|
264
|
+
});
|
|
265
|
+
const inputModel = new InputModel({
|
|
266
|
+
chatContext: model.createChatContext(),
|
|
267
|
+
onSend: (input, model) => updateMessage(message.id, input, model),
|
|
268
|
+
onCancel: () => cancelEdition(),
|
|
269
|
+
value: body,
|
|
270
|
+
activeCellManager: model.activeCellManager,
|
|
271
|
+
selectionWatcher: model.selectionWatcher,
|
|
272
|
+
documentManager: model.documentManager,
|
|
273
|
+
config: {
|
|
274
|
+
sendWithShiftEnter: model.config.sendWithShiftEnter
|
|
275
|
+
},
|
|
276
|
+
attachments: message.attachments,
|
|
277
|
+
mentions: message.mentions
|
|
278
|
+
});
|
|
279
|
+
model.addEditionModel(message.id, inputModel);
|
|
280
|
+
setEdit(true);
|
|
281
|
+
};
|
|
282
282
|
// Cancel the current edition of the message.
|
|
283
283
|
const cancelEdition = () => {
|
|
284
|
+
var _a;
|
|
285
|
+
(_a = model.getEditionModel(message.id)) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
284
286
|
setEdit(false);
|
|
285
287
|
};
|
|
286
288
|
// Update the content of the message.
|
|
287
289
|
const updateMessage = (id, input, inputModel) => {
|
|
290
|
+
var _a;
|
|
288
291
|
if (!canEdit || !inputModel) {
|
|
289
292
|
return;
|
|
290
293
|
}
|
|
@@ -294,6 +297,7 @@ export const ChatMessage = forwardRef((props, ref) => {
|
|
|
294
297
|
updatedMessage.attachments = inputModel.attachments;
|
|
295
298
|
updatedMessage.mentions = inputModel.mentions;
|
|
296
299
|
model.updateMessage(id, updatedMessage);
|
|
300
|
+
(_a = model.getEditionModel(message.id)) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
297
301
|
setEdit(false);
|
|
298
302
|
};
|
|
299
303
|
// Delete the message.
|
|
@@ -305,7 +309,7 @@ export const ChatMessage = forwardRef((props, ref) => {
|
|
|
305
309
|
};
|
|
306
310
|
// Empty if the message has been deleted.
|
|
307
311
|
return deleted ? (React.createElement("div", { ref: ref, "data-index": props.index })) : (React.createElement("div", { ref: ref, "data-index": props.index },
|
|
308
|
-
edit && canEdit &&
|
|
312
|
+
edit && canEdit && model.getEditionModel(message.id) ? (React.createElement(ChatInput, { onCancel: () => cancelEdition(), model: model.getEditionModel(message.id), chatCommandRegistry: props.chatCommandRegistry, toolbarRegistry: props.inputToolbarRegistry })) : (React.createElement(MessageRenderer, { rmRegistry: rmRegistry, markdownStr: message.body, model: model, edit: canEdit ? startEdition : undefined, delete: canDelete ? () => deleteMessage(message.id) : undefined, rendered: props.renderedPromise })),
|
|
309
313
|
message.attachments && !edit && (
|
|
310
314
|
// Display the attachments only if message is not edited, otherwise the
|
|
311
315
|
// input component display them.
|
|
@@ -397,7 +401,8 @@ export function Navigation(props) {
|
|
|
397
401
|
useEffect(() => {
|
|
398
402
|
var _a, _b;
|
|
399
403
|
const viewportChanged = (model, viewport) => {
|
|
400
|
-
setLastInViewport(
|
|
404
|
+
setLastInViewport(model.messages.length === 0 ||
|
|
405
|
+
viewport.includes(model.messages.length - 1));
|
|
401
406
|
};
|
|
402
407
|
(_a = model.viewportChanged) === null || _a === void 0 ? void 0 : _a.connect(viewportChanged);
|
|
403
408
|
viewportChanged(model, (_b = model.messagesInViewport) !== null && _b !== void 0 ? _b : []);
|
package/lib/components/chat.d.ts
CHANGED
package/lib/components/chat.js
CHANGED
|
@@ -19,7 +19,7 @@ export function ChatBody(props) {
|
|
|
19
19
|
inputToolbarRegistry = InputToolbarRegistry.defaultToolbarRegistry();
|
|
20
20
|
}
|
|
21
21
|
return (React.createElement(AttachmentOpenerContext.Provider, { value: props.attachmentOpenerRegistry },
|
|
22
|
-
React.createElement(ChatMessages, { rmRegistry: props.rmRegistry, model: model, chatCommandRegistry: props.chatCommandRegistry, inputToolbarRegistry: inputToolbarRegistry, messageFooterRegistry: props.messageFooterRegistry }),
|
|
22
|
+
React.createElement(ChatMessages, { rmRegistry: props.rmRegistry, model: model, chatCommandRegistry: props.chatCommandRegistry, inputToolbarRegistry: inputToolbarRegistry, messageFooterRegistry: props.messageFooterRegistry, welcomeMessage: props.welcomeMessage }),
|
|
23
23
|
React.createElement(ChatInput, { sx: {
|
|
24
24
|
paddingLeft: 4,
|
|
25
25
|
paddingRight: 4,
|
|
@@ -4,7 +4,6 @@ export * from './chat-messages';
|
|
|
4
4
|
export * from './code-blocks';
|
|
5
5
|
export * from './input';
|
|
6
6
|
export * from './jl-theme-provider';
|
|
7
|
-
export * from './markdown-renderer';
|
|
8
7
|
export * from './mui-extras';
|
|
9
8
|
export * from './scroll-container';
|
|
10
9
|
export * from './toolbar';
|
package/lib/components/index.js
CHANGED
|
@@ -8,7 +8,6 @@ export * from './chat-messages';
|
|
|
8
8
|
export * from './code-blocks';
|
|
9
9
|
export * from './input';
|
|
10
10
|
export * from './jl-theme-provider';
|
|
11
|
-
export * from './markdown-renderer';
|
|
12
11
|
export * from './mui-extras';
|
|
13
12
|
export * from './scroll-container';
|
|
14
13
|
export * from './toolbar';
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
2
2
|
import { PromiseDelegate } from '@lumino/coreutils';
|
|
3
3
|
import React from 'react';
|
|
4
|
-
import { IChatModel } from '
|
|
5
|
-
|
|
4
|
+
import { IChatModel } from '../../model';
|
|
5
|
+
/**
|
|
6
|
+
* The type of the props for the MessageRenderer component.
|
|
7
|
+
*/
|
|
8
|
+
type MessageRendererProps = {
|
|
6
9
|
/**
|
|
7
10
|
* The string to render.
|
|
8
11
|
*/
|
|
@@ -32,6 +35,9 @@ type MarkdownRendererProps = {
|
|
|
32
35
|
*/
|
|
33
36
|
delete?: () => void;
|
|
34
37
|
};
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
/**
|
|
39
|
+
* The message renderer base component.
|
|
40
|
+
*/
|
|
41
|
+
declare function MessageRendererBase(props: MessageRendererProps): JSX.Element;
|
|
42
|
+
export declare const MessageRenderer: React.MemoExoticComponent<typeof MessageRendererBase>;
|
|
37
43
|
export {};
|
|
@@ -4,47 +4,24 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import React, { useState, useEffect } from 'react';
|
|
6
6
|
import { createPortal } from 'react-dom';
|
|
7
|
-
import { CodeToolbar } from '
|
|
8
|
-
import { MessageToolbar } from '
|
|
9
|
-
|
|
10
|
-
const MD_RENDERED_CLASS = 'jp-chat-rendered-markdown';
|
|
7
|
+
import { CodeToolbar } from '../code-blocks/code-toolbar';
|
|
8
|
+
import { MessageToolbar } from '../toolbar';
|
|
9
|
+
import { MarkdownRenderer, MD_RENDERED_CLASS } from '../../markdown-renderer';
|
|
11
10
|
/**
|
|
12
|
-
*
|
|
13
|
-
* after the initial MarkDown render. For example, this function takes '\(` and
|
|
14
|
-
* returns `\\(`.
|
|
15
|
-
*
|
|
16
|
-
* Required for proper rendering of MarkDown + LaTeX markup in the chat by
|
|
17
|
-
* `ILatexTypesetter`.
|
|
11
|
+
* The message renderer base component.
|
|
18
12
|
*/
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
.replace(/\\\(/g, '\\\\(')
|
|
22
|
-
.replace(/\\\)/g, '\\\\)')
|
|
23
|
-
.replace(/\\\[/g, '\\\\[')
|
|
24
|
-
.replace(/\\\]/g, '\\\\]');
|
|
25
|
-
}
|
|
26
|
-
function MarkdownRendererBase(props) {
|
|
13
|
+
function MessageRendererBase(props) {
|
|
14
|
+
const { markdownStr, rmRegistry } = props;
|
|
27
15
|
const appendContent = props.appendContent || false;
|
|
28
16
|
const [renderedContent, setRenderedContent] = useState(null);
|
|
29
17
|
// each element is a two-tuple with the structure [codeToolbarRoot, codeToolbarProps].
|
|
30
18
|
const [codeToolbarDefns, setCodeToolbarDefns] = useState([]);
|
|
31
19
|
useEffect(() => {
|
|
32
20
|
const renderContent = async () => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const model = props.rmRegistry.createModel({
|
|
37
|
-
data: { [MD_MIME_TYPE]: mdStr }
|
|
21
|
+
const renderer = await MarkdownRenderer.renderContent({
|
|
22
|
+
content: markdownStr,
|
|
23
|
+
rmRegistry
|
|
38
24
|
});
|
|
39
|
-
const renderer = props.rmRegistry.createRenderer(MD_MIME_TYPE);
|
|
40
|
-
// step 1: render markdown
|
|
41
|
-
await renderer.renderModel(model);
|
|
42
|
-
(_a = props.rmRegistry.latexTypesetter) === null || _a === void 0 ? void 0 : _a.typeset(renderer.node);
|
|
43
|
-
if (!renderer.node) {
|
|
44
|
-
throw new Error('Rendermime was unable to render Markdown content within a chat message. Please report this upstream to Jupyter chat on GitHub.');
|
|
45
|
-
}
|
|
46
|
-
// step 2: render LaTeX via MathJax.
|
|
47
|
-
(_b = props.rmRegistry.latexTypesetter) === null || _b === void 0 ? void 0 : _b.typeset(renderer.node);
|
|
48
25
|
const newCodeToolbarDefns = [];
|
|
49
26
|
// Attach CodeToolbar root element to each <pre> block
|
|
50
27
|
const preBlocks = renderer.node.querySelectorAll('pre');
|
|
@@ -63,7 +40,7 @@ function MarkdownRendererBase(props) {
|
|
|
63
40
|
props.rendered.resolve();
|
|
64
41
|
};
|
|
65
42
|
renderContent();
|
|
66
|
-
}, [
|
|
43
|
+
}, [markdownStr, rmRegistry]);
|
|
67
44
|
return (React.createElement("div", { className: MD_RENDERED_CLASS },
|
|
68
45
|
renderedContent &&
|
|
69
46
|
(appendContent ? (React.createElement("div", { ref: node => node && node.appendChild(renderedContent) })) : (React.createElement("div", { ref: node => node && node.replaceChildren(renderedContent) }))),
|
|
@@ -76,4 +53,4 @@ function MarkdownRendererBase(props) {
|
|
|
76
53
|
return createPortal(React.createElement(CodeToolbar, { ...codeToolbarProps }), codeToolbarRoot);
|
|
77
54
|
})));
|
|
78
55
|
}
|
|
79
|
-
export const
|
|
56
|
+
export const MessageRenderer = React.memo(MessageRendererBase);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { MarkdownRenderer } from '../../markdown-renderer';
|
|
3
|
+
/**
|
|
4
|
+
* The welcome message component.
|
|
5
|
+
* This message is displayed on top of the chat messages, and is rendered using a
|
|
6
|
+
* markdown renderer.
|
|
7
|
+
*/
|
|
8
|
+
export declare function WelcomeMessage(props: MarkdownRenderer.IOptions): JSX.Element;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { classes } from '@jupyterlab/ui-components';
|
|
6
|
+
import React, { useEffect, useRef } from 'react';
|
|
7
|
+
import { MarkdownRenderer, MD_RENDERED_CLASS } from '../../markdown-renderer';
|
|
8
|
+
const WELCOME_MESSAGE_CLASS = 'jp-chat-welcome-message';
|
|
9
|
+
/**
|
|
10
|
+
* The welcome message component.
|
|
11
|
+
* This message is displayed on top of the chat messages, and is rendered using a
|
|
12
|
+
* markdown renderer.
|
|
13
|
+
*/
|
|
14
|
+
export function WelcomeMessage(props) {
|
|
15
|
+
const { rmRegistry } = props;
|
|
16
|
+
const content = props.content + '\n----\n';
|
|
17
|
+
// ref that tracks the content container to store the rendermime node in
|
|
18
|
+
const renderingContainer = useRef(null);
|
|
19
|
+
// ref that tracks whether the rendermime node has already been inserted
|
|
20
|
+
const renderingInserted = useRef(false);
|
|
21
|
+
/**
|
|
22
|
+
* Effect: use Rendermime to render `props.markdownStr` into an HTML element,
|
|
23
|
+
* and insert it into `renderingContainer` if not yet inserted.
|
|
24
|
+
*/
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const renderContent = async () => {
|
|
27
|
+
const renderer = await MarkdownRenderer.renderContent({
|
|
28
|
+
content,
|
|
29
|
+
rmRegistry
|
|
30
|
+
});
|
|
31
|
+
// insert the rendering into renderingContainer if not yet inserted
|
|
32
|
+
if (renderingContainer.current !== null && !renderingInserted.current) {
|
|
33
|
+
renderingContainer.current.appendChild(renderer.node);
|
|
34
|
+
renderingInserted.current = true;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
renderContent();
|
|
38
|
+
}, [content]);
|
|
39
|
+
return (React.createElement("div", { className: classes(MD_RENDERED_CLASS, WELCOME_MESSAGE_CLASS) },
|
|
40
|
+
React.createElement("div", { ref: renderingContainer })));
|
|
41
|
+
}
|
|
@@ -28,6 +28,6 @@ export function ScrollContainer(props) {
|
|
|
28
28
|
},
|
|
29
29
|
...props.sx
|
|
30
30
|
} },
|
|
31
|
-
React.createElement(Box,
|
|
31
|
+
React.createElement(Box, null, props.children),
|
|
32
32
|
React.createElement(Box, { sx: { overflowAnchor: 'auto', height: '1px' } })));
|
|
33
33
|
}
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
package/lib/input-model.d.ts
CHANGED
|
@@ -114,6 +114,10 @@ export interface IInputModel extends IDisposable {
|
|
|
114
114
|
* Clear mentions list.
|
|
115
115
|
*/
|
|
116
116
|
clearMentions(): void;
|
|
117
|
+
/**
|
|
118
|
+
* A signal emitting when disposing of the model.
|
|
119
|
+
*/
|
|
120
|
+
readonly onDisposed: ISignal<InputModel, void>;
|
|
117
121
|
}
|
|
118
122
|
/**
|
|
119
123
|
* The input model.
|
|
@@ -231,6 +235,10 @@ export declare class InputModel implements IInputModel {
|
|
|
231
235
|
* Dispose the input model.
|
|
232
236
|
*/
|
|
233
237
|
dispose(): void;
|
|
238
|
+
/**
|
|
239
|
+
* A signal emitting when disposing of the model.
|
|
240
|
+
*/
|
|
241
|
+
get onDisposed(): ISignal<InputModel, void>;
|
|
234
242
|
/**
|
|
235
243
|
* Whether the input model is disposed.
|
|
236
244
|
*/
|
|
@@ -252,6 +260,7 @@ export declare class InputModel implements IInputModel {
|
|
|
252
260
|
private _configChanged;
|
|
253
261
|
private _focusInputSignal;
|
|
254
262
|
private _attachmentsChanged;
|
|
263
|
+
private _onDisposed;
|
|
255
264
|
private _isDisposed;
|
|
256
265
|
}
|
|
257
266
|
export declare namespace InputModel {
|
package/lib/input-model.js
CHANGED
|
@@ -60,6 +60,7 @@ export class InputModel {
|
|
|
60
60
|
this._configChanged = new Signal(this);
|
|
61
61
|
this._focusInputSignal = new Signal(this);
|
|
62
62
|
this._attachmentsChanged = new Signal(this);
|
|
63
|
+
this._onDisposed = new Signal(this);
|
|
63
64
|
this._isDisposed = false;
|
|
64
65
|
this._onSend = options.onSend;
|
|
65
66
|
this._chatContext = options.chatContext;
|
|
@@ -235,8 +236,15 @@ export class InputModel {
|
|
|
235
236
|
if (this.isDisposed) {
|
|
236
237
|
return;
|
|
237
238
|
}
|
|
239
|
+
this._onDisposed.emit();
|
|
238
240
|
this._isDisposed = true;
|
|
239
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* A signal emitting when disposing of the model.
|
|
244
|
+
*/
|
|
245
|
+
get onDisposed() {
|
|
246
|
+
return this._onDisposed;
|
|
247
|
+
}
|
|
240
248
|
/**
|
|
241
249
|
* Whether the input model is disposed.
|
|
242
250
|
*/
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { IRenderMime, IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
2
|
+
export declare const MD_RENDERED_CLASS = "jp-chat-rendered-markdown";
|
|
3
|
+
export declare const MD_MIME_TYPE = "text/markdown";
|
|
4
|
+
/**
|
|
5
|
+
* A namespace for the MarkdownRenderer.
|
|
6
|
+
*/
|
|
7
|
+
export declare namespace MarkdownRenderer {
|
|
8
|
+
/**
|
|
9
|
+
* The options for the MarkdownRenderer.
|
|
10
|
+
*/
|
|
11
|
+
interface IOptions {
|
|
12
|
+
/**
|
|
13
|
+
* The rendermime registry.
|
|
14
|
+
*/
|
|
15
|
+
rmRegistry: IRenderMimeRegistry;
|
|
16
|
+
/**
|
|
17
|
+
* The markdown content.
|
|
18
|
+
*/
|
|
19
|
+
content: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A generic function to render a markdown string into a DOM element.
|
|
23
|
+
*
|
|
24
|
+
* @param content - the markdown content.
|
|
25
|
+
* @param rmRegistry - the rendermime registry.
|
|
26
|
+
* @returns a promise that resolves to the renderer.
|
|
27
|
+
*/
|
|
28
|
+
function renderContent(options: IOptions): Promise<IRenderMime.IRenderer>;
|
|
29
|
+
/**
|
|
30
|
+
* Escapes backslashes in LaTeX delimiters such that they appear in the DOM
|
|
31
|
+
* after the initial MarkDown render. For example, this function takes '\(` and
|
|
32
|
+
* returns `\\(`.
|
|
33
|
+
*
|
|
34
|
+
* Required for proper rendering of MarkDown + LaTeX markup in the chat by
|
|
35
|
+
* `ILatexTypesetter`.
|
|
36
|
+
*/
|
|
37
|
+
function escapeLatexDelimiters(text: string): string;
|
|
38
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
export const MD_RENDERED_CLASS = 'jp-chat-rendered-markdown';
|
|
6
|
+
export const MD_MIME_TYPE = 'text/markdown';
|
|
7
|
+
/**
|
|
8
|
+
* A namespace for the MarkdownRenderer.
|
|
9
|
+
*/
|
|
10
|
+
export var MarkdownRenderer;
|
|
11
|
+
(function (MarkdownRenderer) {
|
|
12
|
+
/**
|
|
13
|
+
* A generic function to render a markdown string into a DOM element.
|
|
14
|
+
*
|
|
15
|
+
* @param content - the markdown content.
|
|
16
|
+
* @param rmRegistry - the rendermime registry.
|
|
17
|
+
* @returns a promise that resolves to the renderer.
|
|
18
|
+
*/
|
|
19
|
+
async function renderContent(options) {
|
|
20
|
+
var _a;
|
|
21
|
+
const { rmRegistry, content } = options;
|
|
22
|
+
// initialize mime model
|
|
23
|
+
const mdStr = escapeLatexDelimiters(content);
|
|
24
|
+
const model = rmRegistry.createModel({
|
|
25
|
+
data: { [MD_MIME_TYPE]: mdStr }
|
|
26
|
+
});
|
|
27
|
+
const renderer = rmRegistry.createRenderer(MD_MIME_TYPE);
|
|
28
|
+
// step 1: render markdown
|
|
29
|
+
await renderer.renderModel(model);
|
|
30
|
+
if (!renderer.node) {
|
|
31
|
+
throw new Error('Rendermime was unable to render Markdown content within a chat message. Please report this upstream to Jupyter chat on GitHub.');
|
|
32
|
+
}
|
|
33
|
+
// step 2: render LaTeX via MathJax.
|
|
34
|
+
(_a = rmRegistry.latexTypesetter) === null || _a === void 0 ? void 0 : _a.typeset(renderer.node);
|
|
35
|
+
return renderer;
|
|
36
|
+
}
|
|
37
|
+
MarkdownRenderer.renderContent = renderContent;
|
|
38
|
+
/**
|
|
39
|
+
* Escapes backslashes in LaTeX delimiters such that they appear in the DOM
|
|
40
|
+
* after the initial MarkDown render. For example, this function takes '\(` and
|
|
41
|
+
* returns `\\(`.
|
|
42
|
+
*
|
|
43
|
+
* Required for proper rendering of MarkDown + LaTeX markup in the chat by
|
|
44
|
+
* `ILatexTypesetter`.
|
|
45
|
+
*/
|
|
46
|
+
function escapeLatexDelimiters(text) {
|
|
47
|
+
return text
|
|
48
|
+
.replace(/\\\(/g, '\\\\(')
|
|
49
|
+
.replace(/\\\)/g, '\\\\)')
|
|
50
|
+
.replace(/\\\[/g, '\\\\[')
|
|
51
|
+
.replace(/\\\]/g, '\\\\]');
|
|
52
|
+
}
|
|
53
|
+
MarkdownRenderer.escapeLatexDelimiters = escapeLatexDelimiters;
|
|
54
|
+
})(MarkdownRenderer || (MarkdownRenderer = {}));
|
package/lib/model.d.ts
CHANGED
|
@@ -38,6 +38,10 @@ export interface IChatModel extends IDisposable {
|
|
|
38
38
|
* The input model.
|
|
39
39
|
*/
|
|
40
40
|
readonly input: IInputModel;
|
|
41
|
+
/**
|
|
42
|
+
* The current writer list.
|
|
43
|
+
*/
|
|
44
|
+
readonly writers: IChatModel.IWriter[];
|
|
41
45
|
/**
|
|
42
46
|
* Get the active cell manager.
|
|
43
47
|
*/
|
|
@@ -57,7 +61,7 @@ export interface IChatModel extends IDisposable {
|
|
|
57
61
|
/**
|
|
58
62
|
* A signal emitting when the messages list is updated.
|
|
59
63
|
*/
|
|
60
|
-
|
|
64
|
+
readonly configChanged: ISignal<IChatModel, IConfig>;
|
|
61
65
|
/**
|
|
62
66
|
* A signal emitting when unread messages change.
|
|
63
67
|
*/
|
|
@@ -69,7 +73,11 @@ export interface IChatModel extends IDisposable {
|
|
|
69
73
|
/**
|
|
70
74
|
* A signal emitting when the writers change.
|
|
71
75
|
*/
|
|
72
|
-
readonly writersChanged?: ISignal<IChatModel,
|
|
76
|
+
readonly writersChanged?: ISignal<IChatModel, IChatModel.IWriter[]>;
|
|
77
|
+
/**
|
|
78
|
+
* A signal emitting when the message edition input changed change.
|
|
79
|
+
*/
|
|
80
|
+
readonly messageEditionAdded: ISignal<IChatModel, IChatModel.IMessageEdition>;
|
|
73
81
|
/**
|
|
74
82
|
* Send a message, to be defined depending on the chosen technology.
|
|
75
83
|
* Default to no-op.
|
|
@@ -130,11 +138,19 @@ export interface IChatModel extends IDisposable {
|
|
|
130
138
|
/**
|
|
131
139
|
* Update the current writers list.
|
|
132
140
|
*/
|
|
133
|
-
updateWriters(writers:
|
|
141
|
+
updateWriters(writers: IChatModel.IWriter[]): void;
|
|
134
142
|
/**
|
|
135
143
|
* Create the chat context that will be passed to the input model.
|
|
136
144
|
*/
|
|
137
145
|
createChatContext(): IChatContext;
|
|
146
|
+
/**
|
|
147
|
+
* Get the input model of the edited message, given its id.
|
|
148
|
+
*/
|
|
149
|
+
getEditionModel(messageID: string): IInputModel | undefined;
|
|
150
|
+
/**
|
|
151
|
+
* Add an input model of the edited message.
|
|
152
|
+
*/
|
|
153
|
+
addEditionModel(messageID: string, inputModel: IInputModel): void;
|
|
138
154
|
}
|
|
139
155
|
/**
|
|
140
156
|
* An abstract implementation of IChatModel.
|
|
@@ -165,6 +181,10 @@ export declare abstract class AbstractChatModel implements IChatModel {
|
|
|
165
181
|
* The input model.
|
|
166
182
|
*/
|
|
167
183
|
get input(): IInputModel;
|
|
184
|
+
/**
|
|
185
|
+
* The current writer list.
|
|
186
|
+
*/
|
|
187
|
+
get writers(): IChatModel.IWriter[];
|
|
168
188
|
/**
|
|
169
189
|
* Get the active cell manager.
|
|
170
190
|
*/
|
|
@@ -216,7 +236,11 @@ export declare abstract class AbstractChatModel implements IChatModel {
|
|
|
216
236
|
/**
|
|
217
237
|
* A signal emitting when the writers change.
|
|
218
238
|
*/
|
|
219
|
-
get writersChanged(): ISignal<IChatModel,
|
|
239
|
+
get writersChanged(): ISignal<IChatModel, IChatModel.IWriter[]>;
|
|
240
|
+
/**
|
|
241
|
+
* A signal emitting when the message edition input changed change.
|
|
242
|
+
*/
|
|
243
|
+
get messageEditionAdded(): ISignal<IChatModel, IChatModel.IMessageEdition>;
|
|
220
244
|
/**
|
|
221
245
|
* Send a message, to be defined depending on the chosen technology.
|
|
222
246
|
* Default to no-op.
|
|
@@ -266,11 +290,19 @@ export declare abstract class AbstractChatModel implements IChatModel {
|
|
|
266
290
|
* Update the current writers list.
|
|
267
291
|
* This implementation only propagate the list via a signal.
|
|
268
292
|
*/
|
|
269
|
-
updateWriters(writers:
|
|
293
|
+
updateWriters(writers: IChatModel.IWriter[]): void;
|
|
270
294
|
/**
|
|
271
295
|
* Create the chat context that will be passed to the input model.
|
|
272
296
|
*/
|
|
273
297
|
abstract createChatContext(): IChatContext;
|
|
298
|
+
/**
|
|
299
|
+
* Get the input model of the edited message, given its id.
|
|
300
|
+
*/
|
|
301
|
+
getEditionModel(messageID: string): IInputModel | undefined;
|
|
302
|
+
/**
|
|
303
|
+
* Add an input model of the edited message.
|
|
304
|
+
*/
|
|
305
|
+
addEditionModel(messageID: string, inputModel: IInputModel): void;
|
|
274
306
|
/**
|
|
275
307
|
* Add unread messages to the list.
|
|
276
308
|
* @param indexes - list of new indexes.
|
|
@@ -299,11 +331,14 @@ export declare abstract class AbstractChatModel implements IChatModel {
|
|
|
299
331
|
private _selectionWatcher;
|
|
300
332
|
private _documentManager;
|
|
301
333
|
private _notificationId;
|
|
334
|
+
private _writers;
|
|
335
|
+
private _messageEditions;
|
|
302
336
|
private _messagesUpdated;
|
|
303
337
|
private _configChanged;
|
|
304
338
|
private _unreadChanged;
|
|
305
339
|
private _viewportChanged;
|
|
306
340
|
private _writersChanged;
|
|
341
|
+
private _messageEditionAdded;
|
|
307
342
|
}
|
|
308
343
|
/**
|
|
309
344
|
* The chat model namespace.
|
|
@@ -338,6 +373,32 @@ export declare namespace IChatModel {
|
|
|
338
373
|
*/
|
|
339
374
|
documentManager?: IDocumentManager | null;
|
|
340
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* Representation of a message edition.
|
|
378
|
+
*/
|
|
379
|
+
interface IMessageEdition {
|
|
380
|
+
/**
|
|
381
|
+
* The id of the edited message.
|
|
382
|
+
*/
|
|
383
|
+
id: string;
|
|
384
|
+
/**
|
|
385
|
+
* The model of the input editing the message.
|
|
386
|
+
*/
|
|
387
|
+
model: IInputModel;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Writer interface, including the message ID if the writer is editing a message.
|
|
391
|
+
*/
|
|
392
|
+
interface IWriter {
|
|
393
|
+
/**
|
|
394
|
+
* The user currently writing.
|
|
395
|
+
*/
|
|
396
|
+
user: IUser;
|
|
397
|
+
/**
|
|
398
|
+
* The message ID (optional)
|
|
399
|
+
*/
|
|
400
|
+
messageID?: string;
|
|
401
|
+
}
|
|
341
402
|
}
|
|
342
403
|
/**
|
|
343
404
|
* Interface of the chat context, a 'subset' of the model with readonly attribute,
|