Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 9 additions & 0 deletions packages/instantsearch.js/src/connectors/chat/connectChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ export type ChatConnectorParams<TUiMessage extends UIMessage = UIMessage> = (
* @default 'chat'
*/
type?: string;
/**
* A message to send automatically when the chat is initialized.
*/
initialUserMessage?: string;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have you also considered passing messages or initialMessages? that would be a bit more flexible, but I'm not sure if it's useful for anything

};

export type ChatWidgetDescription<TUiMessage extends UIMessage = UIMessage> = {
Expand Down Expand Up @@ -260,6 +264,7 @@ export default (function connectChat<TWidgetParams extends UnknownWidgetParams>(
resume = false,
tools = {},
type = 'chat',
initialUserMessage,
...options
} = widgetParams || {};

Expand Down Expand Up @@ -547,6 +552,10 @@ export default (function connectChat<TWidgetParams extends UnknownWidgetParams>(
_chatInstance.resumeStream();
}

if (initialUserMessage && _chatInstance.messages.length === 0) {
_chatInstance.sendMessage({ text: initialUserMessage });
}

renderFn(
{
...this.getWidgetRenderState(initOptions),
Expand Down
28 changes: 28 additions & 0 deletions tests/common/connectors/chat/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createSearchClient } from '@instantsearch/mocks';
import { wait } from '@instantsearch/testutils';
import { screen } from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
import { Chat } from 'instantsearch.js/src/lib/chat';

import { skippableDescribe } from '../../common';

Expand Down Expand Up @@ -31,6 +32,33 @@ export function createOptionsTests(
`);
});

test('sends initialUserMessage on init', async () => {
const chat = new Chat({});
const sendMessageSpy = jest
.spyOn(chat, 'sendMessage')
.mockResolvedValue(undefined);

const options: SetupOptions<ChatConnectorSetup> = {
instantSearchOptions: {
indexName: 'indexName',
searchClient: createSearchClient(),
},
widgetParams: {
chat,
agentId: 'agentId',
initialUserMessage: 'Hello, AI!',
} as any,
};

await setup(options);

await act(async () => {
await wait(0);
});

expect(sendMessageSpy).toHaveBeenCalledWith({ text: 'Hello, AI!' });
});

test('provides `input` state to persist text input', async () => {
const options: SetupOptions<ChatConnectorSetup> = {
instantSearchOptions: {
Expand Down
74 changes: 74 additions & 0 deletions tests/common/widgets/chat/options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,80 @@ export function createOptionsTests(
expect(document.querySelector('.ais-ChatPrompt')).toBeInTheDocument();
});

test('sends initialUserMessage on init', async () => {
const searchClient = createSearchClient();

const chat = new Chat({});
const sendMessageSpy = jest
.spyOn(chat, 'sendMessage')
.mockResolvedValue(undefined);

await setup({
instantSearchOptions: {
indexName: 'indexName',
searchClient,
},
widgetParams: {
javascript: {
...createDefaultWidgetParams(chat),
initialUserMessage: 'Hello, AI!',
},
react: {
...createDefaultWidgetParams(chat),
initialUserMessage: 'Hello, AI!',
},
vue: {},
},
});

await act(async () => {
await wait(0);
});

expect(sendMessageSpy).toHaveBeenCalledWith({ text: 'Hello, AI!' });
});

test('does not send initialUserMessage when messages already exist', async () => {
const searchClient = createSearchClient();

const chat = new Chat({
messages: [
{
id: '1',
role: 'user',
parts: [{ type: 'text', text: 'Previous message' }],
},
],
});
const sendMessageSpy = jest
.spyOn(chat, 'sendMessage')
.mockResolvedValue(undefined);

await setup({
instantSearchOptions: {
indexName: 'indexName',
searchClient,
},
widgetParams: {
javascript: {
...createDefaultWidgetParams(chat),
initialUserMessage: 'Hello, AI!',
},
react: {
...createDefaultWidgetParams(chat),
initialUserMessage: 'Hello, AI!',
},
vue: {},
},
});

await act(async () => {
await wait(0);
});

expect(sendMessageSpy).not.toHaveBeenCalled();
});

test('sends messages when prompt is submitted', async () => {
const searchClient = createSearchClient();

Expand Down
Loading