Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/jupyter-chat-example/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ class MyChatModel extends AbstractChatModel {
sendMessage(
newMessage: INewMessage
): Promise<boolean | void> | boolean | void {
if (!newMessage.body) {
return;
}
const message: IMessageContent = {
body: newMessage.body,
id: newMessage.id ?? UUID.uuid4(),
Expand Down
32 changes: 21 additions & 11 deletions packages/jupyter-chat/src/components/messages/message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { MessageRenderer } from './message-renderer';
import { AttachmentPreviewList } from '../attachments';
import { ChatInput } from '../input';
import { useChatContext } from '../../context';
import { IInputModel, InputModel } from '../../input-model';
import { IMessageContent, IMessage } from '../../types';
import { InputModel } from '../../input-model';
import { IMessageContent, IMessage, INewMessage } from '../../types';
import { replaceSpanToMention } from '../../utils';

export const MESSAGE_CONTAINER_CLASS = 'jp-chat-message-container';
Expand Down Expand Up @@ -96,8 +96,8 @@ export function ChatMessageBase(props: ChatMessageProps): JSX.Element {
});
const inputModel = new InputModel({
chatContext: model.createChatContext(),
onSend: (input: string, model?: IInputModel) =>
updateMessage(message.id, input, model),
onSend: (newMessage: INewMessage) =>
updateMessage(message.id, newMessage),
onCancel: () => cancelEdition(),
value: body,
activeCellManager: model.activeCellManager,
Expand All @@ -121,17 +121,27 @@ export function ChatMessageBase(props: ChatMessageProps): JSX.Element {

// Update the content of the message.
const updateMessage = useCallback(
(id: string, input: string, inputModel?: IInputModel): void => {
if (!canEdit || !inputModel) {
(id: string, newMessage: INewMessage): void => {
const inputModel = model.getEditionModel(id);
if (!canEdit || !inputModel || !model.updateMessage) {
setEdit(false);
return;
}

// Update the message
const updatedMessage = { ...message };
updatedMessage.body = input;
updatedMessage.attachments = inputModel.attachments;
updatedMessage.mentions = inputModel.mentions;
model.updateMessage!(id, updatedMessage);
model.getEditionModel(message.id)?.dispose();
if (newMessage.body) {
updatedMessage.body = newMessage.body;
}
if (newMessage.attachments) {
updatedMessage.attachments = newMessage.attachments;
}
if (newMessage.mentions) {
updatedMessage.mentions = newMessage.mentions;
}

model.updateMessage(id, updatedMessage);
inputModel.dispose();
setEdit(false);
},
[model, message, canEdit]
Expand Down
27 changes: 23 additions & 4 deletions packages/jupyter-chat/src/input-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ISignal, Signal } from '@lumino/signaling';
import { IActiveCellManager } from './active-cell-manager';
import { ISelectionWatcher } from './selection-watcher';
import { IChatContext } from './model';
import { IAttachment, INotebookAttachment, IUser } from './types';
import { IAttachment, INewMessage, INotebookAttachment, IUser } from './types';

/**
* The chat input interface.
Expand Down Expand Up @@ -194,8 +194,27 @@ export class InputModel implements IInputModel {
* Function to send a message.
*/
send = (input: string): void => {
this._onSend(input, this);
const message: INewMessage = {
body: input
};

// Add the attachments
if (this.attachments.length) {
message.attachments = [...this.attachments];
}

// Add the mentions
if (this.mentions.length) {
message.mentions = [...this.mentions];
}

// Send the message
this._onSend(message);

// Clear the input
this.value = '';
this.clearAttachments();
this.clearMentions();
};

/**
Expand Down Expand Up @@ -486,7 +505,7 @@ export class InputModel implements IInputModel {
}

private _id: string;
private _onSend: (input: string, model?: InputModel) => void;
private _onSend: (message: INewMessage) => void;
private _chatContext?: IChatContext;
private _value: string;
private _cursorIndex: number | null = null;
Expand Down Expand Up @@ -519,7 +538,7 @@ export namespace InputModel {
* @param content - the content of the message.
* @param model - the model of the input sending the message.
*/
onSend: (content: string, model?: InputModel) => void;
onSend: (message: INewMessage) => void;

/**
* Function that should cancel the message edition.
Expand Down
2 changes: 1 addition & 1 deletion packages/jupyter-chat/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ export abstract class AbstractChatModel implements IChatModel {
config: {
sendWithShiftEnter: config.sendWithShiftEnter
},
onSend: (input: string) => this.sendMessage({ body: input })
onSend: this.sendMessage.bind(this)
});

this._commands = options.commands;
Expand Down
7 changes: 3 additions & 4 deletions packages/jupyter-chat/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,9 @@ export interface IChatHistory {
/**
* The content of a new message.
*/
export interface INewMessage {
body: string;
id?: string;
}
export type INewMessage<T = IUser, U = IAttachment> = Partial<
IMessageContent<T, U>
>;
Comment thread
dlqqq marked this conversation as resolved.

/**
* The attachment type. Jupyter Chat allows for two types of attachments
Expand Down
27 changes: 18 additions & 9 deletions packages/jupyterlab-chat/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,40 +204,49 @@ export class LabChatModel
}

sendMessage(message: INewMessage): Promise<boolean | void> | boolean | void {
if (!message.body && !message.mime_model && !message.attachments?.length) {
return false;
}
this._resetWritingStatus();
if (this._timeoutWriting !== null) {
window.clearTimeout(this._timeoutWriting);
}

const body = message.body ?? '';
const user = message.sender ?? this._user;

const msg: IYmessage = {
type: 'msg',
id: UUID.uuid4(),
body: message.body,
body,
time: Date.now() / 1000,
sender: this._user.username,
sender: user.username,
raw_time: true
};

// Add the user if it does not exist or has changed
if (!(this.sharedModel.getUser(this._user.username) === this._user)) {
this.sharedModel.setUser(this._user);
// Add the MIME model if provided.
if (message.mime_model) {
msg.mime_model = message.mime_model;
}

// Add the user if it does not exist
if (!this.sharedModel.getUser(user.username)) {
this.sharedModel.setUser(user);
}

// Add the attachments to the message.
const attachmentIds = this.input.attachments?.map(attachment =>
const attachmentIds = message.attachments?.map(attachment =>
this.sharedModel.setAttachment(attachment)
);
if (attachmentIds?.length) {
msg.attachments = attachmentIds;
}
this.input.clearAttachments();

// Add the mentioned users.
const mentions = this._buildMentionList(this.input.mentions, message.body);
const mentions = this._buildMentionList(message.mentions, body);
if (mentions.length) {
msg.mentions = mentions;
}
this.input.clearMentions();

this.sharedModel.addMessage(msg);
}
Expand Down
Loading