Skip to content

Commit a86b6f3

Browse files
authored
feat(chat): add assistant and user message components/templates in widget (#6797)
1 parent 630a9e6 commit a86b6f3

7 files changed

Lines changed: 455 additions & 4 deletions

File tree

packages/instantsearch-ui-components/src/components/chat/Chat.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export type ChatClassNames = {
2121
container?: string | string[];
2222
header?: ChatHeaderProps['classNames'];
2323
messages?: ChatMessagesProps['classNames'];
24+
message?: ChatMessagesProps['messageClassNames'];
2425
prompt?: ChatPromptProps['classNames'];
2526
toggleButton?: ChatToggleButtonProps['classNames'];
2627
};
@@ -118,7 +119,11 @@ export function createChatComponent({ createElement, Fragment }: Renderer) {
118119
classNames: classNames.header,
119120
maximized,
120121
})}
121-
<ChatMessages {...messagesProps} classNames={classNames.messages} />
122+
<ChatMessages
123+
{...messagesProps}
124+
classNames={classNames.messages}
125+
messageClassNames={classNames.message}
126+
/>
122127
{createElement(PromptComponent || ChatPrompt, {
123128
...promptProps,
124129
classNames: classNames.prompt,

packages/instantsearch-ui-components/src/components/chat/ChatMessages.tsx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ import {
1313
} from './icons';
1414

1515
import type { ComponentProps, MutableRef, Renderer } from '../../types';
16-
import type { ChatMessageProps, ChatMessageActionProps } from './ChatMessage';
16+
import type {
17+
ChatMessageProps,
18+
ChatMessageActionProps,
19+
ChatMessageClassNames,
20+
ChatMessageTranslations,
21+
} from './ChatMessage';
1722
import type { ChatMessageErrorProps } from './ChatMessageError';
1823
import type { ChatMessageLoaderProps } from './ChatMessageLoader';
1924
import type { ChatMessageBase, ChatStatus, ClientSideTools } from './types';
@@ -115,10 +120,18 @@ export type ChatMessagesProps<
115120
* Optional class names
116121
*/
117122
classNames?: Partial<ChatMessagesClassNames>;
123+
/**
124+
* Optional message class names
125+
*/
126+
messageClassNames?: Partial<ChatMessageClassNames>;
118127
/**
119128
* Optional translations
120129
*/
121130
translations?: Partial<ChatMessagesTranslations>;
131+
/**
132+
* Optional message translations
133+
*/
134+
messageTranslations?: Partial<ChatMessageTranslations>;
122135
/**
123136
* Optional user message props
124137
*/
@@ -181,8 +194,10 @@ function createDefaultMessageComponent<
181194
setIndexUiState,
182195
onReload,
183196
onClose,
184-
translations,
185197
actionsComponent,
198+
classNames,
199+
messageTranslations,
200+
translations,
186201
}: {
187202
key: string;
188203
message: TMessage;
@@ -193,8 +208,10 @@ function createDefaultMessageComponent<
193208
tools: ClientSideTools;
194209
onReload: (messageId?: string) => void;
195210
onClose: () => void;
196-
translations: ChatMessagesTranslations;
197211
actionsComponent?: ChatMessageProps['actionsComponent'];
212+
translations: ChatMessagesTranslations;
213+
classNames?: Partial<ChatMessageClassNames>;
214+
messageTranslations?: Partial<ChatMessageTranslations>;
198215
}) {
199216
const defaultAssistantActions: ChatMessageActionProps[] = [
200217
...(hasTextContent(message)
@@ -230,6 +247,8 @@ function createDefaultMessageComponent<
230247
actions={defaultActions}
231248
actionsComponent={actionsComponent}
232249
data-role={message.role}
250+
classNames={classNames}
251+
translations={messageTranslations}
233252
{...messageProps}
234253
/>
235254
);
@@ -255,6 +274,8 @@ export function createChatMessagesComponent({
255274
>(userProps: ChatMessagesProps<TMessage>) {
256275
const {
257276
classNames = {},
277+
messageClassNames = {},
278+
messageTranslations,
258279
messages = [],
259280
messageComponent: MessageComponent,
260281
loaderComponent: LoaderComponent,
@@ -341,6 +362,8 @@ export function createChatMessagesComponent({
341362
actionsComponent={ActionsComponent}
342363
onClose={onClose}
343364
translations={translations}
365+
classNames={messageClassNames}
366+
messageTranslations={messageTranslations}
344367
/>
345368
))}
346369

packages/instantsearch.js/src/widgets/chat/chat.tsx

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import type {
4545
ChatMessageBase,
4646
ChatMessageErrorProps,
4747
ChatMessageLoaderProps,
48+
ChatMessageProps,
4849
ChatMessagesTranslations,
4950
ChatPromptProps,
5051
ChatPromptTranslations,
@@ -315,7 +316,16 @@ type ChatWrapperProps = {
315316
actionsComponent:
316317
| ((props: { actions: ChatMessageActionProps[] }) => JSX.Element)
317318
| undefined;
319+
assistantMessageProps: {
320+
leadingComponent: ChatMessageProps['leadingComponent'];
321+
footerComponent: ChatMessageProps['footerComponent'];
322+
};
323+
userMessageProps: {
324+
leadingComponent: ChatMessageProps['leadingComponent'];
325+
footerComponent: ChatMessageProps['footerComponent'];
326+
};
318327
translations: Partial<ChatMessagesTranslations>;
328+
messageTranslations: Partial<ChatMessageProps['translations']>;
319329
};
320330
promptProps: {
321331
layoutComponent: ComponentProps<typeof Chat>['promptComponent'];
@@ -402,7 +412,10 @@ function ChatWrapper({
402412
loaderComponent: messagesProps.loaderComponent,
403413
errorComponent: messagesProps.errorComponent,
404414
actionsComponent: messagesProps.actionsComponent,
415+
assistantMessageProps: messagesProps.assistantMessageProps,
416+
userMessageProps: messagesProps.userMessageProps,
405417
translations: messagesProps.translations,
418+
messageTranslations: messagesProps.messageTranslations,
406419
}}
407420
promptProps={{
408421
promptRef: promptProps.promptRef,
@@ -444,6 +457,7 @@ const createRenderer = <THit extends RecordWithObjectID = RecordWithObjectID>({
444457
const state = createLocalState();
445458
const promptRef = { current: null as HTMLTextAreaElement | null };
446459

460+
// eslint-disable-next-line complexity
447461
return (props, isFirstRendering) => {
448462
const {
449463
indexUiState,
@@ -613,6 +627,71 @@ const createRenderer = <THit extends RecordWithObjectID = RecordWithObjectID>({
613627
regenerateLabel: templates.messages?.regenerateLabelText,
614628
});
615629

630+
const assistantMessageTemplateProps = prepareTemplateProps({
631+
defaultTemplates: {} as unknown as NonNullable<
632+
Required<ChatTemplates<THit>['assistantMessage']>
633+
>,
634+
templatesConfig: instantSearchInstance.templatesConfig,
635+
templates: templates.assistantMessage,
636+
}) as PreparedTemplateProps<ChatTemplates<THit>>;
637+
const assistantMessageLeadingComponent = templates.assistantMessage?.leading
638+
? () => {
639+
return (
640+
<TemplateComponent
641+
{...assistantMessageTemplateProps}
642+
templateKey="leading"
643+
rootTagName="fragment"
644+
/>
645+
);
646+
}
647+
: undefined;
648+
const assistantMessageFooterComponent = templates.assistantMessage?.footer
649+
? () => {
650+
return (
651+
<TemplateComponent
652+
{...assistantMessageTemplateProps}
653+
templateKey="footer"
654+
rootTagName="fragment"
655+
/>
656+
);
657+
}
658+
: undefined;
659+
660+
const messageTranslations = getDefinedProperties({
661+
actionsLabel: templates.message?.actionsLabelText,
662+
messageLabel: templates.message?.messageLabelText,
663+
});
664+
665+
const userMessageTemplateProps = prepareTemplateProps({
666+
defaultTemplates: {} as unknown as NonNullable<
667+
Required<ChatTemplates<THit>['userMessage']>
668+
>,
669+
templatesConfig: instantSearchInstance.templatesConfig,
670+
templates: templates.userMessage,
671+
}) as PreparedTemplateProps<ChatTemplates<THit>>;
672+
const userMessageLeadingComponent = templates.userMessage?.leading
673+
? () => {
674+
return (
675+
<TemplateComponent
676+
{...userMessageTemplateProps}
677+
templateKey="leading"
678+
rootTagName="fragment"
679+
/>
680+
);
681+
}
682+
: undefined;
683+
const userMessageFooterComponent = templates.userMessage?.footer
684+
? () => {
685+
return (
686+
<TemplateComponent
687+
{...userMessageTemplateProps}
688+
templateKey="footer"
689+
rootTagName="fragment"
690+
/>
691+
);
692+
}
693+
: undefined;
694+
616695
const promptTemplateProps = prepareTemplateProps({
617696
defaultTemplates: {} as unknown as NonNullable<
618697
Required<ChatTemplates<THit>['prompt']>
@@ -746,7 +825,16 @@ const createRenderer = <THit extends RecordWithObjectID = RecordWithObjectID>({
746825
loaderComponent: messagesLoaderComponent,
747826
errorComponent: messagesErrorComponent,
748827
actionsComponent,
828+
assistantMessageProps: {
829+
leadingComponent: assistantMessageLeadingComponent,
830+
footerComponent: assistantMessageFooterComponent,
831+
},
832+
userMessageProps: {
833+
leadingComponent: userMessageLeadingComponent,
834+
footerComponent: userMessageFooterComponent,
835+
},
749836
translations: messagesTranslations,
837+
messageTranslations,
750838
}}
751839
promptProps={{
752840
layoutComponent: promptLayoutComponent,
@@ -868,6 +956,48 @@ export type ChatTemplates<THit extends NonNullable<object> = BaseHit> =
868956
regenerateLabelText?: string;
869957
}>;
870958

959+
/**
960+
* Templates to use for each message.
961+
*/
962+
message: Partial<{
963+
/**
964+
* Label for the message actions
965+
*/
966+
actionsLabelText?: string;
967+
/**
968+
* Label for the message container
969+
*/
970+
messageLabelText?: string;
971+
}>;
972+
973+
/**
974+
* Templates to use for the assistant message.
975+
*/
976+
assistantMessage: Partial<{
977+
/**
978+
* Template to use for the assistant message leading content.
979+
*/
980+
leading: Template;
981+
/**
982+
* Template to use for the assistant message footer content.
983+
*/
984+
footer: Template;
985+
}>;
986+
987+
/**
988+
* Templates to use for the user message.
989+
*/
990+
userMessage: Partial<{
991+
/**
992+
* Template to use for the user message leading content.
993+
*/
994+
leading: Template;
995+
/**
996+
* Template to use for the user message footer content.
997+
*/
998+
footer: Template;
999+
}>;
1000+
8711001
/**
8721002
* Templates to use for the prompt.
8731003
*/

packages/react-instantsearch/src/widgets/Chat.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {
1919
RecordWithObjectID,
2020
UserClientSideTool,
2121
UserClientSideTools,
22+
ChatMessageProps,
2223
} from 'instantsearch-ui-components';
2324
import type { IndexUiState } from 'instantsearch.js';
2425
import type { UIMessage } from 'instantsearch.js/es/lib/chat';
@@ -75,6 +76,11 @@ type UserMessagesProps = Omit<
7576
| 'setIndexUiState'
7677
| 'scrollRef'
7778
| 'contentRef'
79+
| 'messageComponent'
80+
| 'leadingComponent'
81+
| 'footerComponent'
82+
| 'translations'
83+
| 'classNames'
7884
>;
7985

8086
type UserPromptProps = Omit<
@@ -110,9 +116,14 @@ export type ChatProps<TObject, TUiMessage extends UIMessage = UIMessage> = Omit<
110116
promptHeaderComponent?: ChatUiProps['promptProps']['headerComponent'];
111117
promptFooterComponent?: ChatUiProps['promptProps']['footerComponent'];
112118
actionsComponent?: ChatUiProps['messagesProps']['actionsComponent'];
119+
assistantMessageLeadingComponent?: ChatMessageProps['leadingComponent'];
120+
assistantMessageFooterComponent?: ChatMessageProps['footerComponent'];
121+
userMessageLeadingComponent?: ChatMessageProps['leadingComponent'];
122+
userMessageFooterComponent?: ChatMessageProps['footerComponent'];
113123
translations?: Partial<{
114124
prompt: ChatUiProps['promptProps']['translations'];
115125
header: ChatUiProps['headerProps']['translations'];
126+
message: ChatUiProps['messagesProps']['messageTranslations'];
116127
messages: ChatUiProps['messagesProps']['translations'];
117128
}>;
118129
};
@@ -139,6 +150,10 @@ export function Chat<
139150
promptComponent,
140151
promptHeaderComponent,
141152
promptFooterComponent,
153+
assistantMessageLeadingComponent,
154+
assistantMessageFooterComponent,
155+
userMessageLeadingComponent,
156+
userMessageFooterComponent,
142157
actionsComponent,
143158
classNames,
144159
translations = {},
@@ -149,6 +164,7 @@ export function Chat<
149164
const {
150165
prompt: promptTranslations,
151166
header: headerTranslations,
167+
message: messageTranslations,
152168
messages: messagesTranslations,
153169
} = translations;
154170

@@ -240,7 +256,18 @@ export function Chat<
240256
loaderComponent: messagesLoaderComponent,
241257
errorComponent: messagesErrorComponent,
242258
actionsComponent,
259+
assistantMessageProps: {
260+
leadingComponent: assistantMessageLeadingComponent,
261+
footerComponent: assistantMessageFooterComponent,
262+
...messagesProps?.assistantMessageProps,
263+
},
264+
userMessageProps: {
265+
leadingComponent: userMessageLeadingComponent,
266+
footerComponent: userMessageFooterComponent,
267+
...messagesProps?.userMessageProps,
268+
},
243269
translations: messagesTranslations,
270+
messageTranslations,
244271
...messagesProps,
245272
}}
246273
promptProps={{

0 commit comments

Comments
 (0)