@jupyter/chat 0.13.0 → 0.14.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 +2 -0
- package/lib/active-cell-manager.js +7 -2
- package/lib/components/avatar.d.ts +20 -0
- package/lib/components/avatar.js +29 -0
- package/lib/components/chat.d.ts +1 -3
- package/lib/components/chat.js +2 -3
- package/lib/components/index.d.ts +2 -3
- package/lib/components/index.js +2 -3
- package/lib/components/input/buttons/send-button.js +15 -5
- package/lib/components/{chat-input.d.ts → input/chat-input.d.ts} +3 -3
- package/lib/components/{chat-input.js → input/chat-input.js} +7 -4
- package/lib/components/input/index.d.ts +1 -0
- package/lib/components/input/index.js +1 -0
- package/lib/components/input/toolbar-registry.d.ts +6 -0
- package/lib/components/input/use-chat-commands.d.ts +1 -1
- package/lib/components/input/use-chat-commands.js +23 -12
- package/lib/components/messages/footer.d.ts +2 -2
- package/lib/components/messages/footer.js +1 -1
- package/lib/components/messages/header.d.ts +16 -0
- package/lib/components/messages/header.js +85 -0
- package/lib/components/messages/index.d.ts +9 -0
- package/lib/components/messages/index.js +13 -0
- package/lib/components/messages/message-renderer.js +1 -1
- package/lib/components/messages/message.d.ts +21 -0
- package/lib/components/messages/message.js +102 -0
- package/lib/components/messages/messages.d.ts +38 -0
- package/lib/components/messages/messages.js +139 -0
- package/lib/components/messages/navigation.d.ts +20 -0
- package/lib/components/messages/navigation.js +98 -0
- package/lib/components/messages/writers.d.ts +16 -0
- package/lib/components/messages/writers.js +39 -0
- package/lib/context.d.ts +1 -1
- package/lib/index.d.ts +2 -6
- package/lib/index.js +2 -6
- package/lib/{registry.d.ts → registers/attachment-openers.d.ts} +1 -1
- package/lib/registers/chat-commands.d.ts +108 -0
- package/lib/{chat-commands/registry.js → registers/chat-commands.js} +8 -8
- package/lib/{footers/registry.d.ts → registers/footers.d.ts} +30 -5
- package/lib/registers/index.d.ts +3 -0
- package/lib/{footers → registers}/index.js +3 -2
- package/lib/selection-watcher.d.ts +11 -1
- package/lib/selection-watcher.js +10 -4
- package/lib/widgets/index.d.ts +3 -0
- package/lib/{chat-commands → widgets}/index.js +3 -2
- package/package.json +3 -1
- package/src/active-cell-manager.ts +10 -1
- package/src/components/avatar.tsx +68 -0
- package/src/components/chat.tsx +11 -6
- package/src/components/index.ts +2 -3
- package/src/components/input/buttons/send-button.tsx +17 -5
- package/src/components/{chat-input.tsx → input/chat-input.tsx} +12 -7
- package/src/components/input/index.ts +1 -0
- package/src/components/input/toolbar-registry.tsx +6 -0
- package/src/components/input/use-chat-commands.tsx +30 -15
- package/src/components/messages/footer.tsx +5 -2
- package/src/components/messages/header.tsx +133 -0
- package/src/components/messages/index.ts +14 -0
- package/src/components/messages/message-renderer.tsx +1 -1
- package/src/components/messages/message.tsx +156 -0
- package/src/components/messages/messages.tsx +218 -0
- package/src/components/messages/navigation.tsx +167 -0
- package/src/components/messages/welcome.tsx +1 -0
- package/src/components/messages/writers.tsx +81 -0
- package/src/context.ts +1 -1
- package/src/index.ts +2 -6
- package/src/{registry.ts → registers/attachment-openers.ts} +2 -1
- package/src/registers/chat-commands.ts +142 -0
- package/src/{footers/registry.ts → registers/footers.ts} +35 -8
- package/src/{footers → registers}/index.ts +3 -2
- package/src/selection-watcher.ts +28 -5
- package/src/{chat-commands → widgets}/index.ts +3 -2
- package/style/chat.css +82 -0
- package/lib/chat-commands/index.d.ts +0 -2
- package/lib/chat-commands/registry.d.ts +0 -28
- package/lib/chat-commands/types.d.ts +0 -52
- package/lib/chat-commands/types.js +0 -5
- package/lib/components/chat-messages.d.ts +0 -119
- package/lib/components/chat-messages.js +0 -446
- package/lib/footers/index.d.ts +0 -2
- package/lib/footers/types.d.ts +0 -26
- package/lib/footers/types.js +0 -5
- package/src/chat-commands/registry.ts +0 -60
- package/src/chat-commands/types.ts +0 -67
- package/src/components/chat-messages.tsx +0 -739
- package/src/footers/types.ts +0 -33
- package/lib/components/{toolbar.d.ts → messages/toolbar.d.ts} +0 -0
- package/lib/components/{toolbar.js → messages/toolbar.js} +0 -0
- package/lib/{registry.js → registers/attachment-openers.js} +0 -0
- package/lib/{footers/registry.js → registers/footers.js} +4 -4
- /package/src/components/{toolbar.tsx → messages/toolbar.tsx} +0 -0
|
@@ -5,10 +5,12 @@ import { ISignal } from '@lumino/signaling';
|
|
|
5
5
|
type CellContent = {
|
|
6
6
|
type: string;
|
|
7
7
|
source: string;
|
|
8
|
+
language?: string;
|
|
8
9
|
};
|
|
9
10
|
type CellWithErrorContent = {
|
|
10
11
|
type: 'code';
|
|
11
12
|
source: string;
|
|
13
|
+
language?: string;
|
|
12
14
|
error: {
|
|
13
15
|
name: string;
|
|
14
16
|
value: string;
|
|
@@ -110,8 +110,11 @@ export class ActiveCellManager {
|
|
|
110
110
|
return this._activeCellErrorChanged;
|
|
111
111
|
}
|
|
112
112
|
getContent(withError = false) {
|
|
113
|
-
var _a;
|
|
113
|
+
var _a, _b, _c;
|
|
114
114
|
const sharedModel = (_a = this._notebookTracker.activeCell) === null || _a === void 0 ? void 0 : _a.model.sharedModel;
|
|
115
|
+
const language = (sharedModel === null || sharedModel === void 0 ? void 0 : sharedModel.cell_type) === 'code'
|
|
116
|
+
? (_c = (_b = this._notebookTracker.currentWidget) === null || _b === void 0 ? void 0 : _b.model) === null || _c === void 0 ? void 0 : _c.defaultKernelLanguage
|
|
117
|
+
: undefined;
|
|
115
118
|
if (!sharedModel) {
|
|
116
119
|
return null;
|
|
117
120
|
}
|
|
@@ -119,7 +122,8 @@ export class ActiveCellManager {
|
|
|
119
122
|
if (!withError) {
|
|
120
123
|
return {
|
|
121
124
|
type: sharedModel.cell_type,
|
|
122
|
-
source: sharedModel.getSource()
|
|
125
|
+
source: sharedModel.getSource(),
|
|
126
|
+
language
|
|
123
127
|
};
|
|
124
128
|
}
|
|
125
129
|
// case where withError = true
|
|
@@ -128,6 +132,7 @@ export class ActiveCellManager {
|
|
|
128
132
|
return {
|
|
129
133
|
type: 'code',
|
|
130
134
|
source: sharedModel.getSource(),
|
|
135
|
+
language,
|
|
131
136
|
error: {
|
|
132
137
|
name: error.ename,
|
|
133
138
|
value: error.evalue,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { IUser } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* The avatar props.
|
|
5
|
+
*/
|
|
6
|
+
type AvatarProps = {
|
|
7
|
+
/**
|
|
8
|
+
* The user to display an avatar.
|
|
9
|
+
*/
|
|
10
|
+
user: IUser;
|
|
11
|
+
/**
|
|
12
|
+
* Whether the avatar should be small.
|
|
13
|
+
*/
|
|
14
|
+
small?: boolean;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* The avatar component.
|
|
18
|
+
*/
|
|
19
|
+
export declare function Avatar(props: AvatarProps): JSX.Element | null;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { Avatar as MuiAvatar, Typography } from '@mui/material';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
/**
|
|
8
|
+
* The avatar component.
|
|
9
|
+
*/
|
|
10
|
+
export function Avatar(props) {
|
|
11
|
+
var _a, _b;
|
|
12
|
+
const { user } = props;
|
|
13
|
+
const sharedStyles = {
|
|
14
|
+
height: `${props.small ? '16' : '24'}px`,
|
|
15
|
+
width: `${props.small ? '16' : '24'}px`,
|
|
16
|
+
bgcolor: user.color,
|
|
17
|
+
fontSize: `var(--jp-ui-font-size${props.small ? '0' : '1'})`
|
|
18
|
+
};
|
|
19
|
+
const name = (_b = (_a = user.display_name) !== null && _a !== void 0 ? _a : user.name) !== null && _b !== void 0 ? _b : (user.username || 'User undefined');
|
|
20
|
+
return user.avatar_url ? (React.createElement(MuiAvatar, { sx: {
|
|
21
|
+
...sharedStyles
|
|
22
|
+
}, src: user.avatar_url, alt: name, title: name })) : user.initials ? (React.createElement(MuiAvatar, { sx: {
|
|
23
|
+
...sharedStyles
|
|
24
|
+
}, alt: name, title: name },
|
|
25
|
+
React.createElement(Typography, { sx: {
|
|
26
|
+
fontSize: `var(--jp-ui-font-size${props.small ? '0' : '1'})`,
|
|
27
|
+
color: 'var(--jp-ui-inverse-font-color1)'
|
|
28
|
+
} }, user.initials))) : null;
|
|
29
|
+
}
|
package/lib/components/chat.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { IThemeManager } from '@jupyterlab/apputils';
|
|
3
3
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
4
|
-
import { IChatCommandRegistry } from '../chat-commands';
|
|
5
4
|
import { IInputToolbarRegistry } from './input';
|
|
6
|
-
import { IMessageFooterRegistry } from '../footers';
|
|
7
5
|
import { IChatModel } from '../model';
|
|
8
|
-
import { IAttachmentOpenerRegistry } from '../
|
|
6
|
+
import { IAttachmentOpenerRegistry, IChatCommandRegistry, IMessageFooterRegistry } from '../registers';
|
|
9
7
|
export declare function ChatBody(props: Chat.IChatBodyProps): JSX.Element;
|
|
10
8
|
export declare function Chat(props: Chat.IOptions): JSX.Element;
|
|
11
9
|
/**
|
package/lib/components/chat.js
CHANGED
|
@@ -7,10 +7,9 @@ import SettingsIcon from '@mui/icons-material/Settings';
|
|
|
7
7
|
import { IconButton } from '@mui/material';
|
|
8
8
|
import { Box } from '@mui/system';
|
|
9
9
|
import React, { useState } from 'react';
|
|
10
|
+
import { ChatInput, InputToolbarRegistry } from './input';
|
|
10
11
|
import { JlThemeProvider } from './jl-theme-provider';
|
|
11
|
-
import { ChatMessages } from './
|
|
12
|
-
import { ChatInput } from './chat-input';
|
|
13
|
-
import { InputToolbarRegistry } from './input';
|
|
12
|
+
import { ChatMessages } from './messages';
|
|
14
13
|
import { AttachmentOpenerContext } from '../context';
|
|
15
14
|
export function ChatBody(props) {
|
|
16
15
|
const { model } = props;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
export * from './avatar';
|
|
1
2
|
export * from './chat';
|
|
2
|
-
export * from './chat-input';
|
|
3
|
-
export * from './chat-messages';
|
|
4
3
|
export * from './code-blocks';
|
|
5
4
|
export * from './input';
|
|
6
5
|
export * from './jl-theme-provider';
|
|
6
|
+
export * from './messages';
|
|
7
7
|
export * from './mui-extras';
|
|
8
8
|
export * from './scroll-container';
|
|
9
|
-
export * from './toolbar';
|
package/lib/components/index.js
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
* Copyright (c) Jupyter Development Team.
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
|
+
export * from './avatar';
|
|
5
6
|
export * from './chat';
|
|
6
|
-
export * from './chat-input';
|
|
7
|
-
export * from './chat-messages';
|
|
8
7
|
export * from './code-blocks';
|
|
9
8
|
export * from './input';
|
|
10
9
|
export * from './jl-theme-provider';
|
|
10
|
+
export * from './messages';
|
|
11
11
|
export * from './mui-extras';
|
|
12
12
|
export * from './scroll-container';
|
|
13
|
-
export * from './toolbar';
|
|
@@ -15,7 +15,7 @@ const SEND_INCLUDE_LI_CLASS = 'jp-chat-send-include';
|
|
|
15
15
|
* The send button, with optional 'include selection' menu.
|
|
16
16
|
*/
|
|
17
17
|
export function SendButton(props) {
|
|
18
|
-
const { model } = props;
|
|
18
|
+
const { model, chatCommandRegistry } = props;
|
|
19
19
|
const { activeCellManager, selectionWatcher } = model;
|
|
20
20
|
const hideIncludeSelection = !activeCellManager || !selectionWatcher;
|
|
21
21
|
const [menuAnchorEl, setMenuAnchorEl] = useState(null);
|
|
@@ -77,21 +77,31 @@ export function SendButton(props) {
|
|
|
77
77
|
activeCellManager === null || activeCellManager === void 0 ? void 0 : activeCellManager.availabilityChanged.disconnect(toggleIncludeState);
|
|
78
78
|
};
|
|
79
79
|
}, [activeCellManager, selectionWatcher]);
|
|
80
|
-
function
|
|
80
|
+
async function send() {
|
|
81
|
+
await (chatCommandRegistry === null || chatCommandRegistry === void 0 ? void 0 : chatCommandRegistry.onSubmit(model));
|
|
82
|
+
model.send(model.value);
|
|
83
|
+
}
|
|
84
|
+
async function sendWithSelection() {
|
|
81
85
|
let source = '';
|
|
86
|
+
// Run all chat command providers
|
|
87
|
+
await (chatCommandRegistry === null || chatCommandRegistry === void 0 ? void 0 : chatCommandRegistry.onSubmit(model));
|
|
88
|
+
let language;
|
|
82
89
|
if (selectionWatcher === null || selectionWatcher === void 0 ? void 0 : selectionWatcher.selection) {
|
|
83
90
|
// Append the selected text if exists.
|
|
84
91
|
source = selectionWatcher.selection.text;
|
|
92
|
+
language = selectionWatcher.selection.language;
|
|
85
93
|
}
|
|
86
94
|
else if (activeCellManager === null || activeCellManager === void 0 ? void 0 : activeCellManager.available) {
|
|
87
95
|
// Append the active cell content if exists.
|
|
88
|
-
|
|
96
|
+
const content = activeCellManager.getContent(false);
|
|
97
|
+
source = content.source;
|
|
98
|
+
language = content === null || content === void 0 ? void 0 : content.language;
|
|
89
99
|
}
|
|
90
100
|
let content = model.value;
|
|
91
101
|
if (source) {
|
|
92
102
|
content += `
|
|
93
103
|
|
|
94
|
-
|
|
104
|
+
\`\`\`${language !== null && language !== void 0 ? language : ''}
|
|
95
105
|
${source}
|
|
96
106
|
\`\`\`
|
|
97
107
|
`;
|
|
@@ -101,7 +111,7 @@ ${source}
|
|
|
101
111
|
model.value = '';
|
|
102
112
|
}
|
|
103
113
|
return (React.createElement(React.Fragment, null,
|
|
104
|
-
React.createElement(TooltippedButton, { onClick:
|
|
114
|
+
React.createElement(TooltippedButton, { onClick: send, disabled: disabled, tooltip: tooltip, buttonProps: {
|
|
105
115
|
size: 'small',
|
|
106
116
|
title: tooltip,
|
|
107
117
|
variant: 'contained',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { SxProps, Theme } from '@mui/material';
|
|
3
|
-
import { IInputToolbarRegistry } from '
|
|
4
|
-
import { IInputModel } from '
|
|
5
|
-
import { IChatCommandRegistry } from '
|
|
3
|
+
import { IInputToolbarRegistry } from '.';
|
|
4
|
+
import { IInputModel } from '../../input-model';
|
|
5
|
+
import { IChatCommandRegistry } from '../../registers';
|
|
6
6
|
export declare function ChatInput(props: ChatInput.IProps): JSX.Element;
|
|
7
7
|
/**
|
|
8
8
|
* The chat input namespace.
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import { Autocomplete, Box, InputAdornment, TextField } from '@mui/material';
|
|
6
6
|
import clsx from 'clsx';
|
|
7
7
|
import React, { useEffect, useRef, useState } from 'react';
|
|
8
|
-
import { AttachmentPreviewList } from '
|
|
9
|
-
import { useChatCommands } from '
|
|
8
|
+
import { AttachmentPreviewList } from '../attachments';
|
|
9
|
+
import { useChatCommands } from '.';
|
|
10
10
|
const INPUT_BOX_CLASS = 'jp-chat-input-container';
|
|
11
11
|
const INPUT_TOOLBAR_CLASS = 'jp-chat-input-toolbar';
|
|
12
12
|
export function ChatInput(props) {
|
|
@@ -72,7 +72,8 @@ export function ChatInput(props) {
|
|
|
72
72
|
* "Enter". This also handles many of the edge cases in the MUI Autocomplete
|
|
73
73
|
* component.
|
|
74
74
|
*/
|
|
75
|
-
function handleKeyDown(event) {
|
|
75
|
+
async function handleKeyDown(event) {
|
|
76
|
+
var _a;
|
|
76
77
|
/**
|
|
77
78
|
* IMPORTANT: This statement ensures that arrow keys can be used to navigate
|
|
78
79
|
* the multiline input when the chat commands menu is closed.
|
|
@@ -115,6 +116,8 @@ export function ChatInput(props) {
|
|
|
115
116
|
// Finally, send the message when all other conditions are met.
|
|
116
117
|
if ((sendWithShiftEnter && event.shiftKey) ||
|
|
117
118
|
(!sendWithShiftEnter && !event.shiftKey)) {
|
|
119
|
+
// Run all command providers
|
|
120
|
+
await ((_a = props.chatCommandRegistry) === null || _a === void 0 ? void 0 : _a.onSubmit(model));
|
|
118
121
|
model.send(input);
|
|
119
122
|
event.stopPropagation();
|
|
120
123
|
event.preventDefault();
|
|
@@ -153,7 +156,7 @@ export function ChatInput(props) {
|
|
|
153
156
|
}
|
|
154
157
|
}, renderInput: params => (React.createElement(TextField, { ...params, fullWidth: true, variant: "outlined", multiline: true, onKeyDown: handleKeyDown, placeholder: "Start chatting", inputRef: inputRef, sx: { marginTop: '1px' }, onSelect: () => { var _a, _b; return (model.cursorIndex = (_b = (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.selectionStart) !== null && _b !== void 0 ? _b : null); }, InputProps: {
|
|
155
158
|
...params.InputProps,
|
|
156
|
-
endAdornment: (React.createElement(InputAdornment, { position: "end", className: INPUT_TOOLBAR_CLASS }, toolbarElements.map(item => (React.createElement(item.element, { model: model })))))
|
|
159
|
+
endAdornment: (React.createElement(InputAdornment, { position: "end", className: INPUT_TOOLBAR_CLASS }, toolbarElements.map(item => (React.createElement(item.element, { model: model, chatCommandRegistry: props.chatCommandRegistry })))))
|
|
157
160
|
}, FormHelperTextProps: {
|
|
158
161
|
sx: { marginLeft: 'auto', marginRight: 0 }
|
|
159
162
|
}, helperText: input.length > 2 ? helperText : ' ' })), inputValue: input, onInputChange: (_, newValue, reason) => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { IInputModel } from '../../input-model';
|
|
3
3
|
import { ISignal } from '@lumino/signaling';
|
|
4
|
+
import { IChatCommandRegistry } from '../../registers';
|
|
4
5
|
/**
|
|
5
6
|
* The toolbar registry interface.
|
|
6
7
|
*/
|
|
@@ -90,6 +91,11 @@ export declare namespace InputToolbarRegistry {
|
|
|
90
91
|
* The input model of the input component including the button.
|
|
91
92
|
*/
|
|
92
93
|
model: IInputModel;
|
|
94
|
+
/**
|
|
95
|
+
* Chat command registry. Should be used by the "Send" button to run
|
|
96
|
+
* `onSubmit()` on all command providers before sending the message.
|
|
97
|
+
*/
|
|
98
|
+
chatCommandRegistry?: IChatCommandRegistry;
|
|
93
99
|
}
|
|
94
100
|
/**
|
|
95
101
|
* The default toolbar registry if none is provided.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AutocompleteProps as GenericAutocompleteProps } from '@mui/material';
|
|
2
|
-
import { IChatCommandRegistry } from '../../
|
|
2
|
+
import { IChatCommandRegistry } from '../../registers';
|
|
3
3
|
import { IInputModel } from '../../input-model';
|
|
4
4
|
type AutocompleteProps = GenericAutocompleteProps<any, any, any, any>;
|
|
5
5
|
type UseChatCommandsReturn = {
|
|
@@ -20,6 +20,9 @@ export function useChatCommands(inputModel, chatCommandRegistry) {
|
|
|
20
20
|
// the current word is the space-separated word at the user's cursor.
|
|
21
21
|
const [commands, setCommands] = useState([]);
|
|
22
22
|
useEffect(() => {
|
|
23
|
+
/**
|
|
24
|
+
* Callback that runs whenever the current word changes.
|
|
25
|
+
*/
|
|
23
26
|
async function getCommands(_, currentWord) {
|
|
24
27
|
const providers = chatCommandRegistry === null || chatCommandRegistry === void 0 ? void 0 : chatCommandRegistry.getProviders();
|
|
25
28
|
if (!providers) {
|
|
@@ -31,20 +34,29 @@ export function useChatCommands(inputModel, chatCommandRegistry) {
|
|
|
31
34
|
setHighlighted(false);
|
|
32
35
|
return;
|
|
33
36
|
}
|
|
34
|
-
let
|
|
37
|
+
let commandCompletions = [];
|
|
35
38
|
for (const provider of providers) {
|
|
36
39
|
// TODO: optimize performance when this method is truly async
|
|
37
40
|
try {
|
|
38
|
-
|
|
41
|
+
commandCompletions = commandCompletions.concat(await provider.listCommandCompletions(inputModel));
|
|
39
42
|
}
|
|
40
43
|
catch (e) {
|
|
41
44
|
console.error(`Error when getting chat commands from command provider '${provider.id}': `, e);
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
|
-
if
|
|
45
|
-
|
|
47
|
+
// Immediately replace the current word if it exactly matches one command
|
|
48
|
+
// and 'replaceWith' is set.
|
|
49
|
+
if (commandCompletions.length === 1 &&
|
|
50
|
+
commandCompletions[0].name === inputModel.currentWord &&
|
|
51
|
+
commandCompletions[0].replaceWith !== undefined) {
|
|
52
|
+
const replacement = commandCompletions[0].replaceWith;
|
|
53
|
+
inputModel.replaceCurrentWord(replacement);
|
|
54
|
+
return;
|
|
46
55
|
}
|
|
47
|
-
|
|
56
|
+
// Otherwise, open/close the menu based on the presence of command
|
|
57
|
+
// completions and set the menu entries.
|
|
58
|
+
setOpen(!!commandCompletions.length);
|
|
59
|
+
setCommands(commandCompletions);
|
|
48
60
|
}
|
|
49
61
|
inputModel.currentWordChanged.connect(getCommands);
|
|
50
62
|
return () => {
|
|
@@ -53,7 +65,8 @@ export function useChatCommands(inputModel, chatCommandRegistry) {
|
|
|
53
65
|
}, [inputModel]);
|
|
54
66
|
/**
|
|
55
67
|
* onChange(): the callback invoked when a command is selected from the chat
|
|
56
|
-
* commands menu
|
|
68
|
+
* commands menu. When a command `cmd` is selected, this function replaces the
|
|
69
|
+
* current word with `cmd.replaceWith` if set, `cmd.name` otherwise.
|
|
57
70
|
*/
|
|
58
71
|
const onChange = (e, command, reason) => {
|
|
59
72
|
if (reason !== 'selectOption') {
|
|
@@ -68,13 +81,11 @@ export function useChatCommands(inputModel, chatCommandRegistry) {
|
|
|
68
81
|
if (!currentWord) {
|
|
69
82
|
return;
|
|
70
83
|
}
|
|
71
|
-
|
|
72
|
-
if (command.
|
|
73
|
-
|
|
74
|
-
return;
|
|
84
|
+
let replacement = command.replaceWith === undefined ? command.name : command.replaceWith;
|
|
85
|
+
if (command.spaceOnAccept) {
|
|
86
|
+
replacement += ' ';
|
|
75
87
|
}
|
|
76
|
-
|
|
77
|
-
chatCommandRegistry.handleChatCommand(command, inputModel);
|
|
88
|
+
inputModel.replaceCurrentWord(replacement);
|
|
78
89
|
};
|
|
79
90
|
return {
|
|
80
91
|
autocompleteProps: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import { IMessageFooterRegistry, MessageFooterSectionProps } from '../../
|
|
2
|
+
import { IMessageFooterRegistry, MessageFooterSectionProps } from '../../registers';
|
|
3
3
|
/**
|
|
4
4
|
* The chat footer component properties.
|
|
5
5
|
*/
|
|
@@ -13,4 +13,4 @@ export interface IMessageFootersProps extends MessageFooterSectionProps {
|
|
|
13
13
|
* The chat footer component, which displays footer components on a row according to
|
|
14
14
|
* their respective positions.
|
|
15
15
|
*/
|
|
16
|
-
export declare function
|
|
16
|
+
export declare function MessageFooterComponent(props: IMessageFootersProps): JSX.Element;
|
|
@@ -8,7 +8,7 @@ import React from 'react';
|
|
|
8
8
|
* The chat footer component, which displays footer components on a row according to
|
|
9
9
|
* their respective positions.
|
|
10
10
|
*/
|
|
11
|
-
export function
|
|
11
|
+
export function MessageFooterComponent(props) {
|
|
12
12
|
var _a, _b, _c;
|
|
13
13
|
const { message, model, registry } = props;
|
|
14
14
|
const footer = registry.getFooter();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { IChatMessage } from '../../types';
|
|
3
|
+
/**
|
|
4
|
+
* The message header props.
|
|
5
|
+
*/
|
|
6
|
+
type ChatMessageHeaderProps = {
|
|
7
|
+
/**
|
|
8
|
+
* The chat message.
|
|
9
|
+
*/
|
|
10
|
+
message: IChatMessage;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* The message header component.
|
|
14
|
+
*/
|
|
15
|
+
export declare function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { Box, Typography } from '@mui/material';
|
|
6
|
+
import React, { useEffect, useState } from 'react';
|
|
7
|
+
import { Avatar } from '../avatar';
|
|
8
|
+
const MESSAGE_HEADER_CLASS = 'jp-chat-message-header';
|
|
9
|
+
const MESSAGE_TIME_CLASS = 'jp-chat-message-time';
|
|
10
|
+
/**
|
|
11
|
+
* The message header component.
|
|
12
|
+
*/
|
|
13
|
+
export function ChatMessageHeader(props) {
|
|
14
|
+
var _a, _b;
|
|
15
|
+
const [datetime, setDatetime] = useState({});
|
|
16
|
+
const message = props.message;
|
|
17
|
+
const sender = message.sender;
|
|
18
|
+
/**
|
|
19
|
+
* Effect: update cached datetime strings upon receiving a new message.
|
|
20
|
+
*/
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!datetime[message.time]) {
|
|
23
|
+
const newDatetime = {};
|
|
24
|
+
let datetime;
|
|
25
|
+
const currentDate = new Date();
|
|
26
|
+
const sameDay = (date) => date.getFullYear() === currentDate.getFullYear() &&
|
|
27
|
+
date.getMonth() === currentDate.getMonth() &&
|
|
28
|
+
date.getDate() === currentDate.getDate();
|
|
29
|
+
const msgDate = new Date(message.time * 1000); // Convert message time to milliseconds
|
|
30
|
+
// Display only the time if the day of the message is the current one.
|
|
31
|
+
if (sameDay(msgDate)) {
|
|
32
|
+
// Use the browser's default locale
|
|
33
|
+
datetime = msgDate.toLocaleTimeString([], {
|
|
34
|
+
hour: 'numeric',
|
|
35
|
+
minute: '2-digit'
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// Use the browser's default locale
|
|
40
|
+
datetime = msgDate.toLocaleString([], {
|
|
41
|
+
day: 'numeric',
|
|
42
|
+
month: 'numeric',
|
|
43
|
+
year: 'numeric',
|
|
44
|
+
hour: 'numeric',
|
|
45
|
+
minute: '2-digit'
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
newDatetime[message.time] = datetime;
|
|
49
|
+
setDatetime(newDatetime);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const avatar = message.stacked ? null : Avatar({ user: sender });
|
|
53
|
+
const name = (_b = (_a = sender.display_name) !== null && _a !== void 0 ? _a : sender.name) !== null && _b !== void 0 ? _b : (sender.username || 'User undefined');
|
|
54
|
+
return (React.createElement(Box, { className: MESSAGE_HEADER_CLASS, sx: {
|
|
55
|
+
display: 'flex',
|
|
56
|
+
alignItems: 'center',
|
|
57
|
+
'& > :not(:last-child)': {
|
|
58
|
+
marginRight: 3
|
|
59
|
+
},
|
|
60
|
+
marginBottom: message.stacked ? '0px' : '12px'
|
|
61
|
+
} },
|
|
62
|
+
avatar,
|
|
63
|
+
React.createElement(Box, { sx: {
|
|
64
|
+
display: 'flex',
|
|
65
|
+
flexGrow: 1,
|
|
66
|
+
flexWrap: 'wrap',
|
|
67
|
+
justifyContent: 'space-between',
|
|
68
|
+
alignItems: 'center'
|
|
69
|
+
} },
|
|
70
|
+
React.createElement(Box, { sx: { display: 'flex', alignItems: 'center' } },
|
|
71
|
+
!message.stacked && (React.createElement(Typography, { sx: {
|
|
72
|
+
fontWeight: 700,
|
|
73
|
+
color: 'var(--jp-ui-font-color1)',
|
|
74
|
+
paddingRight: '0.5em'
|
|
75
|
+
} }, name)),
|
|
76
|
+
(message.deleted || message.edited) && (React.createElement(Typography, { sx: {
|
|
77
|
+
fontStyle: 'italic',
|
|
78
|
+
fontSize: 'var(--jp-content-font-size0)'
|
|
79
|
+
} }, message.deleted ? '(message deleted)' : '(edited)'))),
|
|
80
|
+
React.createElement(Typography, { className: MESSAGE_TIME_CLASS, sx: {
|
|
81
|
+
fontSize: '0.8em',
|
|
82
|
+
color: 'var(--jp-ui-font-color2)',
|
|
83
|
+
fontWeight: 300
|
|
84
|
+
}, title: message.raw_time ? 'Unverified time' : '' }, `${datetime[message.time]}${message.raw_time ? '*' : ''}`))));
|
|
85
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './footer';
|
|
2
|
+
export * from './header';
|
|
3
|
+
export * from './message';
|
|
4
|
+
export * from './message-renderer';
|
|
5
|
+
export * from './messages';
|
|
6
|
+
export * from './navigation';
|
|
7
|
+
export * from './toolbar';
|
|
8
|
+
export * from './welcome';
|
|
9
|
+
export * from './writers';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Jupyter Development Team.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
export * from './footer';
|
|
6
|
+
export * from './header';
|
|
7
|
+
export * from './message';
|
|
8
|
+
export * from './message-renderer';
|
|
9
|
+
export * from './messages';
|
|
10
|
+
export * from './navigation';
|
|
11
|
+
export * from './toolbar';
|
|
12
|
+
export * from './welcome';
|
|
13
|
+
export * from './writers';
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import React, { useState, useEffect } from 'react';
|
|
6
6
|
import { createPortal } from 'react-dom';
|
|
7
7
|
import { CodeToolbar } from '../code-blocks/code-toolbar';
|
|
8
|
-
import { MessageToolbar } from '
|
|
8
|
+
import { MessageToolbar } from './toolbar';
|
|
9
9
|
import { MarkdownRenderer, MD_RENDERED_CLASS } from '../../markdown-renderer';
|
|
10
10
|
/**
|
|
11
11
|
* The message renderer base component.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PromiseDelegate } from '@lumino/coreutils';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { BaseMessageProps } from './messages';
|
|
4
|
+
import { IChatMessage } from '../../types';
|
|
5
|
+
/**
|
|
6
|
+
* The message component body.
|
|
7
|
+
*/
|
|
8
|
+
export declare const ChatMessage: React.ForwardRefExoticComponent<BaseMessageProps & {
|
|
9
|
+
/**
|
|
10
|
+
* The message to display.
|
|
11
|
+
*/
|
|
12
|
+
message: IChatMessage;
|
|
13
|
+
/**
|
|
14
|
+
* The index of the message in the list.
|
|
15
|
+
*/
|
|
16
|
+
index: number;
|
|
17
|
+
/**
|
|
18
|
+
* The promise to resolve when the message is rendered.
|
|
19
|
+
*/
|
|
20
|
+
renderedPromise: PromiseDelegate<void>;
|
|
21
|
+
} & React.RefAttributes<HTMLDivElement>>;
|