From bd3d1ba57dabcbd10ed0d9c33d15ae7e943dcfb0 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Fri, 11 Apr 2025 17:58:30 +0200 Subject: [PATCH 1/4] Send the message ID in the writer state when the user edit a message --- .../src/components/chat-messages.tsx | 90 ++++++++++++------- packages/jupyter-chat/src/input-model.ts | 14 +++ packages/jupyter-chat/src/model.ts | 82 +++++++++++++++-- packages/jupyterlab-chat/src/model.ts | 34 +++++-- 4 files changed, 176 insertions(+), 44 deletions(-) diff --git a/packages/jupyter-chat/src/components/chat-messages.tsx b/packages/jupyter-chat/src/components/chat-messages.tsx index ff356b9..0700405 100644 --- a/packages/jupyter-chat/src/components/chat-messages.tsx +++ b/packages/jupyter-chat/src/components/chat-messages.tsx @@ -100,8 +100,8 @@ export function ChatMessages(props: BaseMessageProps): JSX.Element { setMessages([...model.messages]); } - function handleWritersChange(_: IChatModel, writers: IUser[]) { - setCurrentWriters(writers); + function handleWritersChange(_: IChatModel, writers: IChatModel.IWriter[]) { + setCurrentWriters(writers.map(writer => writer.user)); } model.messagesUpdated.connect(handleChatEvents); @@ -357,10 +357,10 @@ export const ChatMessage = forwardRef( const [deleted, setDeleted] = useState(false); const [canEdit, setCanEdit] = useState(false); const [canDelete, setCanDelete] = useState(false); - const [inputModel, setInputModel] = useState(null); // Look if the message can be deleted or edited. useEffect(() => { + // Init canDelete and canEdit state. setDeleted(message.deleted ?? false); if (model.user !== undefined && !message.deleted) { if (model.user.username === message.sender.username) { @@ -371,39 +371,63 @@ export const ChatMessage = forwardRef( setCanEdit(false); setCanDelete(false); } + + // Update canEdit state (only one message can be edited) + const updateCanEdit = ( + _: IChatModel, + edition: IChatModel.IMessageEdition | null + ) => { + if ( + !message.deleted && + model.user?.username === message.sender.username + ) { + if (edition === null || edition.id === message.id) { + setCanEdit(true); + } else { + setCanEdit(false); + } + } else { + setCanEdit(false); + } + }; + model.messageEditionChanged.connect(updateCanEdit); + + return () => { + model.messageEditionChanged.disconnect(updateCanEdit); + }; }, [model, message]); - // Create an input model only if the message is edited. - useEffect(() => { - if (edit && canEdit) { - setInputModel(() => { - let body = message.body; - message.mentions?.forEach(user => { - body = replaceSpanToMention(body, user); - }); - return new InputModel({ - chatContext: model.createChatContext(), - onSend: (input: string, model?: IInputModel) => - updateMessage(message.id, input, model), - onCancel: () => cancelEdition(), - value: body, - activeCellManager: model.activeCellManager, - selectionWatcher: model.selectionWatcher, - documentManager: model.documentManager, - config: { - sendWithShiftEnter: model.config.sendWithShiftEnter - }, - attachments: message.attachments, - mentions: message.mentions - }); - }); - } else { - setInputModel(null); + const startEdition = (): void => { + if (!canEdit) { + return; } - }, [edit]); + let body = message.body; + message.mentions?.forEach(user => { + body = replaceSpanToMention(body, user); + }); + const inputModel = new InputModel({ + chatContext: model.createChatContext(), + onSend: (input: string, model?: IInputModel) => + updateMessage(message.id, input, model), + onCancel: () => cancelEdition(), + value: body, + activeCellManager: model.activeCellManager, + selectionWatcher: model.selectionWatcher, + documentManager: model.documentManager, + config: { + sendWithShiftEnter: model.config.sendWithShiftEnter + }, + attachments: message.attachments, + mentions: message.mentions + }); + model.messageEdition = { id: message.id, model: inputModel }; + setEdit(true); + }; // Cancel the current edition of the message. const cancelEdition = (): void => { + model.messageEdition?.model.dispose(); + model.messageEdition = null; setEdit(false); }; @@ -438,10 +462,10 @@ export const ChatMessage = forwardRef(
) : (
- {edit && canEdit && inputModel ? ( + {edit && canEdit && model.messageEdition ? ( cancelEdition()} - model={inputModel} + model={model.messageEdition.model} chatCommandRegistry={props.chatCommandRegistry} toolbarRegistry={props.inputToolbarRegistry} /> @@ -450,7 +474,7 @@ export const ChatMessage = forwardRef( rmRegistry={rmRegistry} markdownStr={message.body} model={model} - edit={canEdit ? () => setEdit(true) : undefined} + edit={canEdit ? startEdition : undefined} delete={canDelete ? () => deleteMessage(message.id) : undefined} rendered={props.renderedPromise} /> diff --git a/packages/jupyter-chat/src/input-model.ts b/packages/jupyter-chat/src/input-model.ts index d34a77a..1a62d77 100644 --- a/packages/jupyter-chat/src/input-model.ts +++ b/packages/jupyter-chat/src/input-model.ts @@ -147,6 +147,11 @@ export interface IInputModel extends IDisposable { * Clear mentions list. */ clearMentions(): void; + + /** + * A signal emitting when disposing of the model. + */ + readonly onDispose: ISignal; } /** @@ -411,9 +416,17 @@ export class InputModel implements IInputModel { if (this.isDisposed) { return; } + this._onDispose.emit(); this._isDisposed = true; } + /** + * A signal emitting when disposing of the model. + */ + get onDispose(): ISignal { + return this._onDispose; + } + /** * Whether the input model is disposed. */ @@ -438,6 +451,7 @@ export class InputModel implements IInputModel { private _configChanged = new Signal(this); private _focusInputSignal = new Signal(this); private _attachmentsChanged = new Signal(this); + private _onDispose = new Signal(this); private _isDisposed = false; } diff --git a/packages/jupyter-chat/src/model.ts b/packages/jupyter-chat/src/model.ts index dd01a44..53f92a4 100644 --- a/packages/jupyter-chat/src/model.ts +++ b/packages/jupyter-chat/src/model.ts @@ -44,6 +44,11 @@ export interface IChatModel extends IDisposable { */ messagesInViewport?: number[]; + /** + * The input model of the current message in edition (null if no message are edited). + */ + messageEdition: IChatModel.IMessageEdition | null; + /** * The user connected to the chat panel. */ @@ -82,7 +87,7 @@ export interface IChatModel extends IDisposable { /** * A signal emitting when the messages list is updated. */ - get configChanged(): ISignal; + readonly configChanged: ISignal; /** * A signal emitting when unread messages change. @@ -97,7 +102,15 @@ export interface IChatModel extends IDisposable { /** * A signal emitting when the writers change. */ - readonly writersChanged?: ISignal; + readonly writersChanged?: ISignal; + + /** + * A signal emitting when the message edition input changed change. + */ + readonly messageEditionChanged: ISignal< + IChatModel, + IChatModel.IMessageEdition | null + >; /** * Send a message, to be defined depending on the chosen technology. @@ -172,7 +185,7 @@ export interface IChatModel extends IDisposable { /** * Update the current writers list. */ - updateWriters(writers: IUser[]): void; + updateWriters(writers: IChatModel.IWriter[]): void; /** * Create the chat context that will be passed to the input model. @@ -387,6 +400,20 @@ export abstract class AbstractChatModel implements IChatModel { this._viewportChanged.emit(values); } + /** + * The input model of the current message in edition (null if no message are edited). + */ + get messageEdition(): IChatModel.IMessageEdition | null { + return this._messageEdition; + } + set messageEdition(editionModel: IChatModel.IMessageEdition | null) { + if (this._messageEdition) { + this._messageEdition.model.dispose(); + } + this._messageEdition = editionModel; + this._messageEditionChanged.emit(this._messageEdition); + } + /** * A signal emitting when the messages list is updated. */ @@ -418,10 +445,20 @@ export abstract class AbstractChatModel implements IChatModel { /** * A signal emitting when the writers change. */ - get writersChanged(): ISignal { + get writersChanged(): ISignal { return this._writersChanged; } + /** + * A signal emitting when the message edition input changed change. + */ + get messageEditionChanged(): ISignal< + IChatModel, + IChatModel.IMessageEdition | null + > { + return this._messageEditionChanged; + } + /** * Send a message, to be defined depending on the chosen technology. * Default to no-op. @@ -546,7 +583,7 @@ export abstract class AbstractChatModel implements IChatModel { * Update the current writers list. * This implementation only propagate the list via a signal. */ - updateWriters(writers: IUser[]): void { + updateWriters(writers: IChatModel.IWriter[]): void { this._writersChanged.emit(writers); } @@ -616,12 +653,17 @@ export abstract class AbstractChatModel implements IChatModel { private _activeCellManager: IActiveCellManager | null; private _selectionWatcher: ISelectionWatcher | null; private _documentManager: IDocumentManager | null; + private _messageEdition: IChatModel.IMessageEdition | null = null; private _notificationId: string | null = null; private _messagesUpdated = new Signal(this); private _configChanged = new Signal(this); private _unreadChanged = new Signal(this); private _viewportChanged = new Signal(this); - private _writersChanged = new Signal(this); + private _writersChanged = new Signal(this); + private _messageEditionChanged = new Signal< + IChatModel, + IChatModel.IMessageEdition | null + >(this); } /** @@ -662,6 +704,34 @@ export namespace IChatModel { */ documentManager?: IDocumentManager | null; } + + /** + * Representation of a message edition. + */ + export interface IMessageEdition { + /** + * The id of the edited message. + */ + id: string; + /** + * The model of the input editing the message. + */ + model: IInputModel; + } + + /** + * Writer interface, including the message ID if the writer is editing a message. + */ + export interface IWriter { + /** + * The user currently writing. + */ + user: IUser; + /** + * The message ID (optional) + */ + messageID?: string; + } } /** diff --git a/packages/jupyterlab-chat/src/model.ts b/packages/jupyterlab-chat/src/model.ts index 5fa196e..23e3408 100644 --- a/packages/jupyterlab-chat/src/model.ts +++ b/packages/jupyterlab-chat/src/model.ts @@ -67,7 +67,8 @@ export class LabChatModel this.sharedModel.awareness.on('change', this.onAwarenessChange); - this.input.valueChanged.connect(this.onInputChanged); + this.input.valueChanged.connect((_, value) => this.onInputChanged(value)); + this.messageEditionChanged.connect(this.onMessageEditionChanged); } readonly collaborative = true; @@ -261,7 +262,7 @@ export class LabChatModel /** * Function called by the input on key pressed. */ - onInputChanged = (_: IInputModel, value: string): void => { + onInputChanged = (value: string, messageID?: string): void => { if (!value || !this.config.sendTypingNotification) { return; } @@ -269,18 +270,37 @@ export class LabChatModel if (this._timeoutWriting !== null) { window.clearTimeout(this._timeoutWriting); } - awareness.setLocalStateField('isWriting', true); + awareness.setLocalStateField('isWriting', messageID ?? true); this._timeoutWriting = window.setTimeout(() => { this._resetWritingStatus(); }, WRITING_DELAY); }; + /** + * Listen to the message edition input. + */ + onMessageEditionChanged = ( + _: IChatModel, + edition: IChatModel.IMessageEdition | null + ) => { + if (edition !== null) { + const _onInputChanged = (_: IInputModel, value: string) => { + this.onInputChanged(value, edition.id); + }; + + edition.model.valueChanged.connect(_onInputChanged); + edition.model.onDispose.connect(() => + edition.model.valueChanged.disconnect(_onInputChanged) + ); + } + }; + /** * Triggered when an awareness state changes. * Used to populate the writers list. */ onAwarenessChange = () => { - const writers: IUser[] = []; + const writers: IChatModel.IWriter[] = []; const states = this.sharedModel.awareness.getStates(); for (const stateID of states.keys()) { const state = states.get(stateID); @@ -288,7 +308,11 @@ export class LabChatModel continue; } if (state.isWriting) { - writers.push(state.user); + const writer: IChatModel.IWriter = { + user: state.user, + messageID: state.isWriting === true ? undefined : state.isWriting + }; + writers.push(writer); } } this.updateWriters(writers); From 4eff3593bf4336dd9d8c20bfeb81b3cfe5235f5a Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Tue, 15 Apr 2025 12:25:51 +0200 Subject: [PATCH 2/4] Add test on typing notification when editing a message --- ui-tests/tests/ui-config.spec.ts | 40 ++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/ui-tests/tests/ui-config.spec.ts b/ui-tests/tests/ui-config.spec.ts index b91a36e..979c1ea 100644 --- a/ui-tests/tests/ui-config.spec.ts +++ b/ui-tests/tests/ui-config.spec.ts @@ -12,7 +12,7 @@ import { import { User } from '@jupyterlab/services'; import { UUID } from '@lumino/coreutils'; -import { openChat, openSettings, USER } from './test-utils'; +import { openChat, openSettings, sendMessage, USER } from './test-utils'; const FILENAME = 'my-chat.chat'; const MSG_CONTENT = 'Hello World!'; @@ -191,8 +191,11 @@ test.describe('#typingNotification', () => { } ); - test.afterEach(async () => { + test.afterEach(async ({ page }) => { await guestPage.close(); + if (await page.filebrowser.contents.fileExists(FILENAME)) { + await page.filebrowser.contents.deleteFile(FILENAME); + } }); test('should display typing user', async ({ page }) => { @@ -214,6 +217,39 @@ test.describe('#typingNotification', () => { expect(Date.now() - start).toBeLessThanOrEqual(2000); }); + test('should display typing user editing a message', async ({ page }) => { + const chatPanel = await openChat(page, FILENAME); + const writers = chatPanel.locator('.jp-chat-writers'); + + const guestChatPanel = await openChat(guestPage, FILENAME); + + await sendMessage(guestPage, FILENAME, 'test'); + await expect(writers).not.toBeAttached(); + const message = guestChatPanel + .locator('.jp-chat-messages-container .jp-chat-message') + .first(); + const messageContent = message.locator('.jp-chat-rendered-markdown'); + + // Should display the message toolbar + await messageContent.hover({ position: { x: 5, y: 5 } }); + await messageContent.locator('.jp-chat-toolbar jp-button').first().click(); + + const editInput = guestChatPanel + .locator('.jp-chat-messages-container .jp-chat-input-container') + .getByRole('combobox'); + + await editInput.focus(); + + await editInput.press('a'); + await expect(writers).toBeAttached(); + const start = Date.now(); + await expect(writers).toHaveText(/jovyan_2 is writing/); + await expect(writers).not.toBeAttached(); + + // Message should disappear after 1s, but this delay include the awareness update. + expect(Date.now() - start).toBeLessThanOrEqual(2000); + }); + test('should not display typing users if disabled', async ({ page }) => { const chatPanel = await openChat(page, FILENAME); const writers = chatPanel.locator('.jp-chat-writers'); From 73f228fea139047097b39bc7a36df5db63d583f4 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Thu, 17 Apr 2025 16:32:37 +0200 Subject: [PATCH 3/4] Allow several editions in a chat --- .../src/components/chat-messages.tsx | 34 ++------- packages/jupyter-chat/src/input-model.ts | 10 +-- packages/jupyter-chat/src/model.ts | 69 ++++++++++--------- packages/jupyterlab-chat/src/model.ts | 9 +-- 4 files changed, 51 insertions(+), 71 deletions(-) diff --git a/packages/jupyter-chat/src/components/chat-messages.tsx b/packages/jupyter-chat/src/components/chat-messages.tsx index 0700405..7aed71d 100644 --- a/packages/jupyter-chat/src/components/chat-messages.tsx +++ b/packages/jupyter-chat/src/components/chat-messages.tsx @@ -371,32 +371,9 @@ export const ChatMessage = forwardRef( setCanEdit(false); setCanDelete(false); } - - // Update canEdit state (only one message can be edited) - const updateCanEdit = ( - _: IChatModel, - edition: IChatModel.IMessageEdition | null - ) => { - if ( - !message.deleted && - model.user?.username === message.sender.username - ) { - if (edition === null || edition.id === message.id) { - setCanEdit(true); - } else { - setCanEdit(false); - } - } else { - setCanEdit(false); - } - }; - model.messageEditionChanged.connect(updateCanEdit); - - return () => { - model.messageEditionChanged.disconnect(updateCanEdit); - }; }, [model, message]); + // Create an input model only if the message is edited. const startEdition = (): void => { if (!canEdit) { return; @@ -420,14 +397,13 @@ export const ChatMessage = forwardRef( attachments: message.attachments, mentions: message.mentions }); - model.messageEdition = { id: message.id, model: inputModel }; + model.addEditionModel(message.id, inputModel); setEdit(true); }; // Cancel the current edition of the message. const cancelEdition = (): void => { - model.messageEdition?.model.dispose(); - model.messageEdition = null; + model.getEditionModel(message.id)?.dispose(); setEdit(false); }; @@ -462,10 +438,10 @@ export const ChatMessage = forwardRef(
) : (
- {edit && canEdit && model.messageEdition ? ( + {edit && canEdit && model.getEditionModel(message.id) ? ( cancelEdition()} - model={model.messageEdition.model} + model={model.getEditionModel(message.id)!} chatCommandRegistry={props.chatCommandRegistry} toolbarRegistry={props.inputToolbarRegistry} /> diff --git a/packages/jupyter-chat/src/input-model.ts b/packages/jupyter-chat/src/input-model.ts index 1a62d77..e52da46 100644 --- a/packages/jupyter-chat/src/input-model.ts +++ b/packages/jupyter-chat/src/input-model.ts @@ -151,7 +151,7 @@ export interface IInputModel extends IDisposable { /** * A signal emitting when disposing of the model. */ - readonly onDispose: ISignal; + readonly onDisposed: ISignal; } /** @@ -416,15 +416,15 @@ export class InputModel implements IInputModel { if (this.isDisposed) { return; } - this._onDispose.emit(); + this._onDisposed.emit(); this._isDisposed = true; } /** * A signal emitting when disposing of the model. */ - get onDispose(): ISignal { - return this._onDispose; + get onDisposed(): ISignal { + return this._onDisposed; } /** @@ -451,7 +451,7 @@ export class InputModel implements IInputModel { private _configChanged = new Signal(this); private _focusInputSignal = new Signal(this); private _attachmentsChanged = new Signal(this); - private _onDispose = new Signal(this); + private _onDisposed = new Signal(this); private _isDisposed = false; } diff --git a/packages/jupyter-chat/src/model.ts b/packages/jupyter-chat/src/model.ts index 53f92a4..0f0f404 100644 --- a/packages/jupyter-chat/src/model.ts +++ b/packages/jupyter-chat/src/model.ts @@ -44,11 +44,6 @@ export interface IChatModel extends IDisposable { */ messagesInViewport?: number[]; - /** - * The input model of the current message in edition (null if no message are edited). - */ - messageEdition: IChatModel.IMessageEdition | null; - /** * The user connected to the chat panel. */ @@ -107,10 +102,7 @@ export interface IChatModel extends IDisposable { /** * A signal emitting when the message edition input changed change. */ - readonly messageEditionChanged: ISignal< - IChatModel, - IChatModel.IMessageEdition | null - >; + readonly messageEditionAdded: ISignal; /** * Send a message, to be defined depending on the chosen technology. @@ -191,6 +183,16 @@ export interface IChatModel extends IDisposable { * Create the chat context that will be passed to the input model. */ createChatContext(): IChatContext; + + /** + * Get the input model of the edited message, given its id. + */ + getEditionModel(messageID: string): IInputModel | undefined; + + /** + * Add an input model of the edited message. + */ + addEditionModel(messageID: string, inputModel: IInputModel): void; } /** @@ -400,20 +402,6 @@ export abstract class AbstractChatModel implements IChatModel { this._viewportChanged.emit(values); } - /** - * The input model of the current message in edition (null if no message are edited). - */ - get messageEdition(): IChatModel.IMessageEdition | null { - return this._messageEdition; - } - set messageEdition(editionModel: IChatModel.IMessageEdition | null) { - if (this._messageEdition) { - this._messageEdition.model.dispose(); - } - this._messageEdition = editionModel; - this._messageEditionChanged.emit(this._messageEdition); - } - /** * A signal emitting when the messages list is updated. */ @@ -452,11 +440,8 @@ export abstract class AbstractChatModel implements IChatModel { /** * A signal emitting when the message edition input changed change. */ - get messageEditionChanged(): ISignal< - IChatModel, - IChatModel.IMessageEdition | null - > { - return this._messageEditionChanged; + get messageEditionAdded(): ISignal { + return this._messageEditionAdded; } /** @@ -592,6 +577,28 @@ export abstract class AbstractChatModel implements IChatModel { */ abstract createChatContext(): IChatContext; + /** + * Get the input model of the edited message, given its id. + */ + getEditionModel(messageID: string): IInputModel | undefined { + return this._messageEditions.get(messageID); + } + + /** + * Add an input model of the edited message. + */ + addEditionModel(messageID: string, inputModel: IInputModel): void { + if (this.getEditionModel(messageID)) { + this.getEditionModel(messageID)?.dispose(); + } + this._messageEditions.set(messageID, inputModel); + this._messageEditionAdded.emit({ id: messageID, model: inputModel }); + + inputModel.onDisposed.connect(() => { + this._messageEditions.delete(messageID); + }); + } + /** * Add unread messages to the list. * @param indexes - list of new indexes. @@ -653,16 +660,16 @@ export abstract class AbstractChatModel implements IChatModel { private _activeCellManager: IActiveCellManager | null; private _selectionWatcher: ISelectionWatcher | null; private _documentManager: IDocumentManager | null; - private _messageEdition: IChatModel.IMessageEdition | null = null; private _notificationId: string | null = null; + private _messageEditions = new Map(); private _messagesUpdated = new Signal(this); private _configChanged = new Signal(this); private _unreadChanged = new Signal(this); private _viewportChanged = new Signal(this); private _writersChanged = new Signal(this); - private _messageEditionChanged = new Signal< + private _messageEditionAdded = new Signal< IChatModel, - IChatModel.IMessageEdition | null + IChatModel.IMessageEdition >(this); } diff --git a/packages/jupyterlab-chat/src/model.ts b/packages/jupyterlab-chat/src/model.ts index 23e3408..54e729d 100644 --- a/packages/jupyterlab-chat/src/model.ts +++ b/packages/jupyterlab-chat/src/model.ts @@ -68,7 +68,7 @@ export class LabChatModel this.sharedModel.awareness.on('change', this.onAwarenessChange); this.input.valueChanged.connect((_, value) => this.onInputChanged(value)); - this.messageEditionChanged.connect(this.onMessageEditionChanged); + this.messageEditionAdded.connect(this.onMessageEditionAdded); } readonly collaborative = true; @@ -279,9 +279,9 @@ export class LabChatModel /** * Listen to the message edition input. */ - onMessageEditionChanged = ( + onMessageEditionAdded = ( _: IChatModel, - edition: IChatModel.IMessageEdition | null + edition: IChatModel.IMessageEdition ) => { if (edition !== null) { const _onInputChanged = (_: IInputModel, value: string) => { @@ -289,9 +289,6 @@ export class LabChatModel }; edition.model.valueChanged.connect(_onInputChanged); - edition.model.onDispose.connect(() => - edition.model.valueChanged.disconnect(_onInputChanged) - ); } }; From 97e8c7220ebbd8b654512660c1c314ec80e5e401 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet Date: Thu, 15 May 2025 21:54:30 +0200 Subject: [PATCH 4/4] Dispose of the message edition input model when sending the message --- packages/jupyter-chat/src/components/chat-messages.tsx | 1 + packages/jupyter-chat/src/model.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/jupyter-chat/src/components/chat-messages.tsx b/packages/jupyter-chat/src/components/chat-messages.tsx index 7aed71d..182eef9 100644 --- a/packages/jupyter-chat/src/components/chat-messages.tsx +++ b/packages/jupyter-chat/src/components/chat-messages.tsx @@ -422,6 +422,7 @@ export const ChatMessage = forwardRef( updatedMessage.attachments = inputModel.attachments; updatedMessage.mentions = inputModel.mentions; model.updateMessage!(id, updatedMessage); + model.getEditionModel(message.id)?.dispose(); setEdit(false); }; diff --git a/packages/jupyter-chat/src/model.ts b/packages/jupyter-chat/src/model.ts index 0f0f404..c99b44b 100644 --- a/packages/jupyter-chat/src/model.ts +++ b/packages/jupyter-chat/src/model.ts @@ -588,9 +588,9 @@ export abstract class AbstractChatModel implements IChatModel { * Add an input model of the edited message. */ addEditionModel(messageID: string, inputModel: IInputModel): void { - if (this.getEditionModel(messageID)) { - this.getEditionModel(messageID)?.dispose(); - } + // Dispose of an hypothetic previous model for this message. + this.getEditionModel(messageID)?.dispose(); + this._messageEditions.set(messageID, inputModel); this._messageEditionAdded.emit({ id: messageID, model: inputModel });