@jupyter/chat 0.1.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/__tests__/model.spec.d.ts +1 -0
- package/lib/__tests__/model.spec.js +72 -0
- package/lib/__tests__/widgets.spec.d.ts +1 -0
- package/lib/__tests__/widgets.spec.js +33 -0
- package/lib/components/chat-input.d.ts +33 -0
- package/lib/components/chat-input.js +60 -0
- package/lib/components/chat-messages.d.ts +32 -0
- package/lib/components/chat-messages.js +162 -0
- package/lib/components/chat.d.ts +43 -0
- package/lib/components/chat.js +100 -0
- package/lib/components/copy-button.d.ts +6 -0
- package/lib/components/copy-button.js +35 -0
- package/lib/components/jl-theme-provider.d.ts +6 -0
- package/lib/components/jl-theme-provider.js +19 -0
- package/lib/components/mui-extras/stacking-alert.d.ts +28 -0
- package/lib/components/mui-extras/stacking-alert.js +56 -0
- package/lib/components/rendermime-markdown.d.ts +12 -0
- package/lib/components/rendermime-markdown.js +54 -0
- package/lib/components/scroll-container.d.ts +23 -0
- package/lib/components/scroll-container.js +51 -0
- package/lib/components/toolbar.d.ts +11 -0
- package/lib/components/toolbar.js +30 -0
- package/lib/icons.d.ts +2 -0
- package/lib/icons.js +11 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +10 -0
- package/lib/model.d.ts +177 -0
- package/lib/model.js +128 -0
- package/lib/theme-provider.d.ts +3 -0
- package/lib/theme-provider.js +133 -0
- package/lib/types.d.ts +49 -0
- package/lib/types.js +5 -0
- package/lib/widgets/chat-error.d.ts +2 -0
- package/lib/widgets/chat-error.js +26 -0
- package/lib/widgets/chat-sidebar.d.ts +4 -0
- package/lib/widgets/chat-sidebar.js +15 -0
- package/lib/widgets/chat-widget.d.ts +19 -0
- package/lib/widgets/chat-widget.js +28 -0
- package/package.json +209 -0
- package/src/__tests__/model.spec.ts +84 -0
- package/src/__tests__/widgets.spec.ts +43 -0
- package/src/components/chat-input.tsx +143 -0
- package/src/components/chat-messages.tsx +283 -0
- package/src/components/chat.tsx +179 -0
- package/src/components/copy-button.tsx +55 -0
- package/src/components/jl-theme-provider.tsx +28 -0
- package/src/components/mui-extras/stacking-alert.tsx +105 -0
- package/src/components/rendermime-markdown.tsx +88 -0
- package/src/components/scroll-container.tsx +74 -0
- package/src/components/toolbar.tsx +50 -0
- package/src/icons.ts +15 -0
- package/src/index.ts +11 -0
- package/src/model.ts +272 -0
- package/src/theme-provider.ts +137 -0
- package/src/types/mui.d.ts +18 -0
- package/src/types/svg.d.ts +17 -0
- package/src/types.ts +58 -0
- package/src/widgets/chat-error.tsx +43 -0
- package/src/widgets/chat-sidebar.tsx +30 -0
- package/src/widgets/chat-widget.tsx +51 -0
- package/style/base.css +13 -0
- package/style/chat-settings.css +10 -0
- package/style/chat.css +53 -0
- package/style/icons/chat.svg +6 -0
- package/style/index.css +6 -0
- package/style/index.js +6 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import React, { useState, useMemo, useCallback } from 'react';
|
|
6
|
+
import { Alert, Collapse } from '@mui/material';
|
|
7
|
+
/**
|
|
8
|
+
* Hook that returns a function to trigger an alert, and a corresponding alert
|
|
9
|
+
* JSX element for the consumer to render. The number of successive identical
|
|
10
|
+
* alerts `X` is indicated in the element via the suffix "(X)".
|
|
11
|
+
*/
|
|
12
|
+
export function useStackingAlert() {
|
|
13
|
+
const [type, setType] = useState(null);
|
|
14
|
+
const [msg, setMsg] = useState('');
|
|
15
|
+
const [repeatCount, setRepeatCount] = useState(0);
|
|
16
|
+
const [expand, setExpand] = useState(false);
|
|
17
|
+
const [exitPromise, setExitPromise] = useState();
|
|
18
|
+
const [exitPromiseResolver, setExitPromiseResolver] = useState();
|
|
19
|
+
const showAlert = useCallback((nextType, _nextMsg) => {
|
|
20
|
+
// if the alert is identical to the previous alert, increment the
|
|
21
|
+
// `repeatCount` indicator.
|
|
22
|
+
const nextMsg = _nextMsg.toString();
|
|
23
|
+
if (nextType === type && nextMsg === msg) {
|
|
24
|
+
setRepeatCount(currCount => currCount + 1);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (type === null) {
|
|
28
|
+
// if this alert is being shown for the first time, initialize the
|
|
29
|
+
// exitPromise so we can await it on `clear()`.
|
|
30
|
+
setExitPromise(new Promise(res => {
|
|
31
|
+
setExitPromiseResolver(() => res);
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
setType(nextType);
|
|
35
|
+
setMsg(nextMsg);
|
|
36
|
+
setRepeatCount(0);
|
|
37
|
+
setExpand(true);
|
|
38
|
+
}, [msg, type]);
|
|
39
|
+
const alertJsx = useMemo(() => (React.createElement(Collapse, { in: expand, onExited: () => {
|
|
40
|
+
exitPromiseResolver === null || exitPromiseResolver === void 0 ? void 0 : exitPromiseResolver();
|
|
41
|
+
// only clear the alert after the Collapse exits, otherwise the alert
|
|
42
|
+
// disappears without any animation.
|
|
43
|
+
setType(null);
|
|
44
|
+
setMsg('');
|
|
45
|
+
setRepeatCount(0);
|
|
46
|
+
}, timeout: 200 }, type !== null && (React.createElement(Alert, { severity: type }, msg + (repeatCount ? ` (${repeatCount})` : ''))))), [msg, repeatCount, type, expand, exitPromiseResolver]);
|
|
47
|
+
const clearAlert = useCallback(() => {
|
|
48
|
+
setExpand(false);
|
|
49
|
+
return exitPromise;
|
|
50
|
+
}, [expand, exitPromise]);
|
|
51
|
+
return {
|
|
52
|
+
show: showAlert,
|
|
53
|
+
jsx: alertJsx,
|
|
54
|
+
clear: clearAlert
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
type RendermimeMarkdownProps = {
|
|
4
|
+
markdownStr: string;
|
|
5
|
+
rmRegistry: IRenderMimeRegistry;
|
|
6
|
+
appendContent?: boolean;
|
|
7
|
+
edit?: () => void;
|
|
8
|
+
delete?: () => void;
|
|
9
|
+
};
|
|
10
|
+
declare function RendermimeMarkdownBase(props: RendermimeMarkdownProps): JSX.Element;
|
|
11
|
+
export declare const RendermimeMarkdown: React.MemoExoticComponent<typeof RendermimeMarkdownBase>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
6
|
+
import ReactDOM from 'react-dom';
|
|
7
|
+
import { CopyButton } from './copy-button';
|
|
8
|
+
import { MessageToolbar } from './toolbar';
|
|
9
|
+
const MD_MIME_TYPE = 'text/markdown';
|
|
10
|
+
const RENDERMIME_MD_CLASS = 'jp-chat-rendermime-markdown';
|
|
11
|
+
/**
|
|
12
|
+
* Takes \( and returns \\(. Escapes LaTeX delimeters by adding extra backslashes where needed for proper rendering by @jupyterlab/rendermime.
|
|
13
|
+
*/
|
|
14
|
+
function escapeLatexDelimiters(text) {
|
|
15
|
+
return text
|
|
16
|
+
.replace('\\(', '\\\\(')
|
|
17
|
+
.replace('\\)', '\\\\)')
|
|
18
|
+
.replace('\\[', '\\\\[')
|
|
19
|
+
.replace('\\]', '\\\\]');
|
|
20
|
+
}
|
|
21
|
+
function RendermimeMarkdownBase(props) {
|
|
22
|
+
const appendContent = props.appendContent || false;
|
|
23
|
+
const [renderedContent, setRenderedContent] = useState(null);
|
|
24
|
+
const containerRef = useRef(null);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const renderContent = async () => {
|
|
27
|
+
var _a;
|
|
28
|
+
const mdStr = escapeLatexDelimiters(props.markdownStr);
|
|
29
|
+
const model = props.rmRegistry.createModel({
|
|
30
|
+
data: { [MD_MIME_TYPE]: mdStr }
|
|
31
|
+
});
|
|
32
|
+
const renderer = props.rmRegistry.createRenderer(MD_MIME_TYPE);
|
|
33
|
+
await renderer.renderModel(model);
|
|
34
|
+
(_a = props.rmRegistry.latexTypesetter) === null || _a === void 0 ? void 0 : _a.typeset(renderer.node);
|
|
35
|
+
// Attach CopyButton to each <pre> block
|
|
36
|
+
if (containerRef.current && renderer.node) {
|
|
37
|
+
const preBlocks = renderer.node.querySelectorAll('pre');
|
|
38
|
+
preBlocks.forEach(preBlock => {
|
|
39
|
+
var _a;
|
|
40
|
+
const copyButtonContainer = document.createElement('div');
|
|
41
|
+
(_a = preBlock.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(copyButtonContainer, preBlock.nextSibling);
|
|
42
|
+
ReactDOM.render(React.createElement(CopyButton, { value: preBlock.textContent || '' }), copyButtonContainer);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
setRenderedContent(renderer.node);
|
|
46
|
+
};
|
|
47
|
+
renderContent();
|
|
48
|
+
}, [props.markdownStr, props.rmRegistry]);
|
|
49
|
+
return (React.createElement("div", { ref: containerRef, className: RENDERMIME_MD_CLASS },
|
|
50
|
+
renderedContent &&
|
|
51
|
+
(appendContent ? (React.createElement("div", { ref: node => node && node.appendChild(renderedContent) })) : (React.createElement("div", { ref: node => node && node.replaceChildren(renderedContent) }))),
|
|
52
|
+
React.createElement(MessageToolbar, { edit: props.edit, delete: props.delete })));
|
|
53
|
+
}
|
|
54
|
+
export const RendermimeMarkdown = React.memo(RendermimeMarkdownBase);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { SxProps, Theme } from '@mui/material';
|
|
3
|
+
type ScrollContainerProps = {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
sx?: SxProps<Theme>;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Component that handles intelligent scrolling.
|
|
9
|
+
*
|
|
10
|
+
* - If viewport is at the bottom of the overflow container, appending new
|
|
11
|
+
* children keeps the viewport on the bottom of the overflow container.
|
|
12
|
+
*
|
|
13
|
+
* - If viewport is in the middle of the overflow container, appending new
|
|
14
|
+
* children leaves the viewport unaffected.
|
|
15
|
+
*
|
|
16
|
+
* Currently only works for Chrome and Firefox due to reliance on
|
|
17
|
+
* `overflow-anchor`.
|
|
18
|
+
*
|
|
19
|
+
* **References**
|
|
20
|
+
* - https://css-tricks.com/books/greatest-css-tricks/pin-scrolling-to-bottom/
|
|
21
|
+
*/
|
|
22
|
+
export declare function ScrollContainer(props: ScrollContainerProps): JSX.Element;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import React, { useEffect, useMemo } from 'react';
|
|
6
|
+
import { Box } from '@mui/material';
|
|
7
|
+
/**
|
|
8
|
+
* Component that handles intelligent scrolling.
|
|
9
|
+
*
|
|
10
|
+
* - If viewport is at the bottom of the overflow container, appending new
|
|
11
|
+
* children keeps the viewport on the bottom of the overflow container.
|
|
12
|
+
*
|
|
13
|
+
* - If viewport is in the middle of the overflow container, appending new
|
|
14
|
+
* children leaves the viewport unaffected.
|
|
15
|
+
*
|
|
16
|
+
* Currently only works for Chrome and Firefox due to reliance on
|
|
17
|
+
* `overflow-anchor`.
|
|
18
|
+
*
|
|
19
|
+
* **References**
|
|
20
|
+
* - https://css-tricks.com/books/greatest-css-tricks/pin-scrolling-to-bottom/
|
|
21
|
+
*/
|
|
22
|
+
export function ScrollContainer(props) {
|
|
23
|
+
const id = useMemo(() => 'jupyter-chat-scroll-container-' + Date.now().toString(), []);
|
|
24
|
+
/**
|
|
25
|
+
* Effect: Scroll the container to the bottom as soon as it is visible.
|
|
26
|
+
*/
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const el = document.querySelector(`#${id}`);
|
|
29
|
+
if (!el) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const observer = new IntersectionObserver(entries => {
|
|
33
|
+
entries.forEach(entry => {
|
|
34
|
+
if (entry.isIntersecting) {
|
|
35
|
+
el.scroll({ top: 999999999 });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}, { threshold: 1.0 });
|
|
39
|
+
observer.observe(el);
|
|
40
|
+
return () => observer.disconnect();
|
|
41
|
+
}, []);
|
|
42
|
+
return (React.createElement(Box, { id: id, sx: {
|
|
43
|
+
overflowY: 'scroll',
|
|
44
|
+
'& *': {
|
|
45
|
+
overflowAnchor: 'none'
|
|
46
|
+
},
|
|
47
|
+
...props.sx
|
|
48
|
+
} },
|
|
49
|
+
React.createElement(Box, { sx: { minHeight: '100.01%' } }, props.children),
|
|
50
|
+
React.createElement(Box, { sx: { overflowAnchor: 'auto', height: '1px' } })));
|
|
51
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
/**
|
|
3
|
+
* The toolbar attached to a message.
|
|
4
|
+
*/
|
|
5
|
+
export declare function MessageToolbar(props: MessageToolbar.IProps): JSX.Element;
|
|
6
|
+
export declare namespace MessageToolbar {
|
|
7
|
+
interface IProps {
|
|
8
|
+
edit?: () => void;
|
|
9
|
+
delete?: () => void;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { ToolbarButtonComponent, deleteIcon, editIcon } from '@jupyterlab/ui-components';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
const TOOLBAR_CLASS = 'jp-chat-toolbar';
|
|
8
|
+
/**
|
|
9
|
+
* The toolbar attached to a message.
|
|
10
|
+
*/
|
|
11
|
+
export function MessageToolbar(props) {
|
|
12
|
+
const buttons = [];
|
|
13
|
+
if (props.edit !== undefined) {
|
|
14
|
+
const editButton = ToolbarButtonComponent({
|
|
15
|
+
icon: editIcon,
|
|
16
|
+
onClick: props.edit,
|
|
17
|
+
tooltip: 'Edit'
|
|
18
|
+
});
|
|
19
|
+
buttons.push(editButton);
|
|
20
|
+
}
|
|
21
|
+
if (props.delete !== undefined) {
|
|
22
|
+
const deleteButton = ToolbarButtonComponent({
|
|
23
|
+
icon: deleteIcon,
|
|
24
|
+
onClick: props.delete,
|
|
25
|
+
tooltip: 'Delete'
|
|
26
|
+
});
|
|
27
|
+
buttons.push(deleteButton);
|
|
28
|
+
}
|
|
29
|
+
return (React.createElement("div", { className: TOOLBAR_CLASS }, buttons.map(toolbarButton => toolbarButton)));
|
|
30
|
+
}
|
package/lib/icons.d.ts
ADDED
package/lib/icons.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
// This file is based on iconimports.ts in @jupyterlab/ui-components, but is manually generated.
|
|
6
|
+
import { LabIcon } from '@jupyterlab/ui-components';
|
|
7
|
+
import chatSvgStr from '../style/icons/chat.svg';
|
|
8
|
+
export const chatIcon = new LabIcon({
|
|
9
|
+
name: 'jupyter-chat::chat',
|
|
10
|
+
svgstr: chatSvgStr
|
|
11
|
+
});
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
export * from './icons';
|
|
6
|
+
export * from './model';
|
|
7
|
+
export * from './types';
|
|
8
|
+
export * from './widgets/chat-error';
|
|
9
|
+
export * from './widgets/chat-sidebar';
|
|
10
|
+
export * from './widgets/chat-widget';
|
package/lib/model.d.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { IDisposable } from '@lumino/disposable';
|
|
2
|
+
import { ISignal } from '@lumino/signaling';
|
|
3
|
+
import { IChatHistory, INewMessage, IChatMessage, IConfig, IUser } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* The chat model interface.
|
|
6
|
+
*/
|
|
7
|
+
export interface IChatModel extends IDisposable {
|
|
8
|
+
/**
|
|
9
|
+
* The chat model ID.
|
|
10
|
+
*/
|
|
11
|
+
id: string;
|
|
12
|
+
/**
|
|
13
|
+
* The configuration for the chat panel.
|
|
14
|
+
*/
|
|
15
|
+
config: IConfig;
|
|
16
|
+
/**
|
|
17
|
+
* The user connected to the chat panel.
|
|
18
|
+
*/
|
|
19
|
+
readonly user?: IUser;
|
|
20
|
+
/**
|
|
21
|
+
* The chat messages list.
|
|
22
|
+
*/
|
|
23
|
+
readonly messages: IChatMessage[];
|
|
24
|
+
/**
|
|
25
|
+
* The signal emitted when the messages list is updated.
|
|
26
|
+
*/
|
|
27
|
+
readonly messagesUpdated: ISignal<IChatModel, void>;
|
|
28
|
+
/**
|
|
29
|
+
* Send a message, to be defined depending on the chosen technology.
|
|
30
|
+
* Default to no-op.
|
|
31
|
+
*
|
|
32
|
+
* @param message - the message to send.
|
|
33
|
+
* @returns whether the message has been sent or not, or nothing if not needed.
|
|
34
|
+
*/
|
|
35
|
+
addMessage(message: INewMessage): Promise<boolean | void> | boolean | void;
|
|
36
|
+
/**
|
|
37
|
+
* Optional, to update a message from the chat panel.
|
|
38
|
+
*
|
|
39
|
+
* @param id - the unique ID of the message.
|
|
40
|
+
* @param message - the updated message.
|
|
41
|
+
*/
|
|
42
|
+
updateMessage?(id: string, message: IChatMessage): Promise<boolean | void> | boolean | void;
|
|
43
|
+
/**
|
|
44
|
+
* Optional, to delete a message from the chat.
|
|
45
|
+
*
|
|
46
|
+
* @param id - the unique ID of the message.
|
|
47
|
+
*/
|
|
48
|
+
deleteMessage?(id: string): Promise<boolean | void> | boolean | void;
|
|
49
|
+
/**
|
|
50
|
+
* Optional, to get messages history.
|
|
51
|
+
*/
|
|
52
|
+
getHistory?(): Promise<IChatHistory>;
|
|
53
|
+
/**
|
|
54
|
+
* Dispose the chat model.
|
|
55
|
+
*/
|
|
56
|
+
dispose(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Whether the chat handler is disposed.
|
|
59
|
+
*/
|
|
60
|
+
isDisposed: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Function to call when a message is received.
|
|
63
|
+
*
|
|
64
|
+
* @param message - the message with user information and body.
|
|
65
|
+
*/
|
|
66
|
+
messageAdded(message: IChatMessage): void;
|
|
67
|
+
/**
|
|
68
|
+
* Function called when messages are inserted.
|
|
69
|
+
*
|
|
70
|
+
* @param index - the index of the first message of the list.
|
|
71
|
+
* @param messages - the messages list.
|
|
72
|
+
*/
|
|
73
|
+
messagesInserted(index: number, messages: IChatMessage[]): void;
|
|
74
|
+
/**
|
|
75
|
+
* Function called when messages are deleted.
|
|
76
|
+
*
|
|
77
|
+
* @param index - the index of the first message to delete.
|
|
78
|
+
* @param count - the number of messages to delete.
|
|
79
|
+
*/
|
|
80
|
+
messagesDeleted(index: number, count: number): void;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The default chat model implementation.
|
|
84
|
+
* It is not able to send or update a message by itself, since it depends on the
|
|
85
|
+
* chosen technology.
|
|
86
|
+
*/
|
|
87
|
+
export declare class ChatModel implements IChatModel {
|
|
88
|
+
/**
|
|
89
|
+
* Create a new chat model.
|
|
90
|
+
*/
|
|
91
|
+
constructor(options?: ChatModel.IOptions);
|
|
92
|
+
/**
|
|
93
|
+
* The chat messages list.
|
|
94
|
+
*/
|
|
95
|
+
get messages(): IChatMessage[];
|
|
96
|
+
/**
|
|
97
|
+
* The chat model ID.
|
|
98
|
+
*/
|
|
99
|
+
get id(): string;
|
|
100
|
+
set id(value: string);
|
|
101
|
+
/**
|
|
102
|
+
* The chat settings.
|
|
103
|
+
*/
|
|
104
|
+
get config(): IConfig;
|
|
105
|
+
set config(value: Partial<IConfig>);
|
|
106
|
+
/**
|
|
107
|
+
* The signal emitted when the messages list is updated.
|
|
108
|
+
*/
|
|
109
|
+
get messagesUpdated(): ISignal<IChatModel, void>;
|
|
110
|
+
/**
|
|
111
|
+
* Send a message, to be defined depending on the chosen technology.
|
|
112
|
+
* Default to no-op.
|
|
113
|
+
*
|
|
114
|
+
* @param message - the message to send.
|
|
115
|
+
* @returns whether the message has been sent or not.
|
|
116
|
+
*/
|
|
117
|
+
addMessage(message: INewMessage): Promise<boolean | void> | boolean | void;
|
|
118
|
+
/**
|
|
119
|
+
* Optional, to update a message from the chat panel.
|
|
120
|
+
*
|
|
121
|
+
* @param id - the unique ID of the message.
|
|
122
|
+
* @param message - the message to update.
|
|
123
|
+
*/
|
|
124
|
+
updateMessage?(id: string, message: INewMessage): Promise<boolean | void> | boolean | void;
|
|
125
|
+
/**
|
|
126
|
+
* Dispose the chat model.
|
|
127
|
+
*/
|
|
128
|
+
dispose(): void;
|
|
129
|
+
/**
|
|
130
|
+
* Whether the chat handler is disposed.
|
|
131
|
+
*/
|
|
132
|
+
get isDisposed(): boolean;
|
|
133
|
+
/**
|
|
134
|
+
* A function called before transferring the message to the panel(s).
|
|
135
|
+
* Can be useful if some actions are required on the message.
|
|
136
|
+
*/
|
|
137
|
+
protected formatChatMessage(message: IChatMessage): IChatMessage;
|
|
138
|
+
/**
|
|
139
|
+
* Function to call when a message is received.
|
|
140
|
+
*
|
|
141
|
+
* @param message - the message with user information and body.
|
|
142
|
+
*/
|
|
143
|
+
messageAdded(message: IChatMessage): void;
|
|
144
|
+
/**
|
|
145
|
+
* Function called when messages are inserted.
|
|
146
|
+
*
|
|
147
|
+
* @param index - the index of the first message of the list.
|
|
148
|
+
* @param messages - the messages list.
|
|
149
|
+
*/
|
|
150
|
+
messagesInserted(index: number, messages: IChatMessage[]): void;
|
|
151
|
+
/**
|
|
152
|
+
* Function called when messages are deleted.
|
|
153
|
+
*
|
|
154
|
+
* @param index - the index of the first message to delete.
|
|
155
|
+
* @param count - the number of messages to delete.
|
|
156
|
+
*/
|
|
157
|
+
messagesDeleted(index: number, count: number): void;
|
|
158
|
+
private _messages;
|
|
159
|
+
private _id;
|
|
160
|
+
private _config;
|
|
161
|
+
private _isDisposed;
|
|
162
|
+
private _messagesUpdated;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The chat model namespace.
|
|
166
|
+
*/
|
|
167
|
+
export declare namespace ChatModel {
|
|
168
|
+
/**
|
|
169
|
+
* The instantiation options for a ChatModel.
|
|
170
|
+
*/
|
|
171
|
+
interface IOptions {
|
|
172
|
+
/**
|
|
173
|
+
* Initial config for the chat widget.
|
|
174
|
+
*/
|
|
175
|
+
config?: IConfig;
|
|
176
|
+
}
|
|
177
|
+
}
|
package/lib/model.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { Signal } from '@lumino/signaling';
|
|
6
|
+
/**
|
|
7
|
+
* The default chat model implementation.
|
|
8
|
+
* It is not able to send or update a message by itself, since it depends on the
|
|
9
|
+
* chosen technology.
|
|
10
|
+
*/
|
|
11
|
+
export class ChatModel {
|
|
12
|
+
/**
|
|
13
|
+
* Create a new chat model.
|
|
14
|
+
*/
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
var _a;
|
|
17
|
+
this._messages = [];
|
|
18
|
+
this._id = '';
|
|
19
|
+
this._isDisposed = false;
|
|
20
|
+
this._messagesUpdated = new Signal(this);
|
|
21
|
+
this._config = (_a = options.config) !== null && _a !== void 0 ? _a : {};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The chat messages list.
|
|
25
|
+
*/
|
|
26
|
+
get messages() {
|
|
27
|
+
return this._messages;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The chat model ID.
|
|
31
|
+
*/
|
|
32
|
+
get id() {
|
|
33
|
+
return this._id;
|
|
34
|
+
}
|
|
35
|
+
set id(value) {
|
|
36
|
+
this._id = value;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The chat settings.
|
|
40
|
+
*/
|
|
41
|
+
get config() {
|
|
42
|
+
return this._config;
|
|
43
|
+
}
|
|
44
|
+
set config(value) {
|
|
45
|
+
this._config = { ...this._config, ...value };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The signal emitted when the messages list is updated.
|
|
49
|
+
*/
|
|
50
|
+
get messagesUpdated() {
|
|
51
|
+
return this._messagesUpdated;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Send a message, to be defined depending on the chosen technology.
|
|
55
|
+
* Default to no-op.
|
|
56
|
+
*
|
|
57
|
+
* @param message - the message to send.
|
|
58
|
+
* @returns whether the message has been sent or not.
|
|
59
|
+
*/
|
|
60
|
+
addMessage(message) { }
|
|
61
|
+
/**
|
|
62
|
+
* Dispose the chat model.
|
|
63
|
+
*/
|
|
64
|
+
dispose() {
|
|
65
|
+
if (this.isDisposed) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
this._isDisposed = true;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Whether the chat handler is disposed.
|
|
72
|
+
*/
|
|
73
|
+
get isDisposed() {
|
|
74
|
+
return this._isDisposed;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A function called before transferring the message to the panel(s).
|
|
78
|
+
* Can be useful if some actions are required on the message.
|
|
79
|
+
*/
|
|
80
|
+
formatChatMessage(message) {
|
|
81
|
+
return message;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Function to call when a message is received.
|
|
85
|
+
*
|
|
86
|
+
* @param message - the message with user information and body.
|
|
87
|
+
*/
|
|
88
|
+
messageAdded(message) {
|
|
89
|
+
const messageIndex = this._messages.findIndex(msg => msg.id === message.id);
|
|
90
|
+
if (messageIndex > -1) {
|
|
91
|
+
// The message is an update of an existing one.
|
|
92
|
+
// Let's remove it to avoid position conflict if timestamp has changed.
|
|
93
|
+
this._messages.splice(messageIndex, 1);
|
|
94
|
+
}
|
|
95
|
+
// Find the first message that should be after this one.
|
|
96
|
+
let nextMsgIndex = this._messages.findIndex(msg => msg.time > message.time);
|
|
97
|
+
if (nextMsgIndex === -1) {
|
|
98
|
+
// There is no message after this one, so let's insert the message at the end.
|
|
99
|
+
nextMsgIndex = this._messages.length;
|
|
100
|
+
}
|
|
101
|
+
// Insert the message.
|
|
102
|
+
this.messagesInserted(nextMsgIndex, [message]);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Function called when messages are inserted.
|
|
106
|
+
*
|
|
107
|
+
* @param index - the index of the first message of the list.
|
|
108
|
+
* @param messages - the messages list.
|
|
109
|
+
*/
|
|
110
|
+
messagesInserted(index, messages) {
|
|
111
|
+
const formattedMessages = [];
|
|
112
|
+
messages.forEach(message => {
|
|
113
|
+
formattedMessages.push(this.formatChatMessage(message));
|
|
114
|
+
});
|
|
115
|
+
this._messages.splice(index, 0, ...formattedMessages);
|
|
116
|
+
this._messagesUpdated.emit();
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Function called when messages are deleted.
|
|
120
|
+
*
|
|
121
|
+
* @param index - the index of the first message to delete.
|
|
122
|
+
* @param count - the number of messages to delete.
|
|
123
|
+
*/
|
|
124
|
+
messagesDeleted(index, count) {
|
|
125
|
+
this._messages.splice(index, count);
|
|
126
|
+
this._messagesUpdated.emit();
|
|
127
|
+
}
|
|
128
|
+
}
|