Skip to content
Open
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
37 changes: 32 additions & 5 deletions examples/react/getting-started/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ import 'instantsearch.css/themes/satellite.css';

import './App.css';

const searchClient = algoliasearch(
'latency',
'6be0576ff61c053d5f9a3225e2a90f76'
);
const appId = 'F4T6CUV2AH';
const apiKey = 'f33fd36eb0c251c553e3cd7684a6ba33';
const agentId = '6711d1bb-32fb-46ee-9708-ffc7fd6425b5';

const searchClient = algoliasearch(appId, apiKey);

export function App() {
return (
Expand Down Expand Up @@ -52,6 +53,12 @@ export function App() {
<Panel header="brand">
<RefinementList attribute="brand" />
</Panel>
<Panel header="category">
<RefinementList attribute="categories" />
</Panel>
<Panel header="price range">
<RefinementList attribute="price_range" />
</Panel>
</div>

<div className="search-panel__results">
Expand All @@ -72,8 +79,28 @@ export function App() {
</div>

<Chat
agentId="7c2f6816-bfdb-46e9-a51f-9cb8e5fc9628"
itemComponent={ItemComponent}
transport={{
api: `http://localhost:8000/1/agents/${agentId}/completions?compatibilityMode=ai-sdk-5`,
headers: {
'x-algolia-application-id': appId,
'x-algolia-api-Key': apiKey,
},
}}
transformItems={{
suggestedFilters: (items) =>
items.map((item) => {
const attributeLabels: Record<string, string> = {
'hierarchicalCategories.lvl3': 'Category',
'vakgebied.lvl1': 'Field',
categories: 'Categories',
};
return {
...item,
label: attributeLabels[item.attribute] || item.attribute,
};
}),
}}
/>
</InstantSearch>
</div>
Expand Down
58 changes: 58 additions & 0 deletions packages/instantsearch-ui-components/src/components/FilterPill.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/** @jsx createElement */
import { cx } from '../lib/cx';

import type { Renderer } from '../types';

export type FilterPillClassNames = {
/**
* Class names to apply to the root element
*/
root?: string | string[];
/**
* Class names to apply to the label element
*/
label?: string | string[];
/**
* Class names to apply to the value element
*/
value?: string | string[];
/**
* Class names to apply to the count element
*/
count?: string | string[];
};

export type FilterPillProps = {
label: string;
value: string;
count: number;
onClick: () => void;
classNames?: Partial<FilterPillClassNames>;
key?: string;
};

export function createFilterPillComponent({
createElement,
}: Pick<Renderer, 'createElement'>) {
return function FilterPill(userProps: FilterPillProps) {
const { label, value, count, onClick, classNames = {} } = userProps;

return (
<button
className={cx('ais-FilterPill', classNames.root)}
onClick={onClick}
type="button"
>
<span className={cx('ais-FilterPill-label', classNames.label)}>
{label}:
</span>
<span className={cx('ais-FilterPill-value', classNames.value)}>
{value}
</span>
<span className={cx('ais-FilterPill-count', classNames.count)}>
({count})
</span>
</button>
);
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/** @jsx createElement */

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

import { createFilterPillComponent } from './FilterPill';

import type { Renderer } from '../types';

export type SuggestedFilter = {
label?: string;
attribute: string;
value: string;
count: number;
};

export type SuggestedFiltersClassNames = {
/**
* Class names to apply to the root element
*/
root?: string | string[];
/**
* Class names to apply to the header element
*/
header?: string | string[];
/**
* Class names to apply to the filters list element
*/
list?: string | string[];
};

export type SuggestedFiltersProps = {
filters: SuggestedFilter[];
onFilterClick: (attribute: string, value: string) => void;
classNames?: Partial<SuggestedFiltersClassNames>;
};

export function createSuggestedFiltersComponent({
createElement,
}: Pick<Renderer, 'createElement'>) {
const FilterPill = createFilterPillComponent({
createElement,
});

return function SuggestedFilters(userProps: SuggestedFiltersProps) {
const { filters, onFilterClick, classNames = {} } = userProps;

if (filters.length === 0) {
return null;
}

return (
<div className={cx('ais-ChatToolSuggestedFilters', classNames.root)}>
<div className={cx('ais-SuggestedFilters-header', classNames.header)}>
Suggested Filters
</div>
<div className={cx('ais-SuggestedFilters', classNames.list)}>
{filters.map((filter) => (
<FilterPill
key={`${filter.attribute}-${filter.value}`}
label={filter.label || filter.attribute}
value={filter.value}
count={filter.count}
onClick={() => onFilterClick(filter.attribute, filter.value)}
/>
))}
</div>
</div>
);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export type ChatHeaderClassNames = {
* Class names to apply to the title icon element
*/
titleIcon?: string | string[];
/**
* Class names to apply to the actions container element
*/
actions?: string | string[];
/**
* Class names to apply to the maximize button element
*/
Expand Down Expand Up @@ -160,7 +164,7 @@ export function createChatHeaderComponent({ createElement }: Renderer) {
</span>
{translations.title}
</span>
<div className={cx('ais-ChatHeader-actions')}>
<div className={cx('ais-ChatHeader-actions', classNames.actions)}>
{onClear && (
<Button
variant="ghost"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
ChatToolMessage,
ClientSideTools,
} from './types';
import type { DynamicToolUIPart } from 'ai';

export type ChatMessageSide = 'left' | 'right';
export type ChatMessageVariant = 'neutral' | 'subtle';
Expand Down Expand Up @@ -126,6 +127,10 @@ export type ChatMessageProps = ComponentProps<'article'> & {
* Close the chat
*/
onClose: () => void;
/**
* Send a message to the chat
*/
sendMessage: (params: { text: string }) => void;
/**
* Array of tools available for the assistant (for tool messages)
*/
Expand Down Expand Up @@ -158,6 +163,7 @@ export function createChatMessageComponent({ createElement }: Renderer) {
indexUiState,
setIndexUiState,
onClose,
sendMessage,
translations: userTranslations,
...props
} = userProps;
Expand Down Expand Up @@ -231,6 +237,8 @@ export function createChatMessageComponent({ createElement }: Renderer) {
setIndexUiState={setIndexUiState}
addToolResult={boundAddToolResult}
onClose={onClose}
sendMessage={sendMessage}
toolState={(part as DynamicToolUIPart).state}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ export type ChatMessagesProps<
* Function to close the chat
*/
onClose: () => void;
/**
* Function to send a message
*/
sendMessage: (params: { text: string }) => void;
/**
* Optional class names
*/
Expand Down Expand Up @@ -190,6 +194,7 @@ function createDefaultMessageComponent<
setIndexUiState,
onReload,
onClose,
sendMessage,
actionsComponent,
classNames,
messageTranslations,
Expand All @@ -204,6 +209,7 @@ function createDefaultMessageComponent<
tools: ClientSideTools;
onReload: (messageId?: string) => void;
onClose: () => void;
sendMessage: (params: { text: string }) => void;
actionsComponent?: ChatMessageProps['actionsComponent'];
translations: ChatMessagesTranslations;
classNames?: Partial<ChatMessageClassNames>;
Expand Down Expand Up @@ -240,6 +246,7 @@ function createDefaultMessageComponent<
indexUiState={indexUiState}
setIndexUiState={setIndexUiState}
onClose={onClose}
sendMessage={sendMessage}
actions={defaultActions}
actionsComponent={actionsComponent}
data-role={message.role}
Expand Down Expand Up @@ -284,6 +291,7 @@ export function createChatMessagesComponent({
hideScrollToBottom = false,
onReload,
onClose,
sendMessage,
translations: userTranslations,
userMessageProps,
assistantMessageProps,
Expand Down Expand Up @@ -357,6 +365,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 @@ -25,6 +25,7 @@ describe('Chat', () => {
tools: {},
onReload: jest.fn(),
onClose: jest.fn(),
sendMessage: jest.fn(),
}}
promptProps={{}}
toggleButtonProps={{ open: true, onClick: jest.fn() }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe('ChatMessage', () => {
message={{ role: 'user', id: '1', parts: [] }}
tools={{}}
onClose={jest.fn()}
sendMessage={jest.fn()}
/>
);
expect(container).toMatchInlineSnapshot(`
Expand Down Expand Up @@ -65,6 +66,7 @@ describe('ChatMessage', () => {
}}
tools={{}}
onClose={jest.fn()}
sendMessage={jest.fn()}
/>
);
expect(container).toMatchInlineSnapshot(`
Expand Down Expand Up @@ -102,6 +104,7 @@ describe('ChatMessage', () => {
}}
tools={{}}
onClose={jest.fn()}
sendMessage={jest.fn()}
/>
<ChatMessage
indexUiState={{}}
Expand All @@ -113,6 +116,7 @@ describe('ChatMessage', () => {
}}
tools={{}}
onClose={jest.fn()}
sendMessage={jest.fn()}
/>
<ChatMessage
indexUiState={{}}
Expand All @@ -124,6 +128,7 @@ describe('ChatMessage', () => {
}}
tools={{}}
onClose={jest.fn()}
sendMessage={jest.fn()}
/>
</div>
);
Expand Down Expand Up @@ -229,6 +234,7 @@ describe('ChatMessage', () => {
},
}}
onClose={jest.fn()}
sendMessage={jest.fn()}
/>
);
expect(container).toMatchInlineSnapshot(`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('ChatMessages', () => {
tools={{}}
onReload={jest.fn()}
onClose={jest.fn()}
sendMessage={jest.fn()}
/>
);

Expand Down Expand Up @@ -82,6 +83,7 @@ describe('ChatMessages', () => {
tools={{}}
onReload={jest.fn()}
onClose={jest.fn()}
sendMessage={jest.fn()}
/>
);

Expand Down Expand Up @@ -154,6 +156,7 @@ describe('ChatMessages', () => {
tools={{}}
onReload={jest.fn()}
onClose={jest.fn()}
sendMessage={jest.fn()}
/>
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AbstractChat, ChatInit, UIMessage } from 'ai';
import type { AbstractChat, ChatInit, DynamicToolUIPart, UIMessage } from 'ai';

export type ChatStatus = 'ready' | 'submitted' | 'streaming' | 'error';
export type ChatRole = 'data' | 'user' | 'assistant' | 'system';
Expand All @@ -21,9 +21,11 @@ export type AddToolResultWithOutput = (
export type ClientSideToolComponentProps = {
message: ChatToolMessage;
indexUiState: object;
toolState: DynamicToolUIPart['state'];
setIndexUiState: (state: object) => void;
onClose: () => void;
addToolResult: AddToolResultWithOutput;
sendMessage: (params: { text: string }) => void;
};

export type ClientSideToolComponent = (
Expand Down
Loading