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
2 changes: 1 addition & 1 deletion examples/react/getting-started/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export function App() {
</div>

<Chat
agentId="7c2f6816-bfdb-46e9-a51f-9cb8e5fc9628"
agentId="92a0b174-344c-4b0c-bde8-90b5d221f2b3"
itemComponent={ItemComponent}
/>
</InstantSearch>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ export type ChatMessageProps = ComponentProps<'article'> & {
* Close the chat
*/
onClose: () => void;
/**
* Function to send a message
*/
sendMessage: (message: string) => void;
/**
* Array of tools available for the assistant (for tool messages)
*/
Expand Down Expand Up @@ -158,6 +162,7 @@ export function createChatMessageComponent({ createElement }: Renderer) {
indexUiState,
setIndexUiState,
onClose,
sendMessage,
translations: userTranslations,
...props
} = userProps;
Expand Down Expand Up @@ -216,7 +221,18 @@ export function createChatMessageComponent({ createElement }: Renderer) {
toolCallId: toolMessage.toolCallId,
});

if (!ToolLayoutComponent) {
if (toolMessage.state === 'input-available' && tool.renderLast) {
boundAddToolResult({ output: { message: '' } });
}

if (
!ToolLayoutComponent ||
(tool.renderLast &&
(!message.parts.find((p) => p.type === 'text') ||
message.parts.some(
(p) => p.type === 'text' && p.state === 'streaming'
)))
) {
return null;
}

Expand All @@ -230,6 +246,7 @@ export function createChatMessageComponent({ createElement }: Renderer) {
indexUiState={indexUiState}
setIndexUiState={setIndexUiState}
addToolResult={boundAddToolResult}
sendMessage={sendMessage}
onClose={onClose}
/>
</div>
Expand All @@ -239,6 +256,17 @@ export function createChatMessageComponent({ createElement }: Renderer) {
return null;
}

const toolToRenderLast = message.parts.find((part) => {
if (!startsWith(part.type, 'tool-')) {
return false;
}

const toolName = part.type.replace('tool-', '');
const tool = tools[toolName];

return tool?.renderLast;
});

return (
<article
{...props}
Expand All @@ -254,7 +282,11 @@ export function createChatMessageComponent({ createElement }: Renderer) {

<div className={cx(cssClasses.content)}>
<div className={cx(cssClasses.message)}>
{message.parts.map(renderMessagePart)}
{message.parts
.filter((part) => part !== toolToRenderLast)
.map(renderMessagePart)}
{toolToRenderLast &&
renderMessagePart(toolToRenderLast, message.parts.length - 1)}
</div>

{hasActions && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export type ChatMessagesProps<
* Function to close the chat
*/
onClose: () => void;
sendMessage: (message: string) => void;
/**
* Optional class names
*/
Expand Down Expand Up @@ -194,6 +195,7 @@ function createDefaultMessageComponent<
setIndexUiState,
onReload,
onClose,
sendMessage,
actionsComponent,
classNames,
messageTranslations,
Expand All @@ -209,6 +211,7 @@ function createDefaultMessageComponent<
onReload: (messageId?: string) => void;
onClose: () => void;
actionsComponent?: ChatMessageProps['actionsComponent'];
sendMessage: (newMessage: string) => void;
translations: ChatMessagesTranslations;
classNames?: Partial<ChatMessageClassNames>;
messageTranslations?: Partial<ChatMessageTranslations>;
Expand Down Expand Up @@ -244,6 +247,7 @@ function createDefaultMessageComponent<
indexUiState={indexUiState}
setIndexUiState={setIndexUiState}
onClose={onClose}
sendMessage={sendMessage}
actions={defaultActions}
actionsComponent={actionsComponent}
data-role={message.role}
Expand Down Expand Up @@ -288,6 +292,7 @@ export function createChatMessagesComponent({
hideScrollToBottom = false,
onReload,
onClose,
sendMessage,
translations: userTranslations,
userMessageProps,
assistantMessageProps,
Expand Down Expand Up @@ -361,6 +366,7 @@ export function createChatMessagesComponent({
onReload={onReload}
actionsComponent={ActionsComponent}
onClose={onClose}
sendMessage={sendMessage}
translations={translations}
classNames={messageClassNames}
messageTranslations={messageTranslations}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type ClientSideToolComponentProps = {
setIndexUiState: (state: object) => void;
onClose: () => void;
addToolResult: AddToolResultWithOutput;
sendMessage: (message: string) => void;
};

export type ClientSideToolComponent = (
Expand All @@ -40,6 +41,7 @@ export type ClientSideTool = {
addToolResult: AddToolResultWithOutput;
}
) => void;
renderLast?: boolean;
};
export type ClientSideTools = Record<string, ClientSideTool>;

Expand Down
1 change: 1 addition & 0 deletions packages/instantsearch.js/src/lib/chat/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export { Chat } from './chat';

export const SearchIndexToolType = 'algolia_search_index';
export const RecommendToolType = 'algolia_recommend';
export const PromptSuggestionsToolType = 'algolia_prompt_suggestions';
6 changes: 6 additions & 0 deletions packages/react-instantsearch/src/widgets/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { createChatComponent } from 'instantsearch-ui-components';
import {
SearchIndexToolType,
RecommendToolType,
PromptSuggestionsToolType,
} from 'instantsearch.js/es/lib/chat';
import React, { createElement, Fragment } from 'react';
import { useInstantSearch, useChat } from 'react-instantsearch-core';

import { useStickToBottom } from '../lib/useStickToBottom';

import { createPromptSuggestionsTool } from './chat/tools/PromptSuggestionsTool';
import { createCarouselTool } from './chat/tools/SearchIndexTool';

export { SearchIndexToolType, RecommendToolType };
Expand Down Expand Up @@ -45,6 +47,7 @@ export function createDefaultTools<TObject extends RecordWithObjectID>(
itemComponent,
getSearchPageURL
),
[PromptSuggestionsToolType]: createPromptSuggestionsTool(),
};
}

Expand Down Expand Up @@ -268,6 +271,9 @@ export function Chat<
},
translations: messagesTranslations,
messageTranslations,
sendMessage: (newMessage: string) => {
sendMessage({ text: newMessage });
},
...messagesProps,
}}
promptProps={{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { createButtonComponent } from 'instantsearch-ui-components';
import React, { createElement } from 'react';

import type {
ClientSideToolComponentProps,
Pragma,
UserClientSideTool,
} from 'instantsearch-ui-components';

export function createPromptSuggestionsTool(): UserClientSideTool {
const Button = createButtonComponent({
createElement: createElement as Pragma,
});

function PromptSuggestionsComponent({
message,
sendMessage,
}: ClientSideToolComponentProps) {
const input = message.input as { promptSuggestions?: string[] } | undefined;
const promptSuggestions = input?.promptSuggestions;

if (!promptSuggestions || promptSuggestions.length === 0) {
return <></>;
}

return (
<ul className="ais-ChatMessage-toolPromptSuggestions">
{promptSuggestions.map((suggestion, index) => (
<li key={index} style={{ marginBottom: '0.5rem' }}>
<Button onClick={() => sendMessage(suggestion)}>
{suggestion}
</Button>
</li>
))}
</ul>
);
}

return {
layoutComponent: PromptSuggestionsComponent,
renderLast: true,
};
}