@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.
Files changed (66) hide show
  1. package/lib/__tests__/model.spec.d.ts +1 -0
  2. package/lib/__tests__/model.spec.js +72 -0
  3. package/lib/__tests__/widgets.spec.d.ts +1 -0
  4. package/lib/__tests__/widgets.spec.js +33 -0
  5. package/lib/components/chat-input.d.ts +33 -0
  6. package/lib/components/chat-input.js +60 -0
  7. package/lib/components/chat-messages.d.ts +32 -0
  8. package/lib/components/chat-messages.js +162 -0
  9. package/lib/components/chat.d.ts +43 -0
  10. package/lib/components/chat.js +100 -0
  11. package/lib/components/copy-button.d.ts +6 -0
  12. package/lib/components/copy-button.js +35 -0
  13. package/lib/components/jl-theme-provider.d.ts +6 -0
  14. package/lib/components/jl-theme-provider.js +19 -0
  15. package/lib/components/mui-extras/stacking-alert.d.ts +28 -0
  16. package/lib/components/mui-extras/stacking-alert.js +56 -0
  17. package/lib/components/rendermime-markdown.d.ts +12 -0
  18. package/lib/components/rendermime-markdown.js +54 -0
  19. package/lib/components/scroll-container.d.ts +23 -0
  20. package/lib/components/scroll-container.js +51 -0
  21. package/lib/components/toolbar.d.ts +11 -0
  22. package/lib/components/toolbar.js +30 -0
  23. package/lib/icons.d.ts +2 -0
  24. package/lib/icons.js +11 -0
  25. package/lib/index.d.ts +6 -0
  26. package/lib/index.js +10 -0
  27. package/lib/model.d.ts +177 -0
  28. package/lib/model.js +128 -0
  29. package/lib/theme-provider.d.ts +3 -0
  30. package/lib/theme-provider.js +133 -0
  31. package/lib/types.d.ts +49 -0
  32. package/lib/types.js +5 -0
  33. package/lib/widgets/chat-error.d.ts +2 -0
  34. package/lib/widgets/chat-error.js +26 -0
  35. package/lib/widgets/chat-sidebar.d.ts +4 -0
  36. package/lib/widgets/chat-sidebar.js +15 -0
  37. package/lib/widgets/chat-widget.d.ts +19 -0
  38. package/lib/widgets/chat-widget.js +28 -0
  39. package/package.json +209 -0
  40. package/src/__tests__/model.spec.ts +84 -0
  41. package/src/__tests__/widgets.spec.ts +43 -0
  42. package/src/components/chat-input.tsx +143 -0
  43. package/src/components/chat-messages.tsx +283 -0
  44. package/src/components/chat.tsx +179 -0
  45. package/src/components/copy-button.tsx +55 -0
  46. package/src/components/jl-theme-provider.tsx +28 -0
  47. package/src/components/mui-extras/stacking-alert.tsx +105 -0
  48. package/src/components/rendermime-markdown.tsx +88 -0
  49. package/src/components/scroll-container.tsx +74 -0
  50. package/src/components/toolbar.tsx +50 -0
  51. package/src/icons.ts +15 -0
  52. package/src/index.ts +11 -0
  53. package/src/model.ts +272 -0
  54. package/src/theme-provider.ts +137 -0
  55. package/src/types/mui.d.ts +18 -0
  56. package/src/types/svg.d.ts +17 -0
  57. package/src/types.ts +58 -0
  58. package/src/widgets/chat-error.tsx +43 -0
  59. package/src/widgets/chat-sidebar.tsx +30 -0
  60. package/src/widgets/chat-widget.tsx +51 -0
  61. package/style/base.css +13 -0
  62. package/style/chat-settings.css +10 -0
  63. package/style/chat.css +53 -0
  64. package/style/icons/chat.svg +6 -0
  65. package/style/index.css +6 -0
  66. package/style/index.js +6 -0
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,72 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * Example of [Jest](https://jestjs.io/docs/getting-started) unit tests
7
+ */
8
+ import { ChatModel } from '../model';
9
+ describe('test chat model', () => {
10
+ describe('model instantiation', () => {
11
+ it('should create a ChatModel', () => {
12
+ const model = new ChatModel();
13
+ expect(model).toBeInstanceOf(ChatModel);
14
+ });
15
+ it('should dispose a ChatModel', () => {
16
+ const model = new ChatModel();
17
+ model.dispose();
18
+ expect(model.isDisposed).toBeTruthy();
19
+ });
20
+ });
21
+ describe('incoming message', () => {
22
+ class TestChat extends ChatModel {
23
+ formatChatMessage(message) {
24
+ message.body = 'formatted msg';
25
+ return message;
26
+ }
27
+ }
28
+ let model;
29
+ let messages;
30
+ const msg = {
31
+ type: 'msg',
32
+ id: 'message1',
33
+ time: Date.now() / 1000,
34
+ body: 'message test',
35
+ sender: { username: 'user' }
36
+ };
37
+ beforeEach(() => {
38
+ messages = [];
39
+ });
40
+ it('should signal incoming message', () => {
41
+ model = new ChatModel();
42
+ model.messagesUpdated.connect((sender) => {
43
+ expect(sender).toBe(model);
44
+ messages = model.messages;
45
+ });
46
+ model.messageAdded(msg);
47
+ expect(messages).toHaveLength(1);
48
+ expect(messages[0]).toBe(msg);
49
+ });
50
+ it('should format message', () => {
51
+ model = new TestChat();
52
+ model.messagesUpdated.connect((sender) => {
53
+ expect(sender).toBe(model);
54
+ messages = model.messages;
55
+ });
56
+ model.messageAdded({ ...msg });
57
+ expect(messages).toHaveLength(1);
58
+ expect(messages[0]).not.toBe(msg);
59
+ expect(messages[0].body).toBe('formatted msg');
60
+ });
61
+ });
62
+ describe('model config', () => {
63
+ it('should have empty config', () => {
64
+ const model = new ChatModel();
65
+ expect(model.config.sendWithShiftEnter).toBeUndefined();
66
+ });
67
+ it('should allow config', () => {
68
+ const model = new ChatModel({ config: { sendWithShiftEnter: true } });
69
+ expect(model.config.sendWithShiftEnter).toBeTruthy();
70
+ });
71
+ });
72
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,33 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * Example of [Jest](https://jestjs.io/docs/getting-started) unit tests
7
+ */
8
+ import { RenderMimeRegistry } from '@jupyterlab/rendermime';
9
+ import { ChatModel } from '../model';
10
+ import { ChatWidget } from '../widgets/chat-widget';
11
+ describe('test chat widget', () => {
12
+ let model;
13
+ let rmRegistry;
14
+ beforeEach(() => {
15
+ model = new ChatModel();
16
+ rmRegistry = new RenderMimeRegistry();
17
+ });
18
+ describe('model instantiation', () => {
19
+ it('should create a ChatModel', () => {
20
+ const widget = new ChatWidget({ model, rmRegistry });
21
+ expect(widget).toBeInstanceOf(ChatWidget);
22
+ });
23
+ it('should dispose a ChatModel', () => {
24
+ const widget = new ChatWidget({ model, rmRegistry });
25
+ widget.dispose();
26
+ expect(widget.isDisposed).toBeTruthy();
27
+ });
28
+ it('should provides the model', () => {
29
+ const widget = new ChatWidget({ model, rmRegistry });
30
+ expect(widget.model).toBe(model);
31
+ });
32
+ });
33
+ });
@@ -0,0 +1,33 @@
1
+ /// <reference types="react" />
2
+ import { SxProps, Theme } from '@mui/material';
3
+ export declare function ChatInput(props: ChatInput.IProps): JSX.Element;
4
+ /**
5
+ * The chat input namespace.
6
+ */
7
+ export declare namespace ChatInput {
8
+ /**
9
+ * The properties of the react element.
10
+ */
11
+ interface IProps {
12
+ /**
13
+ * The initial value of the input (default to '')
14
+ */
15
+ value?: string;
16
+ /**
17
+ * The function to be called to send the message.
18
+ */
19
+ onSend: (input: string) => unknown;
20
+ /**
21
+ * The function to be called to cancel editing.
22
+ */
23
+ onCancel?: () => unknown;
24
+ /**
25
+ * Whether using shift+enter to send the message.
26
+ */
27
+ sendWithShiftEnter: boolean;
28
+ /**
29
+ * Custom mui/material styles.
30
+ */
31
+ sx?: SxProps<Theme>;
32
+ }
33
+ }
@@ -0,0 +1,60 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import React, { useState } from 'react';
6
+ import { Box, TextField, IconButton, InputAdornment } from '@mui/material';
7
+ import { Send, Cancel } from '@mui/icons-material';
8
+ import clsx from 'clsx';
9
+ const INPUT_BOX_CLASS = 'jp-chat-input-container';
10
+ const SEND_BUTTON_CLASS = 'jp-chat-send-button';
11
+ const CANCEL_BUTTON_CLASS = 'jp-chat-cancel-button';
12
+ export function ChatInput(props) {
13
+ const [input, setInput] = useState(props.value || '');
14
+ function handleKeyDown(event) {
15
+ if (event.key === 'Enter' &&
16
+ ((props.sendWithShiftEnter && event.shiftKey) ||
17
+ (!props.sendWithShiftEnter && !event.shiftKey))) {
18
+ onSend();
19
+ event.stopPropagation();
20
+ event.preventDefault();
21
+ }
22
+ }
23
+ /**
24
+ * Triggered when sending the message.
25
+ */
26
+ function onSend() {
27
+ setInput('');
28
+ props.onSend(input);
29
+ }
30
+ /**
31
+ * Triggered when cancelling edition.
32
+ */
33
+ function onCancel() {
34
+ setInput(props.value || '');
35
+ props.onCancel();
36
+ }
37
+ // Set the helper text based on whether Shift+Enter is used for sending.
38
+ const helperText = props.sendWithShiftEnter ? (React.createElement("span", null,
39
+ "Press ",
40
+ React.createElement("b", null, "Shift"),
41
+ "+",
42
+ React.createElement("b", null, "Enter"),
43
+ " to send message")) : (React.createElement("span", null,
44
+ "Press ",
45
+ React.createElement("b", null, "Shift"),
46
+ "+",
47
+ React.createElement("b", null, "Enter"),
48
+ " to add a new line"));
49
+ return (React.createElement(Box, { sx: props.sx, className: clsx(INPUT_BOX_CLASS) },
50
+ React.createElement(Box, { sx: { display: 'flex' } },
51
+ React.createElement(TextField, { value: input, onChange: e => setInput(e.target.value), fullWidth: true, variant: "outlined", multiline: true, onKeyDown: handleKeyDown, placeholder: "Start chatting", InputProps: {
52
+ endAdornment: (React.createElement(InputAdornment, { position: "end" },
53
+ props.onCancel && (React.createElement(IconButton, { size: "small", color: "primary", onClick: onCancel, disabled: !input.trim().length, title: 'Cancel edition', className: clsx(CANCEL_BUTTON_CLASS) },
54
+ React.createElement(Cancel, null))),
55
+ React.createElement(IconButton, { size: "small", color: "primary", onClick: onSend, disabled: !input.trim().length, title: `Send message ${props.sendWithShiftEnter ? '(SHIFT+ENTER)' : '(ENTER)'}`, className: clsx(SEND_BUTTON_CLASS) },
56
+ React.createElement(Send, null))))
57
+ }, FormHelperTextProps: {
58
+ sx: { marginLeft: 'auto', marginRight: 0 }
59
+ }, helperText: input.length > 2 ? helperText : ' ' }))));
60
+ }
@@ -0,0 +1,32 @@
1
+ /// <reference types="react" />
2
+ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
3
+ import type { SxProps, Theme } from '@mui/material';
4
+ import { IChatModel } from '../model';
5
+ import { IChatMessage, IUser } from '../types';
6
+ type BaseMessageProps = {
7
+ rmRegistry: IRenderMimeRegistry;
8
+ model: IChatModel;
9
+ };
10
+ type ChatMessageProps = BaseMessageProps & {
11
+ message: IChatMessage;
12
+ };
13
+ type ChatMessagesProps = BaseMessageProps & {
14
+ messages: IChatMessage[];
15
+ };
16
+ export type ChatMessageHeaderProps = IUser & {
17
+ timestamp: number;
18
+ rawTime?: boolean;
19
+ deleted?: boolean;
20
+ edited?: boolean;
21
+ sx?: SxProps<Theme>;
22
+ };
23
+ export declare function ChatMessageHeader(props: ChatMessageHeaderProps): JSX.Element;
24
+ /**
25
+ * The messages list UI.
26
+ */
27
+ export declare function ChatMessages(props: ChatMessagesProps): JSX.Element;
28
+ /**
29
+ * the message UI.
30
+ */
31
+ export declare function ChatMessage(props: ChatMessageProps): JSX.Element;
32
+ export {};
@@ -0,0 +1,162 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import { Avatar, Box, Typography } from '@mui/material';
6
+ import clsx from 'clsx';
7
+ import React, { useState, useEffect } from 'react';
8
+ import { ChatInput } from './chat-input';
9
+ import { RendermimeMarkdown } from './rendermime-markdown';
10
+ const MESSAGES_BOX_CLASS = 'jp-chat-messages-container';
11
+ const MESSAGE_CLASS = 'jp-chat-message';
12
+ const MESSAGE_HEADER_CLASS = 'jp-chat-message-header';
13
+ const MESSAGE_TIME_CLASS = 'jp-chat-message-time';
14
+ export function ChatMessageHeader(props) {
15
+ var _a, _b;
16
+ const [datetime, setDatetime] = useState({});
17
+ const sharedStyles = {
18
+ height: '24px',
19
+ width: '24px'
20
+ };
21
+ /**
22
+ * Effect: update cached datetime strings upon receiving a new message.
23
+ */
24
+ useEffect(() => {
25
+ if (!datetime[props.timestamp]) {
26
+ const newDatetime = {};
27
+ let datetime;
28
+ const currentDate = new Date();
29
+ const sameDay = (date) => date.getFullYear() === currentDate.getFullYear() &&
30
+ date.getMonth() === currentDate.getMonth() &&
31
+ date.getDate() === currentDate.getDate();
32
+ const msgDate = new Date(props.timestamp * 1000); // Convert message time to milliseconds
33
+ // Display only the time if the day of the message is the current one.
34
+ if (sameDay(msgDate)) {
35
+ // Use the browser's default locale
36
+ datetime = msgDate.toLocaleTimeString([], {
37
+ hour: 'numeric',
38
+ minute: '2-digit'
39
+ });
40
+ }
41
+ else {
42
+ // Use the browser's default locale
43
+ datetime = msgDate.toLocaleString([], {
44
+ day: 'numeric',
45
+ month: 'numeric',
46
+ year: 'numeric',
47
+ hour: 'numeric',
48
+ minute: '2-digit'
49
+ });
50
+ }
51
+ newDatetime[props.timestamp] = datetime;
52
+ setDatetime(newDatetime);
53
+ }
54
+ });
55
+ const bgcolor = props.color;
56
+ const avatar = props.avatar_url ? (React.createElement(Avatar, { sx: {
57
+ ...sharedStyles,
58
+ ...(bgcolor && { bgcolor })
59
+ }, src: props.avatar_url })) : props.initials ? (React.createElement(Avatar, { sx: {
60
+ ...sharedStyles,
61
+ ...(bgcolor && { bgcolor })
62
+ } },
63
+ React.createElement(Typography, { sx: {
64
+ fontSize: 'var(--jp-ui-font-size1)',
65
+ color: 'var(--jp-ui-inverse-font-color1)'
66
+ } }, props.initials))) : null;
67
+ const name = (_b = (_a = props.display_name) !== null && _a !== void 0 ? _a : props.name) !== null && _b !== void 0 ? _b : (props.username || 'User undefined');
68
+ return (React.createElement(Box, { className: MESSAGE_HEADER_CLASS, sx: {
69
+ display: 'flex',
70
+ alignItems: 'center',
71
+ '& > :not(:last-child)': {
72
+ marginRight: 3
73
+ },
74
+ ...props.sx
75
+ } },
76
+ avatar,
77
+ React.createElement(Box, { sx: {
78
+ display: 'flex',
79
+ flexGrow: 1,
80
+ flexWrap: 'wrap',
81
+ justifyContent: 'space-between',
82
+ alignItems: 'center'
83
+ } },
84
+ React.createElement(Box, { sx: { display: 'flex', alignItems: 'center' } },
85
+ React.createElement(Typography, { sx: { fontWeight: 700, color: 'var(--jp-ui-font-color1)' } }, name),
86
+ (props.deleted || props.edited) && (React.createElement(Typography, { sx: {
87
+ fontStyle: 'italic',
88
+ fontSize: 'var(--jp-content-font-size0)',
89
+ paddingLeft: '0.5em'
90
+ } }, props.deleted ? '(message deleted)' : '(edited)'))),
91
+ React.createElement(Typography, { className: MESSAGE_TIME_CLASS, sx: {
92
+ fontSize: '0.8em',
93
+ color: 'var(--jp-ui-font-color2)',
94
+ fontWeight: 300
95
+ }, title: props.rawTime ? 'Unverified time' : '' }, `${datetime[props.timestamp]}${props.rawTime ? '*' : ''}`))));
96
+ }
97
+ /**
98
+ * The messages list UI.
99
+ */
100
+ export function ChatMessages(props) {
101
+ return (React.createElement(Box, { sx: {
102
+ '& > :not(:last-child)': {
103
+ borderBottom: '1px solid var(--jp-border-color2)'
104
+ }
105
+ }, className: clsx(MESSAGES_BOX_CLASS) }, props.messages.map((message, i) => {
106
+ let sender;
107
+ if (typeof message.sender === 'string') {
108
+ sender = { username: message.sender };
109
+ }
110
+ else {
111
+ sender = message.sender;
112
+ }
113
+ return (
114
+ // extra div needed to ensure each bubble is on a new line
115
+ React.createElement(Box, { key: i, sx: { padding: '1em 1em 0 1em' }, className: clsx(MESSAGE_CLASS) },
116
+ React.createElement(ChatMessageHeader, { ...sender, timestamp: message.time, rawTime: message.raw_time, deleted: message.deleted, edited: message.edited, sx: { marginBottom: 3 } }),
117
+ React.createElement(ChatMessage, { ...props, message: message })));
118
+ })));
119
+ }
120
+ /**
121
+ * the message UI.
122
+ */
123
+ export function ChatMessage(props) {
124
+ var _a;
125
+ const { message, model, rmRegistry } = props;
126
+ let canEdit = false;
127
+ let canDelete = false;
128
+ if (model.user !== undefined && !message.deleted) {
129
+ const username = typeof message.sender === 'string'
130
+ ? message.sender
131
+ : message.sender.username;
132
+ if (model.user.username === username && model.updateMessage !== undefined) {
133
+ canEdit = true;
134
+ }
135
+ if (model.user.username === username && model.deleteMessage !== undefined) {
136
+ canDelete = true;
137
+ }
138
+ }
139
+ const [edit, setEdit] = useState(false);
140
+ const cancelEdition = () => {
141
+ setEdit(false);
142
+ };
143
+ const updateMessage = (id, input) => {
144
+ if (!canEdit) {
145
+ return;
146
+ }
147
+ // Update the message
148
+ const updatedMessage = { ...message };
149
+ updatedMessage.body = input;
150
+ model.updateMessage(id, updatedMessage);
151
+ setEdit(false);
152
+ };
153
+ const deleteMessage = (id) => {
154
+ if (!canDelete) {
155
+ return;
156
+ }
157
+ // Delete the message
158
+ model.deleteMessage(id);
159
+ };
160
+ // Empty if the message has been deleted
161
+ return message.deleted ? (React.createElement(React.Fragment, null)) : (React.createElement("div", null, edit && canEdit ? (React.createElement(ChatInput, { value: message.body, onSend: (input) => updateMessage(message.id, input), onCancel: () => cancelEdition(), sendWithShiftEnter: (_a = model.config.sendWithShiftEnter) !== null && _a !== void 0 ? _a : false })) : (React.createElement(RendermimeMarkdown, { rmRegistry: rmRegistry, markdownStr: message.body, edit: canEdit ? () => setEdit(true) : undefined, delete: canDelete ? () => deleteMessage(message.id) : undefined }))));
162
+ }
@@ -0,0 +1,43 @@
1
+ /// <reference types="react" />
2
+ import { IThemeManager } from '@jupyterlab/apputils';
3
+ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
4
+ import { IChatModel } from '../model';
5
+ export declare function Chat(props: Chat.IOptions): JSX.Element;
6
+ /**
7
+ * The chat UI namespace
8
+ */
9
+ export declare namespace Chat {
10
+ /**
11
+ * The options to build the Chat UI.
12
+ */
13
+ interface IOptions {
14
+ /**
15
+ * The chat model.
16
+ */
17
+ model: IChatModel;
18
+ /**
19
+ * The rendermime registry.
20
+ */
21
+ rmRegistry: IRenderMimeRegistry;
22
+ /**
23
+ * The theme manager.
24
+ */
25
+ themeManager?: IThemeManager | null;
26
+ /**
27
+ * The view to render.
28
+ */
29
+ chatView?: ChatView;
30
+ /**
31
+ * A settings panel that can be used for dedicated settings (e.g. jupyter-ai)
32
+ */
33
+ settingsPanel?: () => JSX.Element;
34
+ }
35
+ /**
36
+ * The view to render.
37
+ * The settings view is available only if the settings panel is provided in options.
38
+ */
39
+ enum ChatView {
40
+ Chat = 0,
41
+ Settings = 1
42
+ }
43
+ }
@@ -0,0 +1,100 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import ArrowBackIcon from '@mui/icons-material/ArrowBack';
6
+ import SettingsIcon from '@mui/icons-material/Settings';
7
+ import { IconButton } from '@mui/material';
8
+ import { Box } from '@mui/system';
9
+ import React, { useState, useEffect } from 'react';
10
+ import { JlThemeProvider } from './jl-theme-provider';
11
+ import { ChatMessages } from './chat-messages';
12
+ import { ChatInput } from './chat-input';
13
+ import { ScrollContainer } from './scroll-container';
14
+ function ChatBody({ model, rmRegistry: renderMimeRegistry }) {
15
+ var _a;
16
+ const [messages, setMessages] = useState([]);
17
+ /**
18
+ * Effect: fetch history and config on initial render
19
+ */
20
+ useEffect(() => {
21
+ async function fetchHistory() {
22
+ if (!model.getHistory) {
23
+ return;
24
+ }
25
+ model
26
+ .getHistory()
27
+ .then(history => setMessages(history.messages))
28
+ .catch(e => console.error(e));
29
+ }
30
+ fetchHistory();
31
+ }, [model]);
32
+ /**
33
+ * Effect: listen to chat messages
34
+ */
35
+ useEffect(() => {
36
+ function handleChatEvents(_) {
37
+ setMessages([...model.messages]);
38
+ }
39
+ model.messagesUpdated.connect(handleChatEvents);
40
+ return function cleanup() {
41
+ model.messagesUpdated.disconnect(handleChatEvents);
42
+ };
43
+ }, [model]);
44
+ // no need to append to messageGroups imperatively here. all of that is
45
+ // handled by the listeners registered in the effect hooks above.
46
+ const onSend = async (input) => {
47
+ // send message to backend
48
+ model.addMessage({ body: input });
49
+ };
50
+ return (React.createElement(React.Fragment, null,
51
+ React.createElement(ScrollContainer, { sx: { flexGrow: 1 } },
52
+ React.createElement(ChatMessages, { messages: messages, rmRegistry: renderMimeRegistry, model: model })),
53
+ React.createElement(ChatInput, { onSend: onSend, sx: {
54
+ paddingLeft: 4,
55
+ paddingRight: 4,
56
+ paddingTop: 3.5,
57
+ paddingBottom: 0,
58
+ borderTop: '1px solid var(--jp-border-color1)'
59
+ }, sendWithShiftEnter: (_a = model.config.sendWithShiftEnter) !== null && _a !== void 0 ? _a : false })));
60
+ }
61
+ export function Chat(props) {
62
+ var _a;
63
+ const [view, setView] = useState(props.chatView || Chat.ChatView.Chat);
64
+ return (React.createElement(JlThemeProvider, { themeManager: (_a = props.themeManager) !== null && _a !== void 0 ? _a : null },
65
+ React.createElement(Box
66
+ // root box should not include padding as it offsets the vertical
67
+ // scrollbar to the left
68
+ , {
69
+ // root box should not include padding as it offsets the vertical
70
+ // scrollbar to the left
71
+ sx: {
72
+ width: '100%',
73
+ height: '100%',
74
+ boxSizing: 'border-box',
75
+ background: 'var(--jp-layout-color0)',
76
+ display: 'flex',
77
+ flexDirection: 'column'
78
+ } },
79
+ React.createElement(Box, { sx: { display: 'flex', justifyContent: 'space-between' } },
80
+ view !== Chat.ChatView.Chat ? (React.createElement(IconButton, { onClick: () => setView(Chat.ChatView.Chat) },
81
+ React.createElement(ArrowBackIcon, null))) : (React.createElement(Box, null)),
82
+ view === Chat.ChatView.Chat && props.settingsPanel ? (React.createElement(IconButton, { onClick: () => setView(Chat.ChatView.Settings) },
83
+ React.createElement(SettingsIcon, null))) : (React.createElement(Box, null))),
84
+ view === Chat.ChatView.Chat && (React.createElement(ChatBody, { model: props.model, rmRegistry: props.rmRegistry })),
85
+ view === Chat.ChatView.Settings && props.settingsPanel && (React.createElement(props.settingsPanel, null)))));
86
+ }
87
+ /**
88
+ * The chat UI namespace
89
+ */
90
+ (function (Chat) {
91
+ /**
92
+ * The view to render.
93
+ * The settings view is available only if the settings panel is provided in options.
94
+ */
95
+ let ChatView;
96
+ (function (ChatView) {
97
+ ChatView[ChatView["Chat"] = 0] = "Chat";
98
+ ChatView[ChatView["Settings"] = 1] = "Settings";
99
+ })(ChatView = Chat.ChatView || (Chat.ChatView = {}));
100
+ })(Chat || (Chat = {}));
@@ -0,0 +1,6 @@
1
+ /// <reference types="react" />
2
+ type CopyButtonProps = {
3
+ value: string;
4
+ };
5
+ export declare function CopyButton(props: CopyButtonProps): JSX.Element;
6
+ export {};
@@ -0,0 +1,35 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import React, { useState, useCallback } from 'react';
6
+ import { Box, Button } from '@mui/material';
7
+ var CopyStatus;
8
+ (function (CopyStatus) {
9
+ CopyStatus[CopyStatus["None"] = 0] = "None";
10
+ CopyStatus[CopyStatus["Copied"] = 1] = "Copied";
11
+ })(CopyStatus || (CopyStatus = {}));
12
+ const COPYBTN_TEXT_BY_STATUS = {
13
+ [CopyStatus.None]: 'Copy to Clipboard',
14
+ [CopyStatus.Copied]: 'Copied!'
15
+ };
16
+ export function CopyButton(props) {
17
+ const [copyStatus, setCopyStatus] = useState(CopyStatus.None);
18
+ const copy = useCallback(async () => {
19
+ try {
20
+ await navigator.clipboard.writeText(props.value);
21
+ }
22
+ catch (err) {
23
+ console.error('Failed to copy text: ', err);
24
+ setCopyStatus(CopyStatus.None);
25
+ return;
26
+ }
27
+ setCopyStatus(CopyStatus.Copied);
28
+ setTimeout(() => setCopyStatus(CopyStatus.None), 1000);
29
+ }, [props.value]);
30
+ return (React.createElement(Box, { sx: { display: 'flex', flexDirection: 'column' } },
31
+ React.createElement(Button, { onClick: copy, disabled: copyStatus !== CopyStatus.None, "aria-label": "Copy To Clipboard", sx: {
32
+ alignSelf: 'flex-end',
33
+ textTransform: 'none'
34
+ } }, COPYBTN_TEXT_BY_STATUS[copyStatus])));
35
+ }
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import type { IThemeManager } from '@jupyterlab/apputils';
3
+ export declare function JlThemeProvider(props: {
4
+ themeManager: IThemeManager | null;
5
+ children: React.ReactNode;
6
+ }): JSX.Element;
@@ -0,0 +1,19 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ import React, { useState, useEffect } from 'react';
6
+ import { ThemeProvider, createTheme } from '@mui/material/styles';
7
+ import { getJupyterLabTheme } from '../theme-provider';
8
+ export function JlThemeProvider(props) {
9
+ const [theme, setTheme] = useState(createTheme());
10
+ useEffect(() => {
11
+ var _a;
12
+ async function setJlTheme() {
13
+ setTheme(await getJupyterLabTheme());
14
+ }
15
+ setJlTheme();
16
+ (_a = props.themeManager) === null || _a === void 0 ? void 0 : _a.themeChanged.connect(setJlTheme);
17
+ }, []);
18
+ return React.createElement(ThemeProvider, { theme: theme }, props.children);
19
+ }
@@ -0,0 +1,28 @@
1
+ /// <reference types="react" />
2
+ import { AlertColor } from '@mui/material';
3
+ export type StackingAlert = {
4
+ /**
5
+ * A function that triggers an alert. Successive alerts are indicated in the
6
+ * JSX element.
7
+ * @param alertType Type of alert.
8
+ * @param msg Message contained within the alert.
9
+ * @returns
10
+ */
11
+ show: (alertType: AlertColor, msg: string | Error) => void;
12
+ /**
13
+ * The Alert JSX element that should be rendered by the consumer.
14
+ * This will be `null` if no alerts were triggered.
15
+ */
16
+ jsx: JSX.Element | null;
17
+ /**
18
+ * An async function that closes the alert, and returns a Promise that
19
+ * resolves when the onClose animation is completed.
20
+ */
21
+ clear: () => void | Promise<void>;
22
+ };
23
+ /**
24
+ * Hook that returns a function to trigger an alert, and a corresponding alert
25
+ * JSX element for the consumer to render. The number of successive identical
26
+ * alerts `X` is indicated in the element via the suffix "(X)".
27
+ */
28
+ export declare function useStackingAlert(): StackingAlert;