@jupyter/chat 0.18.2 → 0.19.0-alpha.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 (74) hide show
  1. package/lib/components/attachments.js +47 -21
  2. package/lib/components/chat.d.ts +5 -0
  3. package/lib/components/chat.js +7 -6
  4. package/lib/components/code-blocks/code-toolbar.js +29 -10
  5. package/lib/components/code-blocks/copy-button.js +11 -3
  6. package/lib/components/index.d.ts +0 -1
  7. package/lib/components/index.js +0 -1
  8. package/lib/components/input/buttons/attach-button.js +7 -2
  9. package/lib/components/input/buttons/cancel-button.js +12 -10
  10. package/lib/components/input/buttons/index.d.ts +2 -0
  11. package/lib/components/input/buttons/index.js +2 -0
  12. package/lib/components/input/buttons/save-edit-button.d.ts +6 -0
  13. package/lib/components/input/buttons/save-edit-button.js +51 -0
  14. package/lib/components/input/buttons/send-button.d.ts +1 -1
  15. package/lib/components/input/buttons/send-button.js +35 -122
  16. package/lib/components/input/buttons/stop-button.d.ts +6 -0
  17. package/lib/components/input/buttons/stop-button.js +63 -0
  18. package/lib/components/input/chat-input.d.ts +15 -0
  19. package/lib/components/input/chat-input.js +109 -46
  20. package/lib/components/input/index.d.ts +1 -0
  21. package/lib/components/input/index.js +1 -0
  22. package/lib/components/input/toolbar-registry.d.ts +10 -0
  23. package/lib/components/input/toolbar-registry.js +10 -1
  24. package/lib/components/input/use-chat-commands.js +9 -4
  25. package/lib/components/input/writing-indicator.d.ts +15 -0
  26. package/lib/components/input/writing-indicator.js +50 -0
  27. package/lib/components/messages/header.d.ts +4 -0
  28. package/lib/components/messages/header.js +4 -0
  29. package/lib/components/messages/index.d.ts +0 -1
  30. package/lib/components/messages/index.js +0 -1
  31. package/lib/components/messages/message.js +1 -1
  32. package/lib/components/messages/messages.d.ts +5 -0
  33. package/lib/components/messages/messages.js +24 -14
  34. package/lib/components/messages/toolbar.js +37 -15
  35. package/lib/input-model.d.ts +14 -0
  36. package/lib/input-model.js +12 -4
  37. package/lib/model.d.ts +8 -0
  38. package/lib/model.js +6 -0
  39. package/lib/types.d.ts +4 -0
  40. package/lib/widgets/chat-widget.d.ts +4 -0
  41. package/lib/widgets/chat-widget.js +36 -11
  42. package/lib/widgets/multichat-panel.js +2 -1
  43. package/package.json +1 -1
  44. package/src/components/attachments.tsx +70 -33
  45. package/src/components/chat.tsx +13 -4
  46. package/src/components/code-blocks/code-toolbar.tsx +56 -28
  47. package/src/components/code-blocks/copy-button.tsx +21 -12
  48. package/src/components/index.ts +0 -1
  49. package/src/components/input/buttons/attach-button.tsx +8 -2
  50. package/src/components/input/buttons/cancel-button.tsx +20 -15
  51. package/src/components/input/buttons/index.ts +2 -0
  52. package/src/components/input/buttons/save-edit-button.tsx +75 -0
  53. package/src/components/input/buttons/send-button.tsx +50 -167
  54. package/src/components/input/buttons/stop-button.tsx +88 -0
  55. package/src/components/input/chat-input.tsx +188 -83
  56. package/src/components/input/index.ts +1 -0
  57. package/src/components/input/toolbar-registry.tsx +25 -1
  58. package/src/components/input/use-chat-commands.tsx +25 -5
  59. package/src/components/input/writing-indicator.tsx +83 -0
  60. package/src/components/messages/header.tsx +8 -0
  61. package/src/components/messages/index.ts +0 -1
  62. package/src/components/messages/message.tsx +1 -0
  63. package/src/components/messages/messages.tsx +63 -39
  64. package/src/components/messages/toolbar.tsx +51 -21
  65. package/src/input-model.ts +21 -0
  66. package/src/model.ts +12 -0
  67. package/src/types.ts +5 -0
  68. package/src/widgets/chat-widget.tsx +43 -12
  69. package/src/widgets/multichat-panel.tsx +2 -1
  70. package/style/chat.css +13 -141
  71. package/style/input.css +0 -58
  72. package/lib/components/messages/writers.d.ts +0 -16
  73. package/lib/components/messages/writers.js +0 -39
  74. package/src/components/messages/writers.tsx +0 -81
@@ -14,12 +14,11 @@ import { ChatMessageHeader } from './header';
14
14
  import { ChatMessage } from './message';
15
15
  import { Navigation } from './navigation';
16
16
  import { WelcomeMessage } from './welcome';
17
- import { WritingUsersList } from './writers';
18
17
  import { IInputToolbarRegistry } from '../input';
19
18
  import { ScrollContainer } from '../scroll-container';
20
19
  import { IChatCommandRegistry, IMessageFooterRegistry } from '../../registers';
21
20
  import { IChatModel } from '../../model';
22
- import { IChatMessage, IUser } from '../../types';
21
+ import { ChatArea, IChatMessage } from '../../types';
23
22
 
24
23
  export const MESSAGE_CLASS = 'jp-chat-message';
25
24
  const MESSAGES_BOX_CLASS = 'jp-chat-messages-container';
@@ -53,6 +52,10 @@ export type BaseMessageProps = {
53
52
  * The welcome message.
54
53
  */
55
54
  welcomeMessage?: string;
55
+ /**
56
+ * The area where the chat is displayed.
57
+ */
58
+ area?: ChatArea;
56
59
  };
57
60
 
58
61
  /**
@@ -62,7 +65,6 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
62
65
  const { model } = props;
63
66
  const [messages, setMessages] = useState<IChatMessage[]>(model.messages);
64
67
  const refMsgBox = useRef<HTMLDivElement>(null);
65
- const [currentWriters, setCurrentWriters] = useState<IUser[]>([]);
66
68
  const [allRendered, setAllRendered] = useState<boolean>(false);
67
69
 
68
70
  // The list of message DOM and their rendered promises.
@@ -84,7 +86,6 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
84
86
  }
85
87
 
86
88
  fetchHistory();
87
- setCurrentWriters([]);
88
89
  }, [model]);
89
90
 
90
91
  /**
@@ -95,16 +96,10 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
95
96
  setMessages([...model.messages]);
96
97
  }
97
98
 
98
- function handleWritersChange(_: IChatModel, writers: IChatModel.IWriter[]) {
99
- setCurrentWriters(writers.map(writer => writer.user));
100
- }
101
-
102
99
  model.messagesUpdated.connect(handleChatEvents);
103
- model.writersChanged?.connect(handleWritersChange);
104
100
 
105
101
  return function cleanup() {
106
102
  model.messagesUpdated.disconnect(handleChatEvents);
107
- model.writersChanged?.disconnect(handleChatEvents);
108
103
  };
109
104
  }, [model]);
110
105
 
@@ -170,6 +165,7 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
170
165
  };
171
166
  }, [messages, allRendered]);
172
167
 
168
+ const horizontalPadding = props.area === 'main' ? 8 : 4;
173
169
  return (
174
170
  <>
175
171
  <ScrollContainer sx={{ flexGrow: 1 }}>
@@ -179,39 +175,67 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element {
179
175
  content={props.welcomeMessage}
180
176
  />
181
177
  )}
182
- <Box ref={refMsgBox} className={clsx(MESSAGES_BOX_CLASS)}>
183
- {messages.map((message, i) => {
184
- renderedPromise.current[i] = new PromiseDelegate();
185
- return (
186
- // extra div needed to ensure each bubble is on a new line
187
- <Box
188
- key={i}
189
- className={clsx(
190
- MESSAGE_CLASS,
191
- message.stacked ? MESSAGE_STACKED_CLASS : ''
192
- )}
193
- >
194
- <ChatMessageHeader message={message} />
195
- <ChatMessage
196
- {...props}
197
- message={message}
198
- index={i}
199
- renderedPromise={renderedPromise.current[i]}
200
- ref={el => (listRef.current[i] = el)}
201
- />
202
- {props.messageFooterRegistry && (
203
- <MessageFooterComponent
204
- registry={props.messageFooterRegistry}
178
+ <Box
179
+ sx={{
180
+ paddingLeft: horizontalPadding,
181
+ paddingRight: horizontalPadding,
182
+ paddingTop: 4,
183
+ paddingBottom: 16,
184
+ display: 'flex',
185
+ flexDirection: 'column',
186
+ gap: 4
187
+ }}
188
+ ref={refMsgBox}
189
+ className={clsx(MESSAGES_BOX_CLASS)}
190
+ >
191
+ {messages
192
+ .filter(message => !message.deleted)
193
+ .map((message, i) => {
194
+ renderedPromise.current[i] = new PromiseDelegate();
195
+ const isCurrentUser =
196
+ model.user !== undefined &&
197
+ model.user.username === message.sender.username;
198
+ return (
199
+ // extra div needed to ensure each bubble is on a new line
200
+ <Box
201
+ key={i}
202
+ sx={{
203
+ ...(isCurrentUser && {
204
+ marginLeft: props.area === 'main' ? '25%' : '10%',
205
+ backgroundColor: 'var(--jp-layout-color2)',
206
+ border: 'none',
207
+ borderRadius: 2,
208
+ padding: 2
209
+ })
210
+ }}
211
+ className={clsx(
212
+ MESSAGE_CLASS,
213
+ message.stacked ? MESSAGE_STACKED_CLASS : ''
214
+ )}
215
+ >
216
+ <ChatMessageHeader
217
+ message={message}
218
+ isCurrentUser={isCurrentUser}
219
+ />
220
+ <ChatMessage
221
+ {...props}
205
222
  message={message}
206
- model={model}
223
+ index={i}
224
+ renderedPromise={renderedPromise.current[i]}
225
+ ref={el => (listRef.current[i] = el)}
207
226
  />
208
- )}
209
- </Box>
210
- );
211
- })}
227
+ {props.messageFooterRegistry && (
228
+ <MessageFooterComponent
229
+ registry={props.messageFooterRegistry}
230
+ message={message}
231
+ model={model}
232
+ />
233
+ )}
234
+ </Box>
235
+ );
236
+ })}
212
237
  </Box>
213
238
  </ScrollContainer>
214
- <WritingUsersList writers={currentWriters}></WritingUsersList>
215
239
  <Navigation {...props} refMsgBox={refMsgBox} allRendered={allRendered} />
216
240
  </>
217
241
  );
@@ -3,11 +3,9 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
 
6
- import {
7
- ToolbarButtonComponent,
8
- deleteIcon,
9
- editIcon
10
- } from '@jupyterlab/ui-components';
6
+ // import EditIcon from '@mui/icons-material/Edit';
7
+ import DeleteIcon from '@mui/icons-material/Delete';
8
+ import { Box, IconButton, Tooltip } from '@mui/material';
11
9
  import React from 'react';
12
10
 
13
11
  const TOOLBAR_CLASS = 'jp-chat-toolbar';
@@ -18,27 +16,59 @@ const TOOLBAR_CLASS = 'jp-chat-toolbar';
18
16
  export function MessageToolbar(props: MessageToolbar.IProps): JSX.Element {
19
17
  const buttons: JSX.Element[] = [];
20
18
 
21
- if (props.edit !== undefined) {
22
- const editButton = ToolbarButtonComponent({
23
- icon: editIcon,
24
- onClick: props.edit,
25
- tooltip: 'Edit'
26
- });
27
- buttons.push(editButton);
28
- }
19
+ // if (props.edit !== undefined) {
20
+ // const editButton = (
21
+ // <Tooltip key="edit" title="Edit" placement="top" arrow>
22
+ // <span>
23
+ // <IconButton
24
+ // onClick={props.edit}
25
+ // aria-label="Edit"
26
+ // sx={{
27
+ // width: '24px',
28
+ // height: '24px',
29
+ // padding: 0,
30
+ // lineHeight: 0
31
+ // }}
32
+ // >
33
+ // <EditIcon sx={{ fontSize: '16px' }} />
34
+ // </IconButton>
35
+ // </span>
36
+ // </Tooltip>
37
+ // );
38
+ // buttons.push(editButton);
39
+ // }
29
40
  if (props.delete !== undefined) {
30
- const deleteButton = ToolbarButtonComponent({
31
- icon: deleteIcon,
32
- onClick: props.delete,
33
- tooltip: 'Delete'
34
- });
41
+ const deleteButton = (
42
+ <Tooltip key="delete" title="Delete" placement="top" arrow>
43
+ <span>
44
+ <IconButton
45
+ onClick={props.delete}
46
+ aria-label="Delete"
47
+ sx={{
48
+ width: '24px',
49
+ height: '24px',
50
+ padding: 0,
51
+ lineHeight: 0
52
+ }}
53
+ >
54
+ <DeleteIcon sx={{ fontSize: '16px' }} />
55
+ </IconButton>
56
+ </span>
57
+ </Tooltip>
58
+ );
35
59
  buttons.push(deleteButton);
36
60
  }
37
61
 
38
62
  return (
39
- <div className={TOOLBAR_CLASS}>
40
- {buttons.map(toolbarButton => toolbarButton)}
41
- </div>
63
+ <Box
64
+ className={TOOLBAR_CLASS}
65
+ sx={{
66
+ display: 'flex',
67
+ gap: 2
68
+ }}
69
+ >
70
+ {buttons}
71
+ </Box>
42
72
  );
43
73
  }
44
74
 
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { IDocumentManager } from '@jupyterlab/docmanager';
7
+ import { UUID } from '@lumino/coreutils';
7
8
  import { IDisposable } from '@lumino/disposable';
8
9
  import { ISignal, Signal } from '@lumino/signaling';
9
10
  import { IActiveCellManager } from './active-cell-manager';
@@ -116,6 +117,11 @@ export interface IInputModel extends IDisposable {
116
117
  */
117
118
  clearAttachments(): void;
118
119
 
120
+ /**
121
+ * Unique identifier for the input (needed for drag-and-drop).
122
+ */
123
+ readonly id: string;
124
+
119
125
  /**
120
126
  * A signal emitting when the attachment list has changed.
121
127
  */
@@ -157,6 +163,7 @@ export interface IInputModel extends IDisposable {
157
163
  */
158
164
  export class InputModel implements IInputModel {
159
165
  constructor(options: InputModel.IOptions) {
166
+ this._id = options.id ?? `input-${UUID.uuid4()}`;
160
167
  this._onSend = options.onSend;
161
168
  this._chatContext = options.chatContext;
162
169
  this._value = options.value || '';
@@ -196,6 +203,13 @@ export class InputModel implements IInputModel {
196
203
  */
197
204
  cancel: (() => void) | undefined;
198
205
 
206
+ /**
207
+ * Unique identifier for the input (needed for drag-and-drop).
208
+ */
209
+ get id(): string {
210
+ return this._id;
211
+ }
212
+
199
213
  /**
200
214
  * The entire input value.
201
215
  */
@@ -471,6 +485,7 @@ export class InputModel implements IInputModel {
471
485
  return this._isDisposed;
472
486
  }
473
487
 
488
+ private _id: string;
474
489
  private _onSend: (input: string, model?: InputModel) => void;
475
490
  private _chatContext?: IChatContext;
476
491
  private _value: string;
@@ -532,6 +547,12 @@ export namespace InputModel {
532
547
  */
533
548
  cursorIndex?: number;
534
549
 
550
+ /**
551
+ * Optional unique identifier for this input model.
552
+ * If not provided, one will be generated automatically.
553
+ */
554
+ id?: string;
555
+
535
556
  /**
536
557
  * The configuration for the input component.
537
558
  */
package/src/model.ts CHANGED
@@ -201,6 +201,11 @@ export interface IChatModel extends IDisposable {
201
201
  */
202
202
  getEditionModel(messageID: string): IInputModel | undefined;
203
203
 
204
+ /**
205
+ * Get the input models of all edited messages.
206
+ */
207
+ getEditionModels(): IInputModel[];
208
+
204
209
  /**
205
210
  * Add an input model of the edited message.
206
211
  */
@@ -637,6 +642,13 @@ export abstract class AbstractChatModel implements IChatModel {
637
642
  return this._messageEditions.get(messageID);
638
643
  }
639
644
 
645
+ /**
646
+ * Get the input models of all edited messages.
647
+ */
648
+ getEditionModels(): IInputModel[] {
649
+ return Array.from(this._messageEditions.values());
650
+ }
651
+
640
652
  /**
641
653
  * Add an input model of the edited message.
642
654
  */
package/src/types.ts CHANGED
@@ -172,3 +172,8 @@ export interface IAttachmentSelection {
172
172
  * An empty interface to describe optional settings that could be fetched from server.
173
173
  */
174
174
  export interface ISettings {} /* eslint-disable-line @typescript-eslint/no-empty-object-type */
175
+
176
+ /**
177
+ * The area where the chat is displayed.
178
+ */
179
+ export type ChatArea = 'sidebar' | 'main';
@@ -20,6 +20,7 @@ import {
20
20
  INotebookAttachmentCell
21
21
  } from '../types';
22
22
  import { ActiveCellManager } from '../active-cell-manager';
23
+ import { IInputModel } from '../input-model';
23
24
 
24
25
  // MIME type constant for file browser drag events
25
26
  const FILE_BROWSER_MIME = 'application/x-jupyter-icontentsrich';
@@ -141,12 +142,19 @@ export class ChatWidget extends ReactWidget {
141
142
  * Handle drag over events
142
143
  */
143
144
  private _handleDrag(event: Drag.Event): void {
144
- const inputContainer = this.node.querySelector(`.${INPUT_CONTAINER_CLASS}`);
145
+ const inputContainers = this.node.querySelectorAll<HTMLElement>(
146
+ `.${INPUT_CONTAINER_CLASS}`
147
+ );
145
148
  const target = event.target as HTMLElement;
146
- const isOverInput =
147
- inputContainer?.contains(target) || inputContainer === target;
149
+ let overInput: HTMLElement | null = null;
150
+ for (const container of inputContainers) {
151
+ if (container.contains(target)) {
152
+ overInput = container;
153
+ break;
154
+ }
155
+ }
148
156
 
149
- if (!isOverInput) {
157
+ if (!overInput) {
150
158
  this._removeDragHoverClass();
151
159
  return;
152
160
  }
@@ -159,12 +167,9 @@ export class ChatWidget extends ReactWidget {
159
167
  event.stopPropagation();
160
168
  event.dropAction = 'move';
161
169
 
162
- if (
163
- inputContainer &&
164
- !inputContainer.classList.contains(DRAG_HOVER_CLASS)
165
- ) {
166
- inputContainer.classList.add(DRAG_HOVER_CLASS);
167
- this._dragTarget = inputContainer as HTMLElement;
170
+ if (!overInput.classList.contains(DRAG_HOVER_CLASS)) {
171
+ overInput.classList.add(DRAG_HOVER_CLASS);
172
+ this._dragTarget = overInput;
168
173
  }
169
174
  }
170
175
 
@@ -203,6 +208,30 @@ export class ChatWidget extends ReactWidget {
203
208
  }
204
209
  }
205
210
 
211
+ /**
212
+ * Get the input model associated with the event target and input ids.
213
+ */
214
+ private _getInputFromEvent(event: Drag.Event): IInputModel | undefined {
215
+ let element = event.target as HTMLElement | null;
216
+
217
+ while (element) {
218
+ if (
219
+ element.classList.contains(INPUT_CONTAINER_CLASS) &&
220
+ element.dataset.inputId
221
+ ) {
222
+ const inputId = element.dataset.inputId;
223
+ const inputModel =
224
+ this.model.input.id === inputId
225
+ ? this.model.input
226
+ : this.model.getEditionModels().find(model => model.id === inputId);
227
+ return inputModel;
228
+ }
229
+ element = element.parentElement;
230
+ }
231
+
232
+ return;
233
+ }
234
+
206
235
  /**
207
236
  * Process dropped files
208
237
  */
@@ -221,7 +250,8 @@ export class ChatWidget extends ReactWidget {
221
250
  value: data.model.path,
222
251
  mimetype: data.model.mimetype
223
252
  };
224
- this.model.input.addAttachment?.(attachment);
253
+ const inputModel = this._getInputFromEvent(event);
254
+ inputModel?.addAttachment?.(attachment);
225
255
  }
226
256
 
227
257
  /**
@@ -283,7 +313,8 @@ export class ChatWidget extends ReactWidget {
283
313
  value: notebookPath,
284
314
  cells: validCells
285
315
  };
286
- this.model.input.addAttachment?.(attachment);
316
+ const inputModel = this._getInputFromEvent(event);
317
+ inputModel?.addAttachment?.(attachment);
287
318
  }
288
319
  } catch (error) {
289
320
  console.error('Failed to process cell drop: ', error);
@@ -155,7 +155,8 @@ export class MultiChatPanel extends SidePanel {
155
155
  attachmentOpenerRegistry: this._attachmentOpenerRegistry,
156
156
  inputToolbarRegistry,
157
157
  messageFooterRegistry: this._messageFooterRegistry,
158
- welcomeMessage: this._welcomeMessage
158
+ welcomeMessage: this._welcomeMessage,
159
+ area: 'sidebar'
159
160
  });
160
161
 
161
162
  const section = new ChatSection({
package/style/chat.css CHANGED
@@ -2,20 +2,18 @@
2
2
  * Copyright (c) Jupyter Development Team.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
- .jp-chat-message:not(.jp-chat-message-stacked) {
6
- padding: 1em 1em 0;
7
- }
8
5
 
9
- .jp-chat-message:not(:first-child, .jp-chat-message-stacked) {
10
- border-top: 1px solid var(--jp-border-color2);
6
+ .jp-chat-rendered-markdown {
7
+ position: relative;
11
8
  }
12
9
 
13
- .jp-chat-message.jp-chat-message-stacked {
14
- padding: 0 1em;
10
+ .jp-chat-rendered-markdown hr {
11
+ color: #00000026;
12
+ background-color: transparent;
15
13
  }
16
14
 
17
- .jp-chat-rendered-markdown {
18
- position: relative;
15
+ .jp-chat-rendered-markdown .jp-RenderedHTMLCommon > :last-child {
16
+ margin-bottom: 0;
19
17
  }
20
18
 
21
19
  /*
@@ -38,7 +36,7 @@
38
36
  overflow-x: auto;
39
37
  white-space: pre;
40
38
  margin: 0;
41
- padding: 4px 2px 0 6px;
39
+ padding: 4px 6px;
42
40
  border: var(--jp-border-width) solid var(--jp-cell-editor-border-color);
43
41
  }
44
42
 
@@ -53,7 +51,7 @@
53
51
  }
54
52
 
55
53
  .jp-chat-toolbar {
56
- display: none;
54
+ visibility: hidden;
57
55
  position: absolute;
58
56
  right: 2px;
59
57
  top: 2px;
@@ -67,107 +65,13 @@
67
65
  }
68
66
 
69
67
  .jp-chat-rendered-markdown:hover .jp-chat-toolbar {
70
- display: inherit;
68
+ visibility: visible;
71
69
  }
72
70
 
73
71
  .jp-chat-toolbar > .jp-ToolbarButtonComponent {
74
72
  margin-top: 0;
75
73
  }
76
74
 
77
- .jp-chat-writers {
78
- display: flex;
79
- flex-wrap: wrap;
80
- position: sticky;
81
- bottom: 0;
82
- padding: 8px;
83
- background-color: var(--jp-layout-color0);
84
- border-top: 1px solid var(--jp-border-color2);
85
- z-index: 1;
86
- }
87
-
88
- .jp-chat-writers-content {
89
- display: flex;
90
- align-items: center;
91
- gap: 4px;
92
- flex-wrap: wrap;
93
- }
94
-
95
- .jp-chat-writer-item {
96
- display: flex;
97
- align-items: center;
98
- gap: 6px;
99
- }
100
-
101
- .jp-chat-writer-name {
102
- color: var(--jp-ui-font-color1);
103
- font-weight: 500;
104
- }
105
-
106
- .jp-chat-writer-separator {
107
- color: var(--jp-ui-font-color2);
108
- }
109
-
110
- .jp-chat-writing-status {
111
- display: flex;
112
- align-items: center;
113
- gap: 8px;
114
- }
115
-
116
- .jp-chat-writing-text {
117
- color: var(--jp-ui-font-color2);
118
- }
119
-
120
- /* Animated typing indicator */
121
- .jp-chat-typing-indicator {
122
- display: flex;
123
- align-items: center;
124
- gap: 2px;
125
- padding: 2px 4px;
126
- }
127
-
128
- .jp-chat-typing-dot {
129
- width: 4px;
130
- height: 4px;
131
- border-radius: 50%;
132
- background-color: var(--jp-brand-color1);
133
- animation: jp-chat-typing-bounce 1.4s infinite ease-in-out;
134
- }
135
-
136
- .jp-chat-typing-dot:nth-child(1) {
137
- animation-delay: -0.32s;
138
- }
139
-
140
- .jp-chat-typing-dot:nth-child(2) {
141
- animation-delay: -0.16s;
142
- }
143
-
144
- .jp-chat-typing-dot:nth-child(3) {
145
- animation-delay: 0s;
146
- }
147
-
148
- /* Keyframe animations */
149
- @keyframes jp-chat-typing-bounce {
150
- 0%,
151
- 80%,
152
- 100% {
153
- transform: scale(0.8);
154
- opacity: 0.5;
155
- }
156
-
157
- 40% {
158
- transform: scale(1.2);
159
- opacity: 1;
160
- }
161
- }
162
-
163
- .jp-chat-writers > div {
164
- display: flex;
165
- align-items: center;
166
- gap: 0.2em;
167
- white-space: pre;
168
- padding-left: 0.5em;
169
- }
170
-
171
75
  .jp-chat-navigation {
172
76
  position: absolute;
173
77
  right: 10px;
@@ -197,40 +101,8 @@
197
101
  bottom: 120px;
198
102
  }
199
103
 
200
- .jp-chat-attachments {
201
- display: flex;
202
- gap: 4px;
203
- flex-wrap: wrap;
204
- min-height: 1.5em;
205
- padding: 4px 0;
206
- }
207
-
208
- .jp-chat-attachment {
209
- border: solid 1px;
210
- border-radius: 10px;
211
- margin: 0 0.2em;
212
- padding: 0 0.3em;
213
- align-content: center;
214
- background-color: var(--jp-border-color3);
215
- flex-shrink: 0;
216
- }
217
-
218
- .jp-chat-attachment .jp-chat-attachment-clickable:hover {
219
- cursor: pointer;
220
- }
221
-
222
- .jp-chat-command-name {
223
- font-weight: normal;
224
- margin: 5px;
225
- }
226
-
227
- .jp-chat-command-description {
228
- color: gray;
229
- margin: 5px;
230
- }
231
-
232
104
  .jp-chat-mention {
233
- border-radius: 10px;
234
- padding: 0 0.2em;
235
- background-color: var(--jp-brand-color4);
105
+ border-radius: 4px;
106
+ padding: 2px 0;
107
+ font-weight: bold;
236
108
  }