Skip to content

Commit

Permalink
Enabled sending conversation history through adapters
Browse files Browse the repository at this point in the history
  • Loading branch information
salmenus committed Feb 11, 2024
1 parent 3dc0c3a commit 8b7271d
Show file tree
Hide file tree
Showing 62 changed files with 1,491 additions and 227 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/run-all-tests.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs

name: 200+ Unit Tests
name: 250+ Unit Tests

on:
push:
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
## The JS / React Library For Building Conversational AI Interfaces ✨💬

NLUX _(for Natural Language User Experience)_ is an open-source Javascript library that makes it super simple to
integrate
powerful large language models (LLMs) like ChatGPT into your web app or website. With just a few lines of code, you
can add conversational AI capabilities and interact with your favourite LLM.
integrate powerful large language models (LLMs) like ChatGPT into your web app or website. With just a few lines
of code, you can add conversational AI capabilities and interact with your favourite LLM.

## Key Features 🌟

Expand Down Expand Up @@ -88,6 +87,8 @@ cross platforms, with a focus on performance and usability.
## Community & Support 🙏

* **Star The Repo** 🌟 ― If you like NLUX, please star the repo to show your support.


* **[GitHub Discussions](https://github.com/nluxai/nlux/discussions)** ― Ask questions, report issues, and share your
ideas with the community.
* **[Discord Community](https://discord.gg/VY4TDaf4)** ― Join our Discord server to chat with the community and get
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Observable} from '../../../../core/bus/observable';
import {ExceptionId} from '../../../../exceptions/exceptions';
import {DataTransferMode} from '../../../../types/adapter';
import {AdapterExtras, DataTransferMode} from '../../../../types/adapter';
import {NluxContext} from '../../../../types/context';
import {warn} from '../../../../x/warn';
import {CompConversation} from '../../conversation/conversation.model';
Expand Down Expand Up @@ -70,23 +70,30 @@ export const submitPromptFactory = ({
// Set the default data transfer mode based on the adapter's capabilities
const defaultDataTransferMode = supportedDataTransferModes.length === 1 ?
supportedDataTransferModes[0] : 'stream';

const dataTransferModeToUse = dataTransferMode ?? defaultDataTransferMode;
const extras: AdapterExtras = {
aiChatProps: context.aiChatProps,
conversationHistory: conversation.getConversationContentForAdapter(
context.aiChatProps?.conversationOptions?.historyPayloadSize,
),
};

if (dataTransferModeToUse === 'stream') {
if (!context.adapter.streamText) {
throw new Error('Streaming mode requested but adapter does not implement streamText');
}

observable = new Observable<string>();
context.adapter.streamText(messageToSend, observable);
context.adapter.streamText(messageToSend, observable, extras);
messageContentType = 'stream';
} else {
if (!context.adapter.fetchText) {
throw new Error('Fetch mode requested but adapter does not implement fetchText');
}

observable = undefined;
sentResponse = context.adapter.fetchText(messageToSend);
sentResponse = context.adapter.fetchText(messageToSend, extras);
messageContentType = 'promise';
}

Expand Down Expand Up @@ -115,6 +122,11 @@ export const submitPromptFactory = ({
sentResponse.then((promiseContent) => {
message.setContent(promiseContent);
resetPromptBox(true);

// Only add user message to conversation content (used for history, and not displayed) if the
// message was sent successfully and a response was received.
conversation.updateConversationContent({role: 'user', message: messageToSend});
conversation.updateConversationContent({role: 'ai', message: promiseContent});
context.emit('messageReceived', promiseContent);
}).catch((error) => {
message.setErrored();
Expand Down Expand Up @@ -159,7 +171,12 @@ export const submitPromptFactory = ({
complete: () => {
message.commitContent();
resetPromptBox(true);

if (message.content) {
// Only add user message to conversation content (used for history, and not displayed)
// if the message was sent successfully and a response was received.
conversation.updateConversationContent({role: 'user', message: messageToSend});
conversation.updateConversationContent({role: 'ai', message: message.content});
context.emit('messageReceived', message.content);
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class CompChatRoom extends BaseComp<
promptBox,
botPersona,
userPersona,
conversationHistory,
initialConversationContent,
}: CompChatRoomProps) {
super(context, {
visible,
Expand All @@ -58,7 +58,7 @@ export class CompChatRoom extends BaseComp<
getStreamingAnimationSpeed(streamingAnimationSpeed),
botPersona,
userPersona,
conversationHistory,
initialConversationContent,
);

this.addPromptBox(promptBox?.placeholder, promptBox?.autoFocus);
Expand Down Expand Up @@ -137,7 +137,7 @@ export class CompChatRoom extends BaseComp<
streamingAnimationSpeed: number,
botPersona?: BotPersona,
userPersona?: UserPersona,
conversationHistory?: readonly ConversationItem[],
initialConversationContent?: readonly ConversationItem[],
) {
this.conversation = comp(CompConversation)
.withContext(this.context)
Expand All @@ -146,7 +146,7 @@ export class CompChatRoom extends BaseComp<
streamingAnimationSpeed,
botPersona,
userPersona,
messages: conversationHistory,
messages: initialConversationContent,
})
.create();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type CompChatRoomProps = {
visible?: boolean;
botPersona?: BotPersona,
userPersona?: UserPersona,
conversationHistory?: readonly ConversationItem[];
initialConversationContent?: readonly ConversationItem[];
scrollWhenGenerating?: boolean;
streamingAnimationSpeed?: number | null;
containerMaxHeight?: number | string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import {BotPersona, UserPersona} from '@nlux/core';
import {BaseComp} from '../../../core/comp/base';
import {comp} from '../../../core/comp/comp';
import {CompEventListener, Model} from '../../../core/comp/decorators';
import {HistoryPayloadSize} from '../../../core/options/conversationOptions';
import {NluxContext} from '../../../types/context';
import {ConversationItem} from '../../../types/conversation';
import {warnOnce} from '../../../x/warn';
import {CompList} from '../../miscellaneous/list/model';
import {messageInList, textMessage} from '../chat-room/utils/textMessage';
import {CompMessage} from '../message/message.model';
Expand All @@ -21,6 +24,7 @@ import {updateConversation} from './conversation.update';
export class CompConversation extends BaseComp<
CompConversationProps, CompConversationElements, CompConversationEvents, CompConversationActions
> {
private conversationContent: ConversationItem[] = [];
private lastMessageId?: string;
private lastMessageResizedListener?: Function;
private messagesContainerRendered: boolean = false;
Expand All @@ -32,6 +36,7 @@ export class CompConversation extends BaseComp<
super(context, props);
this.addConversation();
this.scrollWhenGeneratingUserOption = props.scrollWhenGenerating ?? true;
this.conversationContent = props.messages?.map((message) => ({...message})) ?? [];
}

public addMessage(
Expand Down Expand Up @@ -107,6 +112,30 @@ export class CompConversation extends BaseComp<
return message.id;
}

public getConversationContentForAdapter(
historyPayloadSize: HistoryPayloadSize = 'max',
): Readonly<ConversationItem[]> | undefined {
if (typeof historyPayloadSize === 'number' && historyPayloadSize <= 0) {
warnOnce(
`Invalid value provided for 'historyPayloadSize' : "${historyPayloadSize}"! ` +
`Value must be a positive integer or 'all'.`,
);

return undefined;
}

if (historyPayloadSize === 'none') {
return undefined;
}

if (historyPayloadSize === 'max') {
// We should return a new reference
return [...this.conversationContent];
}

return this.conversationContent.slice(-historyPayloadSize);
}

public getMessageById(messageId: string): CompMessage | undefined {
return this.messagesList?.getComponentById(messageId);
}
Expand Down Expand Up @@ -150,6 +179,10 @@ export class CompConversation extends BaseComp<
this.scrollWhenGeneratingUserOption = autoScrollToStreamingMessage;
}

public updateConversationContent(newItem: ConversationItem) {
this.conversationContent.push(newItem);
}

private addConversation() {
this.messagesList = comp(CompList<CompMessage>).withContext(this.context).create();
this.addSubComponent(this.messagesList.id, this.messagesList, 'messagesContainer');
Expand Down
28 changes: 14 additions & 14 deletions packages/js/core/src/core/aiChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {Adapter} from '../types/adapter';
import {AdapterBuilder} from '../types/adapterBuilder';
import {ConversationItem} from '../types/conversation';
import {EventCallback, EventName, EventsMap} from '../types/event';
import {NluxProps} from '../types/props';
import {AiChatProps} from '../types/props';
import {StandardAdapter} from '../types/standardAdapter';
import {debug} from '../x/debug';
import {NluxController} from './controller/controller';
Expand All @@ -20,8 +20,8 @@ export class AiChat implements IAiChat {
protected theAdapterBuilder: StandardAdapter<any, any> | null = null;
protected theAdapterType: 'builder' | 'instance' | null = null;
protected theClassName: string | null = null;
protected theConversationHistory: ConversationItem[] | null = null;
protected theConversationOptions: ConversationOptions | null = null;
protected theInitialConversation: ConversationItem[] | null = null;
protected theLayoutOptions: LayoutOptions | null = null;
protected thePersonasOptions: PersonaOptions | null = null;
protected thePromptBoxOptions: PromptBoxOptions | null = null;
Expand Down Expand Up @@ -77,7 +77,7 @@ export class AiChat implements IAiChat {
themeId: this.theThemeId ?? undefined,
adapter: adapterToUser,
className: this.theClassName ?? undefined,
conversationHistory: this.theConversationHistory ?? undefined,
initialConversation: this.theInitialConversation ?? undefined,
syntaxHighlighter: this.theSyntaxHighlighter ?? undefined,
layoutOptions: this.theLayoutOptions ?? {},
conversationOptions: this.theConversationOptions ?? {},
Expand Down Expand Up @@ -171,7 +171,7 @@ export class AiChat implements IAiChat {
this.unregisteredEventListeners.clear();
}

public updateProps(props: Partial<NluxProps>) {
public updateProps(props: Partial<AiChatProps>) {
if (!this.controller) {
throw new NluxRenderingError({
source: this.constructor.name,
Expand Down Expand Up @@ -249,41 +249,41 @@ export class AiChat implements IAiChat {
return this;
}

public withConversationHistory(conversationHistory: ConversationItem[]) {
public withConversationOptions(conversationOptions: ConversationOptions) {
if (this.mounted) {
throw new NluxUsageError({
source: this.constructor.name,
message: 'Unable to set conversation history. NLUX is already mounted.',
message: 'Unable to set conversation options. NLUX is already mounted.',
});
}

if (this.theConversationHistory) {
if (this.theConversationOptions) {
throw new NluxUsageError({
source: this.constructor.name,
message: 'Unable to change config. Conversation history was already set.',
message: 'Unable to change config. Conversation options were already set.',
});
}

this.theConversationHistory = conversationHistory;
this.theConversationOptions = conversationOptions;
return this;
}

public withConversationOptions(conversationOptions: ConversationOptions) {
public withInitialConversation(initialConversation: ConversationItem[]) {
if (this.mounted) {
throw new NluxUsageError({
source: this.constructor.name,
message: 'Unable to set conversation options. NLUX is already mounted.',
message: 'Unable to set conversation history. NLUX is already mounted.',
});
}

if (this.theConversationOptions) {
if (this.theInitialConversation) {
throw new NluxUsageError({
source: this.constructor.name,
message: 'Unable to change config. Conversation options were already set.',
message: 'Unable to change config. Conversation history was already set.',
});
}

this.theConversationOptions = conversationOptions;
this.theInitialConversation = initialConversation;
return this;
}

Expand Down
5 changes: 5 additions & 0 deletions packages/js/core/src/core/context.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {ContextProps, NluxContext} from '../types/context';
import {EventName, EventsMap} from '../types/event';
import {AiChatProps} from '../types/props';

export const createContext = (
props: ContextProps,
getAiChatProps: () => Readonly<AiChatProps>,
emitEvent: <EventToEmit extends EventName>(
event: EventToEmit,
...params: Parameters<EventsMap[EventToEmit]>
Expand All @@ -19,6 +21,9 @@ export const createContext = (
) => {
emitEvent(eventName, ...params);
},
get aiChatProps() {
return getAiChatProps();
},
};

return context;
Expand Down
Loading

0 comments on commit 8b7271d

Please sign in to comment.