Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add canSendMessage state & update ChatHeadless constructor #13

Merged
merged 3 commits into from
May 25, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 7 additions & 1 deletion apps/test-site/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ function App() {

function ChatComponent() {
const isLoading = useChatState((s) => s.conversation.isLoading);
const canSendMessage = useChatState((s) => s.conversation.canSendMessage);
const messages = useChatState((s) => s.conversation.messages);
const [input, setInput] = useState("");
const actions = useChatActions();
Expand Down Expand Up @@ -57,7 +58,12 @@ function ChatComponent() {
<p key={i}>{`${m.source}: ${m.text}`}</p>
))}
{isLoading && <p>loading...</p>}
<input type="text" value={input} onChange={onInputChange} />
<input
type="text"
value={input}
disabled={!canSendMessage}
onChange={onInputChange}
/>
<button onClick={onClick}>Send</button>
<button onClick={onClickStream}>Send (Stream)</button>
</div>
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@ Constructs a new instance of the [ChatHeadless](./chat-headless.chatheadless.md)
**Signature:**

```typescript
constructor(config: ChatConfig, saveToSessionStorage?: boolean);
constructor(config: HeadlessConfig);
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| config | ChatConfig | The configuration for the [ChatHeadless](./chat-headless.chatheadless.md) instance |
| saveToSessionStorage | boolean | _(Optional)_ Whether to save the instance's [ConversationState](./chat-headless.conversationstate.md) to session storage. Defaults to true. |
| config | [HeadlessConfig](./chat-headless.headlessconfig.md) | The configuration for the [ChatHeadless](./chat-headless.chatheadless.md) instance |

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Performs a Chat API request for the next message generated by chat bot using the
**Signature:**

```typescript
getNextMessage(text?: string, source?: MessageSource): Promise<MessageResponse>;
getNextMessage(text?: string, source?: MessageSource): Promise<MessageResponse | undefined>;
```

## Parameters
Expand All @@ -21,7 +21,7 @@ getNextMessage(text?: string, source?: MessageSource): Promise<MessageResponse>;

**Returns:**

Promise&lt;MessageResponse&gt;
Promise&lt;MessageResponse \| undefined&gt;

a Promise of a response from the Chat API

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export declare class ChatHeadless

| Constructor | Modifiers | Description |
| --- | --- | --- |
| [(constructor)(config, saveToSessionStorage)](./chat-headless.chatheadless._constructor_.md) | | Constructs a new instance of the [ChatHeadless](./chat-headless.chatheadless.md) class. |
| [(constructor)(config)](./chat-headless.chatheadless._constructor_.md) | | Constructs a new instance of the [ChatHeadless](./chat-headless.chatheadless.md) class. |

## Properties

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Performs a Chat Stream API request for the next message generated by chat bot us
**Signature:**

```typescript
streamNextMessage(text?: string, source?: MessageSource): Promise<MessageResponse>;
streamNextMessage(text?: string, source?: MessageSource): Promise<MessageResponse | undefined>;
```

## Parameters
Expand All @@ -21,7 +21,7 @@ streamNextMessage(text?: string, source?: MessageSource): Promise<MessageRespons

**Returns:**

Promise&lt;MessageResponse&gt;
Promise&lt;MessageResponse \| undefined&gt;

a Promise of the full response from the Chat Stream API

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/chat-headless](./chat-headless.md) &gt; [ConversationState](./chat-headless.conversationstate.md) &gt; [canSendMessage](./chat-headless.conversationstate.cansendmessage.md)

## ConversationState.canSendMessage property

Whether a new message can be sent to Chat API. This is set to false when a previous message is being processed.

**Signature:**

```typescript
canSendMessage: boolean;
```
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

## ConversationState.isLoading property

Whether the next message is currently processing or has finished processing.
Whether the next message is currently processing or has started responding.

**Signature:**

```typescript
isLoading?: boolean;
isLoading: boolean;
```
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ export interface ConversationState

| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [canSendMessage](./chat-headless.conversationstate.cansendmessage.md) | | boolean | Whether a new message can be sent to Chat API. This is set to false when a previous message is being processed. |
| [conversationId?](./chat-headless.conversationstate.conversationid.md) | | string | _(Optional)_ The id of the current conversation. |
| [isLoading?](./chat-headless.conversationstate.isloading.md) | | boolean | _(Optional)_ Whether the next message is currently processing or has finished processing. |
| [isLoading](./chat-headless.conversationstate.isloading.md) | | boolean | Whether the next message is currently processing or has started responding. |
| [messages](./chat-headless.conversationstate.messages.md) | | Message\[\] | The messages in a conversation. |
| [notes?](./chat-headless.conversationstate.notes.md) | | MessageNotes | _(Optional)_ Information relevant to the current state of the conversation, generated and provided by Chat API. |

21 changes: 21 additions & 0 deletions packages/chat-headless/docs/chat-headless.headlessconfig.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/chat-headless](./chat-headless.md) &gt; [HeadlessConfig](./chat-headless.headlessconfig.md)

## HeadlessConfig interface

The configuration for a SearchHeadless instance.

**Signature:**

```typescript
export interface HeadlessConfig extends ChatConfig
```
**Extends:** ChatConfig

## Properties

| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [saveToSessionStorage?](./chat-headless.headlessconfig.savetosessionstorage.md) | | boolean | _(Optional)_ Whether to save the instance's [ConversationState](./chat-headless.conversationstate.md) to session storage. Defaults to true. |

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/chat-headless](./chat-headless.md) &gt; [HeadlessConfig](./chat-headless.headlessconfig.md) &gt; [saveToSessionStorage](./chat-headless.headlessconfig.savetosessionstorage.md)

## HeadlessConfig.saveToSessionStorage property

Whether to save the instance's [ConversationState](./chat-headless.conversationstate.md) to session storage. Defaults to true.

**Signature:**

```typescript
saveToSessionStorage?: boolean;
```
1 change: 1 addition & 0 deletions packages/chat-headless/docs/chat-headless.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
| Interface | Description |
| --- | --- |
| [ConversationState](./chat-headless.conversationstate.md) | Maintains the data for the current conversation. |
| [HeadlessConfig](./chat-headless.headlessconfig.md) | The configuration for a SearchHeadless instance. |
| [MetaState](./chat-headless.metastate.md) | Maintains the metadata for Chat Headless. |
| [State](./chat-headless.state.md) | The state representing a ChatHeadless instance. |
| [StateListener](./chat-headless.statelistener.md) | Represents a listener for a specific value of type T in the state. |
Expand Down
14 changes: 10 additions & 4 deletions packages/chat-headless/etc/chat-headless.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ export { ChatConfig }

// @public
export class ChatHeadless {
constructor(config: ChatConfig, saveToSessionStorage?: boolean);
constructor(config: HeadlessConfig);
addListener<T>(listener: StateListener<T>): Unsubscribe;
getNextMessage(text?: string, source?: MessageSource): Promise<MessageResponse>;
getNextMessage(text?: string, source?: MessageSource): Promise<MessageResponse | undefined>;
restartConversation(): void;
setChatLoadingStatus(isLoading: boolean): void;
setContext(context: any): void;
Expand All @@ -37,19 +37,25 @@ export class ChatHeadless {
get state(): State;
// @internal
get store(): Store;
streamNextMessage(text?: string, source?: MessageSource): Promise<MessageResponse>;
streamNextMessage(text?: string, source?: MessageSource): Promise<MessageResponse | undefined>;
}

// @public
export interface ConversationState {
canSendMessage: boolean;
conversationId?: string;
isLoading?: boolean;
isLoading: boolean;
messages: Message[];
notes?: MessageNotes;
}

export { EndEvent }

// @public
export interface HeadlessConfig extends ChatConfig {
saveToSessionStorage?: boolean;
}

export { Message }

export { MessageNotes }
Expand Down
2 changes: 1 addition & 1 deletion packages/chat-headless/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@yext/chat-headless",
"version": "0.3.0",
"version": "0.3.1",
"description": "A library for powering UI components for Yext Chat integrations",
"main": "./dist/commonjs/index.js",
"module": "./dist/esm/index.js",
Expand Down
48 changes: 38 additions & 10 deletions packages/chat-headless/src/ChatHeadless.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
ChatConfig,
ChatCore,
Message,
MessageNotes,
Expand All @@ -11,6 +10,7 @@ import { State } from "./models/state";
import { ReduxStateManager } from "./ReduxStateManager";
import {
loadSessionState,
setCanSendMessage,
setConversationId,
setIsLoading,
setMessageNotes,
Expand All @@ -20,6 +20,7 @@ import {
import { Store, Unsubscribe } from "@reduxjs/toolkit";
import { StateListener } from "./models";
import { setContext } from "./slices/meta";
import { HeadlessConfig } from "./models/HeadlessConfig";

/**
* Provides the functionality for interacting with a Chat Bot
Expand All @@ -37,12 +38,15 @@ export class ChatHeadless {
* @public
*
* @param config - The configuration for the {@link ChatHeadless} instance
* @param saveToSessionStorage - Whether to save the instance's {@link ConversationState} to session storage. Defaults to true.
*/
constructor(config: ChatConfig, saveToSessionStorage = true) {
this.chatCore = new ChatCore(config);
constructor(config: HeadlessConfig) {
const defaultConfig: Partial<HeadlessConfig> = {
saveToSessionStorage: true,
};
const mergedConfig = { ...defaultConfig, ...config };
this.chatCore = new ChatCore(mergedConfig);
this.stateManager = new ReduxStateManager();
if (saveToSessionStorage) {
if (mergedConfig.saveToSessionStorage) {
this.setState({
...this.state,
conversation: loadSessionState(),
Expand Down Expand Up @@ -149,6 +153,17 @@ export class ChatHeadless {
this.stateManager.dispatch(setConversationId(id));
}

/**
* Sets {@link ConversationState.canSendMessage} to the specified state
*
* @internal
*
* @param canSendMessage - the state to set
*/
private setCanSendMessage(canSendMessage: boolean) {
this.stateManager.dispatch(setCanSendMessage(canSendMessage));
}

/**
* Resets all fields within {@link ConversationState}
*
Expand All @@ -157,6 +172,7 @@ export class ChatHeadless {
restartConversation() {
this.setConversationId(undefined);
this.setChatLoadingStatus(false);
this.setCanSendMessage(true);
this.setMessageNotes({});
this.setMessages([]);
}
Expand Down Expand Up @@ -190,7 +206,7 @@ export class ChatHeadless {
async getNextMessage(
text?: string,
source: MessageSource = MessageSource.USER
): Promise<MessageResponse> {
): Promise<MessageResponse | undefined> {
return this.nextMessageHandler(
async () => {
const { messages, conversationId, notes } = this.state.conversation;
Expand Down Expand Up @@ -229,7 +245,7 @@ export class ChatHeadless {
async streamNextMessage(
text?: string,
source: MessageSource = MessageSource.USER
): Promise<MessageResponse> {
): Promise<MessageResponse | undefined> {
return this.nextMessageHandler(
async () => {
let messageResponse: MessageResponse | undefined = undefined;
Expand All @@ -245,6 +261,7 @@ export class ChatHeadless {
context: this.state.meta.context,
});
stream.addEventListener(StreamEventName.StartEvent, ({ data }) => {
this.setChatLoadingStatus(false);
this.setMessageNotes(data);
});
stream.addEventListener(
Expand Down Expand Up @@ -277,8 +294,10 @@ export class ChatHeadless {

/**
* Setup relevant state before hitting Chat API endpoint for next message, such as
* setting loading status and appending new user's message in conversation state.
* Also update loading state when the next message is received or an error occurred.
* setting loading status, "canSendMessage" status, and appending new user's message
* in conversation state.
*
* @internal
*
* @param nextMessageFn - function to invoke to get next message
* @param text - the text of the next message
Expand All @@ -289,7 +308,14 @@ export class ChatHeadless {
nextMessageFn: () => Promise<MessageResponse>,
text?: string,
source: MessageSource = MessageSource.USER
): Promise<MessageResponse> {
): Promise<MessageResponse | undefined> {
if (!this.state.conversation.canSendMessage) {
console.warn(
"Unable to process new message at the moment. Another message is still being processed."
);
return;
}
this.setCanSendMessage(false);
this.setChatLoadingStatus(true);
let messages: Message[] = this.state.conversation.messages;
if (text && text.length > 0) {
Expand All @@ -307,9 +333,11 @@ export class ChatHeadless {
try {
messageResponse = await nextMessageFn();
} catch (e) {
this.setCanSendMessage(true);
this.setChatLoadingStatus(false);
return Promise.reject(e as Error);
}
this.setCanSendMessage(true);
this.setChatLoadingStatus(false);
return messageResponse;
}
Expand Down
11 changes: 11 additions & 0 deletions packages/chat-headless/src/models/HeadlessConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ChatConfig } from "@yext/chat-core";

/**
* The configuration for a SearchHeadless instance.
*
* @public
*/
export interface HeadlessConfig extends ChatConfig {
/** Whether to save the instance's {@link ConversationState} to session storage. Defaults to true. */
saveToSessionStorage?: boolean;
}
5 changes: 3 additions & 2 deletions packages/chat-headless/src/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { State } from "./state";
export { ConversationState } from "./slices/conversation";
export { MetaState } from "./slices/meta";
export { ConversationState } from "./slices/ConversationState";
export { MetaState } from "./slices/MetaState";
export { StateListener } from "./utils/StateListeners";
export { HeadlessConfig } from "./HeadlessConfig";
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ export interface ConversationState {
messages: Message[];
/** Information relevant to the current state of the conversation, generated and provided by Chat API. */
notes?: MessageNotes;
/** Whether the next message is currently processing or has finished processing. */
isLoading?: boolean;
/** Whether the next message is currently processing or has started responding. */
isLoading: boolean;
/**
* Whether a new message can be sent to Chat API.
* This is set to false when a previous message is being processed.
*/
canSendMessage: boolean;
}
4 changes: 2 additions & 2 deletions packages/chat-headless/src/models/state.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ConversationState } from "./slices/conversation";
import { MetaState } from "./slices/meta";
import { ConversationState } from "./slices/ConversationState";
import { MetaState } from "./slices/MetaState";

/**
* The state representing a ChatHeadless instance.
Expand Down
Loading