remix-nlux
Version:
Remix IDE NLUX integration. Remix IDE is the leading IDE for building and deploying smart contracts on Ethereum. NLUX is a JavaScript and React library for building conversational AI experiences.
160 lines (141 loc) • 6.68 kB
text/typescript
import {AutoScrollController} from '@shared/interactions/autoScroll/type';
import {submitPrompt} from '@shared/services/submitPrompt/submitPromptImpl';
import {ChatAdapterExtras} from '@shared/types/adapters/chat/chatAdapterExtras';
import {ChatSegmentItem} from '@shared/types/chatSegment/chatSegment';
import {NLErrors} from '@shared/types/exceptions/errors';
import {domOp} from '@shared/utils/dom/domOp';
import {warn} from '@shared/utils/warn';
import {ControllerContext} from '../../../../types/controllerContext';
import {CompComposer} from '../../composer/composer.model';
import {CompConversation} from '../../conversation/conversation.model';
export const submitPromptFactory = <
AiMsg
>({
context,
composerInstance,
conversation,
autoScrollController,
messageToSend,
resetComposer,
setComposerAsWaiting,
}: {
context: ControllerContext<AiMsg>;
composerInstance: CompComposer<AiMsg>;
conversation: CompConversation<AiMsg>;
autoScrollController?: AutoScrollController;
messageToSend: string;
resetComposer: (resetTextInput?: boolean) => void;
setComposerAsWaiting: () => void;
}) => {
return () => {
const segmentId = conversation.addChatSegment();
try {
// Disable prompt while sending message
const currentComposerProps = composerInstance.getProp('domCompProps');
composerInstance.setDomProps({...currentComposerProps, status: 'submitting-prompt'});
// Build request and submit prompt
const extras: ChatAdapterExtras<AiMsg> = {
aiChatProps: context.aiChatProps,
conversationHistory: conversation.getConversationContentForAdapter(
context.aiChatProps?.conversationOptions?.historyPayloadSize,
),
};
const result = submitPrompt(
messageToSend,
context.adapter,
extras,
);
// Listen to observable events
// Always listen to error event
result.observable.on('error', (errorId, errorObject) => {
conversation.removeChatSegment(segmentId);
autoScrollController?.handleChatSegmentRemoved(segmentId);
resetComposer(false);
context.exception(errorId);
context.emit('error', {
errorId,
message: NLErrors[errorId],
errorObject,
});
});
result.observable.on('userMessageReceived', (userMessage) => {
conversation.addChatItem(segmentId, userMessage);
context.emit('messageSent', {uid: userMessage.uid, message: userMessage.content});
domOp(() => {
if (autoScrollController) {
const chatSegmentContainer = conversation.getChatSegmentContainer(segmentId);
if (chatSegmentContainer) {
autoScrollController.handleNewChatSegmentAdded(segmentId, chatSegmentContainer);
}
}
});
});
if (result.dataTransferMode === 'batch') {
// In batch mode — Listen to aiMessageReceived event and complete event
result.observable.on('aiMessageReceived', (aiMessage) => {
const isStringContent = typeof aiMessage.content === 'string';
const newAiMessage = {
...aiMessage,
// When content is a string, we stream is instead of adding it into the chat segment
content: (isStringContent ? '' : aiMessage.content),
} as ChatSegmentItem<AiMsg>;
conversation.addChatItem(segmentId, newAiMessage);
if (isStringContent) {
conversation.addChunk(
segmentId,
newAiMessage.uid,
aiMessage.content,
aiMessage.serverResponse,
);
}
conversation.completeChatSegment(segmentId);
context.emit('messageReceived', {
uid: aiMessage.uid,
message: aiMessage.content,
});
resetComposer(true);
});
} else {
result.observable.on('aiMessageStreamStarted', (aiMessageStream) => {
conversation.addChatItem(segmentId, aiMessageStream);
setComposerAsWaiting();
context.emit('messageStreamStarted', {uid: aiMessageStream.uid});
});
result.observable.on('aiChunkReceived', (item) => {
const {messageId, chunk, serverResponse} = item;
conversation.addChunk(segmentId, messageId, chunk, serverResponse);
});
result.observable.on('aiMessageStreamed', (aiMessage) => {
if (aiMessage.status === 'complete') {
context.emit('messageReceived', {
uid: aiMessage.uid,
// We only pass the response to custom renderer when the status is 'complete'.
message: aiMessage.content as AiMsg,
});
}
});
result.observable.on('aiServerComponentStreamStarted', (aiMessage) => {
conversation.addChatItem(segmentId, aiMessage);
setComposerAsWaiting();
context.emit('messageStreamStarted', {uid: aiMessage.uid});
});
result.observable.on('aiServerComponentStreamed', (aiMessage) => {
if (aiMessage.status === 'complete') {
context.emit('messageReceived', {
uid: aiMessage.uid,
// We only pass the response to custom renderer when the status is 'complete'.
message: aiMessage.content as AiMsg,
});
}
});
result.observable.on('complete', () => {
conversation.completeChatSegment(segmentId);
resetComposer(false);
});
}
} catch (error) {
warn(error);
resetComposer(false);
}
};
};