Skip to content

Commit 154d999

Browse files
brichetdlqqq
authored andcommitted
Allow adding or removing attachment when editing a message
1 parent 1f76f8d commit 154d999

File tree

5 files changed

+49
-16
lines changed

5 files changed

+49
-16
lines changed

packages/jupyter-chat/src/components/chat-input.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,17 @@ const INPUT_BOX_CLASS = 'jp-chat-input-container';
2828

2929
export function ChatInput(props: ChatInput.IProps): JSX.Element {
3030
const { documentManager, model } = props;
31-
const [input, setInput] = useState<string>(model.value || '');
31+
const [input, setInput] = useState<string>(model.value);
3232
const inputRef = useRef<HTMLInputElement>();
3333

3434
const chatCommands = useChatCommands(model, props.chatCommandRegistry);
3535

3636
const [sendWithShiftEnter, setSendWithShiftEnter] = useState<boolean>(
3737
model.config.sendWithShiftEnter ?? false
3838
);
39-
const [attachments, setAttachments] = useState<IAttachment[]>([]);
39+
const [attachments, setAttachments] = useState<IAttachment[]>(
40+
model.attachments
41+
);
4042

4143
// Display the include selection menu if it is not explicitly hidden, and if at least
4244
// one of the tool to check for text or cell selection is enabled.

packages/jupyter-chat/src/components/chat-messages.tsx

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
import { Button } from '@jupyter/react-components';
7+
import { IDocumentManager } from '@jupyterlab/docmanager';
78
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
89
import {
910
LabIcon,
@@ -21,7 +22,7 @@ import { ChatInput } from './chat-input';
2122
import { MarkdownRenderer } from './markdown-renderer';
2223
import { ScrollContainer } from './scroll-container';
2324
import { IChatCommandRegistry } from '../chat-commands';
24-
import { InputModel } from '../input-model';
25+
import { IInputModel, InputModel } from '../input-model';
2526
import { IChatModel } from '../model';
2627
import { IChatMessage, IUser } from '../types';
2728

@@ -43,6 +44,7 @@ type BaseMessageProps = {
4344
rmRegistry: IRenderMimeRegistry;
4445
model: IChatModel;
4546
chatCommandRegistry?: IChatCommandRegistry;
47+
documentManager?: IDocumentManager;
4648
};
4749

4850
/**
@@ -341,6 +343,7 @@ export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
341343
const [deleted, setDeleted] = useState<boolean>(false);
342344
const [canEdit, setCanEdit] = useState<boolean>(false);
343345
const [canDelete, setCanDelete] = useState<boolean>(false);
346+
const [inputModel, setInputModel] = useState<IInputModel | null>(null);
344347

345348
// Look if the message can be deleted or edited.
346349
useEffect(() => {
@@ -356,6 +359,25 @@ export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
356359
}
357360
}, [model, message]);
358361

362+
// Create an input model only if the message is edited.
363+
useEffect(() => {
364+
if (edit && canEdit) {
365+
setInputModel(
366+
new InputModel({
367+
value: message.body,
368+
activeCellManager: model.activeCellManager,
369+
selectionWatcher: model.selectionWatcher,
370+
config: {
371+
sendWithShiftEnter: model.config.sendWithShiftEnter
372+
},
373+
attachments: message.attachments
374+
})
375+
);
376+
} else {
377+
setInputModel(null);
378+
}
379+
}, [edit]);
380+
359381
// Cancel the current edition of the message.
360382
const cancelEdition = (): void => {
361383
setEdit(false);
@@ -369,6 +391,7 @@ export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
369391
// Update the message
370392
const updatedMessage = { ...message };
371393
updatedMessage.body = input;
394+
updatedMessage.attachments = inputModel?.attachments;
372395
model.updateMessage!(id, updatedMessage);
373396
setEdit(false);
374397
};
@@ -386,22 +409,14 @@ export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
386409
<div ref={ref} data-index={props.index}></div>
387410
) : (
388411
<div ref={ref} data-index={props.index}>
389-
{edit && canEdit ? (
412+
{edit && canEdit && inputModel ? (
390413
<ChatInput
391414
onSend={(input: string) => updateMessage(message.id, input)}
392415
onCancel={() => cancelEdition()}
393-
model={
394-
new InputModel({
395-
value: message.body,
396-
activeCellManager: model.activeCellManager,
397-
selectionWatcher: model.selectionWatcher,
398-
config: {
399-
sendWithShiftEnter: model.config.sendWithShiftEnter
400-
}
401-
})
402-
}
416+
model={inputModel}
403417
hideIncludeSelection={true}
404418
chatCommandRegistry={props.chatCommandRegistry}
419+
documentManager={props.documentManager}
405420
/>
406421
) : (
407422
<MarkdownRenderer
@@ -413,7 +428,9 @@ export const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>(
413428
rendered={props.renderedPromise}
414429
/>
415430
)}
416-
{message.attachments && (
431+
{message.attachments && !edit && (
432+
// Display the attachments only if message is not edited, otherwise the
433+
// input component display them.
417434
<AttachmentPreviewList attachments={message.attachments} />
418435
)}
419436
</div>

packages/jupyter-chat/src/components/chat.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export function ChatBody(props: Chat.IChatBodyProps): JSX.Element {
3636
rmRegistry={props.rmRegistry}
3737
model={model}
3838
chatCommandRegistry={props.chatCommandRegistry}
39+
documentManager={props.documentManager}
3940
/>
4041
<ChatInput
4142
onSend={onSend}

packages/jupyter-chat/src/input-model.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export interface IInputModel extends IDisposable {
113113
export class InputModel implements IInputModel {
114114
constructor(options: InputModel.IOptions) {
115115
this._value = options.value || '';
116+
this._attachments = options.attachments || [];
116117
this.cursorIndex = options.cursorIndex || this.value.length;
117118
this._activeCellManager = options.activeCellManager ?? null;
118119
this._selectionWatcher = options.selectionWatcher ?? null;
@@ -316,7 +317,7 @@ export class InputModel implements IInputModel {
316317
private _value: string;
317318
private _cursorIndex: number | null = null;
318319
private _currentWord: string | null = null;
319-
private _attachments: IAttachment[] = [];
320+
private _attachments: IAttachment[];
320321
private _activeCellManager: IActiveCellManager | null;
321322
private _selectionWatcher: ISelectionWatcher | null;
322323
private _config: InputModel.IConfig;
@@ -335,6 +336,12 @@ export namespace InputModel {
335336
* The initial value of the input.
336337
*/
337338
value?: string;
339+
340+
/**
341+
* The initial attachments.
342+
*/
343+
attachments?: IAttachment[];
344+
338345
/**
339346
* The current cursor index.
340347
* This refers to the index of the character in front of the cursor.

packages/jupyterlab-chat/src/model.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@ export class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
194194
edited: true
195195
};
196196
}
197+
const attachmentIds = updatedMessage.attachments?.map(attachment =>
198+
this.sharedModel.setAttachment(attachment)
199+
);
200+
if (attachmentIds) {
201+
message.attachments = attachmentIds;
202+
}
197203
this.sharedModel.updateMessage(index, message as IYmessage);
198204
}
199205

0 commit comments

Comments
 (0)