Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { cx } from '../../lib';

import type { ComponentProps, Renderer } from '../../types';
import type { ChatEmptyProps } from './types';
import type { ChatComponentPropsWithMetadata } from './types';

export type ChatGreetingTranslations = {
/**
Expand Down Expand Up @@ -35,7 +35,7 @@ export type ChatGreetingClassNames = {
banner?: string | string[];
};

export type ChatGreetingProps = ChatEmptyProps & ComponentProps<'div'> & {
export type ChatGreetingProps = ComponentProps<'div'> & {
/**
* Optional translations
*/
Expand All @@ -53,21 +53,18 @@ export type ChatGreetingProps = ChatEmptyProps & ComponentProps<'div'> & {
export function createChatGreetingComponent({
createElement,
}: Pick<Renderer, 'createElement'>) {
return function ChatGreeting(userProps: ChatGreetingProps) {
return function ChatGreeting(
userProps: ChatComponentPropsWithMetadata<ChatGreetingProps>
) {
const {
translations: userTranslations,
classNames = {},
sendMessage: _sendMessage,
status: _status,
onClose: _onClose,
setInput: _setInput,
metadata,
banner,
...props
} = userProps;
Comment on lines +56 to 65
const translations: Required<ChatGreetingTranslations> = {
heading:
userTranslations?.heading ??
'How can I help you today?',
heading: userTranslations?.heading ?? 'How can I help you today?',
subheading:
userTranslations?.subheading ??
"Ask me anything about our products, and I'll do my best to assist you.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ import { MenuIcon } from './icons';
import type { ComponentProps, Renderer, VNode } from '../../types';
import type {
AddToolResultWithOutput,
ChatComponentPropsWithMetadata,
ChatMessageBase,
ChatStatus,
ChatToolMessage,
ClientSideTools,
} from './types';

export type ChatMessageSide = 'left' | 'right';
Expand Down Expand Up @@ -84,10 +83,6 @@ export type ChatMessageProps = ComponentProps<'article'> & {
* The message object associated with this chat message
*/
message: ChatMessageBase;
/**
* The status of the message (e.g. whether it's still streaming)
*/
status: ChatStatus;
/**
* The side of the message
*/
Expand All @@ -111,10 +106,12 @@ export type ChatMessageProps = ComponentProps<'article'> & {
/**
* Custom actions renderer
*/
actionsComponent?: (props: {
actions: ChatMessageActionProps[];
message: ChatMessageBase;
}) => JSX.Element | null;
actionsComponent?: (
props: ChatComponentPropsWithMetadata<{
actions: ChatMessageActionProps[];
message: ChatMessageBase;
}>
) => JSX.Element | null;
/**
* Footer content
*/
Expand All @@ -133,14 +130,6 @@ export type ChatMessageProps = ComponentProps<'article'> & {
* preceding search tool's hits.
*/
messages?: ChatMessageBase[];
/**
* Close the chat
*/
onClose: () => void;
/**
* Array of tools available for the assistant (for tool messages)
*/
tools: ClientSideTools;
/**
* Optional suggestions element
*/
Expand Down Expand Up @@ -172,29 +161,31 @@ const SearchIndexToolType = 'algolia_search_index';
export function createChatMessageComponent({ createElement }: Renderer) {
const Button = createButtonComponent({ createElement });

return function ChatMessage(userProps: ChatMessageProps) {
return function ChatMessage(
userProps: ChatComponentPropsWithMetadata<ChatMessageProps>
) {
const {
classNames = {},
message,
status,
side = 'left',
variant = 'subtle',
actions = [],
autoHideActions = false,
leadingComponent: LeadingComponent,
actionsComponent: ActionsComponent,
footerComponent: FooterComponent,
tools = {},
indexUiState,
setIndexUiState,
messages,
onClose,
translations: userTranslations,
suggestionsElement,
parseMarkdown = true,
metadata,
...props
} = userProps;

const { status, tools } = metadata;

const translations: Required<ChatMessageTranslations> = {
messageLabel: 'Message',
actionsLabel: 'Message actions',
Expand Down Expand Up @@ -248,10 +239,7 @@ export function createChatMessageComponent({ createElement }: Renderer) {
// Wrapped in a `<p>` to keep some structure for screen readers
// (markdown produces semantic elements; a bare text node would not).
return (
<p
key={`${message.id}-${index}`}
className="ais-ChatMessage-text"
>
<p key={`${message.id}-${index}`} className="ais-ChatMessage-text">
{part.text}
</p>
);
Expand Down Expand Up @@ -315,7 +303,7 @@ export function createChatMessageComponent({ createElement }: Renderer) {
addToolResult={boundAddToolResult}
applyFilters={tool.applyFilters}
sendEvent={tool.sendEvent || (() => {})}
onClose={onClose}
metadata={metadata}
/>
</div>
);
Expand Down Expand Up @@ -350,7 +338,11 @@ export function createChatMessageComponent({ createElement }: Renderer) {
aria-label={translations.actionsLabel}
>
{ActionsComponent ? (
<ActionsComponent actions={actions} message={message} />
<ActionsComponent
actions={actions}
message={message}
metadata={metadata}
/>
) : (
actions.map((action, index) => (
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { createButtonComponent } from '../Button';
import { ReloadIcon } from './icons';

import type { ComponentProps, Renderer } from '../../types';
import type { ChatComponentPropsWithMetadata } from './types';

export type ChatMessageErrorTranslations = {
/**
Expand Down Expand Up @@ -60,13 +61,16 @@ export function createChatMessageErrorComponent({
}: Pick<Renderer, 'createElement'>) {
const Button = createButtonComponent({ createElement });

return function ChatMessageError(userProps: ChatMessageErrorProps) {
return function ChatMessageError(
userProps: ChatComponentPropsWithMetadata<ChatMessageErrorProps>
) {
const {
errorMessage,
onReload,
onNewConversation,
actions,
translations: userTranslations,
metadata,
...props
} = userProps;
Comment on lines +64 to 75
const defaultErrorMessage =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { LoadingSpinnerIcon } from './icons';

import type { ComponentProps, Renderer } from '../../types';
import type { ChatComponentPropsWithMetadata } from './types';

export type ChatMessageLoaderTranslations = {
/**
Expand All @@ -21,8 +22,10 @@ export type ChatMessageLoaderProps = ComponentProps<'article'> & {
export function createChatMessageLoaderComponent({
createElement,
}: Pick<Renderer, 'createElement'>) {
return function ChatMessageLoader(userProps: ChatMessageLoaderProps) {
const { translations: userTranslations, ...props } = userProps;
return function ChatMessageLoader(
userProps: ChatComponentPropsWithMetadata<ChatMessageLoaderProps>
) {
const { translations: userTranslations, metadata, ...props } = userProps;
const translations: Required<ChatMessageLoaderTranslations> = {
Comment on lines +25 to 29
loaderText: '',
...userTranslations,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ import type {
import type { ChatMessageErrorProps } from './ChatMessageError';
import type { ChatMessageLoaderProps } from './ChatMessageLoader';
import type {
ChatEmptyProps,
ChatComponentMetadata,
ChatComponentPropsWithMetadata,
ChatLayoutOwnProps,
ChatMessageBase,
ChatStatus,
Expand Down Expand Up @@ -114,19 +115,27 @@ export type ChatMessagesProps<
/**
* Custom message renderer
*/
messageComponent?: (props: { message: TMessage }) => JSX.Element;
messageComponent?: (
props: ChatComponentPropsWithMetadata<{ message: TMessage }, TMessage>
) => JSX.Element;
/**
* Custom loader component
*/
loaderComponent?: (props: ChatMessageLoaderProps) => JSX.Element;
loaderComponent?: (
props: ChatComponentPropsWithMetadata<ChatMessageLoaderProps, TMessage>
) => JSX.Element;
/**
* Custom error component
*/
errorComponent?: (props: ChatMessageErrorProps) => JSX.Element;
errorComponent?: (
props: ChatComponentPropsWithMetadata<ChatMessageErrorProps, TMessage>
) => JSX.Element;
/**
* Custom empty component shown when there are no messages
*/
emptyComponent?: (props: ChatEmptyProps) => JSX.Element;
emptyComponent?: (
props: ChatComponentPropsWithMetadata<{}, TMessage>
) => JSX.Element;
/**
* Custom actions component
*/
Expand Down Expand Up @@ -282,41 +291,37 @@ function createDefaultMessageComponent<

return function DefaultMessage({
message,
status,
userMessageProps,
assistantMessageProps,
tools,
indexUiState,
setIndexUiState,
messages,
onReload,
onClose,
onFeedback,
feedbackState,
actionsComponent,
classNames,
messageTranslations,
translations,
suggestionsElement,
metadata,
}: {
key: string;
message: TMessage;
status: ChatStatus;
userMessageProps?: Partial<ChatMessageProps>;
assistantMessageProps?: Partial<ChatMessageProps>;
indexUiState: object;
setIndexUiState: (state: object) => void;
messages?: ChatMessageBase[];
tools: ClientSideTools;
onReload: (messageId?: string) => void;
onClose: () => void;
onFeedback?: (messageId: string, vote: 0 | 1) => void;
feedbackState?: Record<string, 'sending' | 0 | 1>;
actionsComponent?: ChatMessageProps['actionsComponent'];
translations: ChatMessagesTranslations;
classNames?: Partial<ChatMessageClassNames>;
messageTranslations?: Partial<ChatMessageTranslations>;
suggestionsElement?: VNode;
metadata: ChatComponentMetadata<TMessage>;
}) {
const defaultAssistantActions: ChatMessageActionProps[] = [
...(hasTextContent(message)
Expand Down Expand Up @@ -389,18 +394,16 @@ function createDefaultMessageComponent<
side={message.role === 'user' ? 'right' : 'left'}
variant={message.role === 'user' ? 'neutral' : 'subtle'}
message={message}
status={status}
tools={tools}
indexUiState={indexUiState}
setIndexUiState={setIndexUiState}
messages={messages}
onClose={onClose}
actions={defaultActions}
actionsComponent={actionsComponent}
data-role={message.role}
classNames={classNames}
translations={messageTranslations}
suggestionsElement={suggestionsElement}
metadata={metadata}
{...messageProps}
/>
);
Expand Down Expand Up @@ -496,6 +499,21 @@ export function createChatMessagesComponent({
const lastPart = lastMessage?.parts?.[lastMessage.parts.length - 1];
const showLoader = getShowLoader(status, lastPart, tools);

// The shared bag handed to every overridable chat component, so custom
// components can read the current chat state and common callbacks from a
// single, consistent place.
const metadata: ChatComponentMetadata<TMessage> = {
messages,
status,
error,
isClearing,
activePart: lastPart,
tools,
sendMessage,
setInput,
onClose,
};

const showEmpty =
messages.length === 0 && !showLoader && !isClearing && status !== 'error';

Expand Down Expand Up @@ -528,33 +546,26 @@ export function createChatMessagesComponent({
}}
>
{showEmpty && EmptyComponent && (
<EmptyComponent
sendMessage={sendMessage}
setInput={setInput}
status={status}
onClose={onClose}
/>
<EmptyComponent metadata={metadata} />
)}

{messages.map((message, index) => (
<DefaultMessage
key={message.id}
message={message}
status={status}
userMessageProps={userMessageProps}
assistantMessageProps={assistantMessageProps}
tools={tools}
indexUiState={indexUiState}
setIndexUiState={setIndexUiState}
messages={messages}
onReload={onReload}
onFeedback={onFeedback}
feedbackState={feedbackState}
actionsComponent={ActionsComponent}
onClose={onClose}
translations={translations}
classNames={messageClassNames}
messageTranslations={messageTranslations}
metadata={metadata}
suggestionsElement={
status === 'ready' &&
message.role === 'assistant' &&
Expand All @@ -568,6 +579,7 @@ export function createChatMessagesComponent({
{showLoader && (
<DefaultLoader
translations={{ loaderText: translations.loaderText }}
metadata={metadata}
/>
)}

Expand All @@ -588,6 +600,7 @@ export function createChatMessagesComponent({
}
: undefined
}
metadata={metadata}
/>
)}
</div>
Expand Down
Loading
Loading