Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3a6dc3e
init
shaejaz Jan 16, 2026
fba3162
fix conjuctive facets
shaejaz Jan 16, 2026
5d57326
update view all logic
shaejaz Jan 20, 2026
16331da
update current refinements
shaejaz Jan 20, 2026
0991aa4
update view all logic
shaejaz Jan 21, 2026
5471d13
add js widget
shaejaz Jan 23, 2026
55647cb
move to connector
shaejaz Jan 23, 2026
1a94ac6
revert current refinements
shaejaz Jan 23, 2026
88835f4
check for non existing attributes
shaejaz Jan 23, 2026
da1d461
add tests
shaejaz Jan 23, 2026
0b5e33a
fix lint
shaejaz Jan 23, 2026
24ff653
Merge branch 'master' into feat/view-all-refinements
shaejaz Jan 23, 2026
ea5af10
fix bundlesize
shaejaz Jan 23, 2026
6d8458c
fix test
shaejaz Jan 23, 2026
2ae9280
use generic type
shaejaz Jan 26, 2026
22f0fe6
revert example change
shaejaz Jan 26, 2026
738423b
address comments
shaejaz Jan 28, 2026
0d4dedd
dont skip applying
shaejaz Jan 29, 2026
bb62911
add user get search url
shaejaz Jan 29, 2026
7e55c82
add searchbox in react test
shaejaz Jan 29, 2026
652fcff
remove boolean return type
shaejaz Jan 29, 2026
4bcb957
move util fn
shaejaz Jan 29, 2026
5f4e7be
update tests
shaejaz Jan 29, 2026
05ef70a
fix lint
shaejaz Jan 29, 2026
4293e3f
Merge branch 'master' into feat/view-all-refinements
shaejaz Feb 2, 2026
b41c148
fix bundlesize
shaejaz Feb 2, 2026
1caa6c9
fix get url state param
shaejaz Feb 2, 2026
12d4d58
fix helper state not being cleared
shaejaz Feb 10, 2026
081392b
always clear filters
shaejaz Feb 10, 2026
8f72ccd
fix bundlesize
shaejaz Feb 10, 2026
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
8 changes: 4 additions & 4 deletions bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@
},
{
"path": "./packages/instantsearch.js/dist/instantsearch.production.min.js",
"maxSize": "117 kB"
"maxSize": "117.25 kB"
},
{
"path": "./packages/instantsearch.js/dist/instantsearch.development.js",
"maxSize": "237.5 kB"
"maxSize": "238 kB"
},
{
"path": "packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js",
"maxSize": "61.25 kB"
"maxSize": "61.50 kB"
},
{
"path": "packages/react-instantsearch/dist/umd/ReactInstantSearch.min.js",
"maxSize": "96.5 kB"
"maxSize": "96.75 kB"
},
{
"path": "packages/vue-instantsearch/vue2/umd/index.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ export function createChatMessageComponent({ createElement }: Renderer) {
indexUiState={indexUiState}
setIndexUiState={setIndexUiState}
addToolResult={boundAddToolResult}
applyFilters={tool.applyFilters}
onClose={onClose}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ describe('ChatMessage', () => {
),
addToolResult: jest.fn(),
onToolCall: jest.fn(),
applyFilters: jest.fn(),
},
}}
onClose={jest.fn()}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { SearchParameters } from 'algoliasearch-helper';

export type ChatStatus = 'ready' | 'submitted' | 'streaming' | 'error';
export type ChatRole = 'data' | 'user' | 'assistant' | 'system';

Expand Down Expand Up @@ -444,12 +446,24 @@ export type AddToolResultWithOutput = (
params: Pick<Parameters<AddToolResult>[0], 'output'>
) => ReturnType<AddToolResult>;

export type SearchToolInput = {
query: string;
number_of_results?: number;
facet_filters?: string[][];
};

export type ApplyFiltersParams = {
query?: string;
facetFilters?: string[][];
};

export type ClientSideToolComponentProps = {
message: ChatToolMessage;
indexUiState: object;
setIndexUiState: (state: object) => void;
onClose: () => void;
addToolResult: AddToolResultWithOutput;
applyFilters: (params: ApplyFiltersParams) => SearchParameters;
};

export type ClientSideToolComponent = (
Expand All @@ -466,8 +480,12 @@ export type ClientSideTool = {
addToolResult: AddToolResultWithOutput;
}
) => void;
applyFilters: (params: ApplyFiltersParams) => SearchParameters;
};
export type ClientSideTools = Record<string, ClientSideTool>;

export type UserClientSideTool = Omit<ClientSideTool, 'addToolResult'>;
export type UserClientSideTool = Omit<
ClientSideTool,
'addToolResult' | 'applyFilters'
>;
export type UserClientSideTools = Record<string, UserClientSideTool>;
31 changes: 30 additions & 1 deletion packages/instantsearch.js/src/__tests__/common-widgets.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -621,11 +621,40 @@ const testSetups: TestSetupsMap<TestSuites, 'javascript'> = {
.start();
},
createChatWidgetTests({ instantSearchOptions, widgetParams }) {
const { renderRefinements, ...chatWidgetParams } = widgetParams;

const refinementsWidgets = [];
if (renderRefinements) {
refinementsWidgets.push(
...[
searchBox({
container: document.body.appendChild(document.createElement('div')),
}),
refinementList({
container: document.body.appendChild(document.createElement('div')),
attribute: 'brand',
}),
refinementList({
container: document.body.appendChild(document.createElement('div')),
attribute: 'category',
}),
hierarchicalMenu({
container: document.body.appendChild(document.createElement('div')),
attributes: [
'hierarchicalCategories.lvl0',
'hierarchicalCategories.lvl1',
],
}),
]
);
}

instantsearch(instantSearchOptions)
.addWidgets([
...refinementsWidgets,
chat({
container: document.body.appendChild(document.createElement('div')),
...widgetParams,
...chatWidgetParams,
}),
])
.on('error', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ describe('connectChat', () => {
testTool: {
...mockTool,
addToolResult: expect.any(Function),
applyFilters: expect.any(Function),
},
});
});
Expand Down
84 changes: 83 additions & 1 deletion packages/instantsearch.js/src/connectors/chat/connectChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ import {
import { Chat } from '../../lib/chat';
import {
checkRendering,
clearRefinements,
createDocumentationMessageGenerator,
createSendEventForHits,
getAlgoliaAgent,
getAppIdAndApiKey,
getRefinements,
noop,
uniq,
warning,
} from '../../lib/utils';
import { flat } from '../../lib/utils/flat';

import type {
AbstractChat,
Expand All @@ -30,6 +34,7 @@ import type {
WidgetRenderState,
IndexRenderState,
} from '../../types';
import type { AlgoliaSearchHelper, SearchResults } from 'algoliasearch-helper';
import type {
AddToolResultWithOutput,
UserClientSideTool,
Expand Down Expand Up @@ -105,6 +110,11 @@ export type ChatTransport = {
transport?: ConstructorParameters<typeof DefaultChatTransport>[0];
};

export type ApplyFiltersParams = {
query?: string;
facetFilters?: string[][];
};

export type ChatInit<TUiMessage extends UIMessage> =
ChatInitWithoutTransport<TUiMessage> & ChatTransport;

Expand Down Expand Up @@ -144,6 +154,73 @@ export type ChatConnector<TUiMessage extends UIMessage = UIMessage> = Connector<
ChatConnectorParams<TUiMessage>
>;

function getAttributesToClear({
results,
helper,
}: {
results: SearchResults;
helper: AlgoliaSearchHelper;
}) {
return uniq(
getRefinements(results, helper.state, true).map(
(refinement) => refinement.attribute
)
);
}

function updateStateFromSearchToolInput(
params: ApplyFiltersParams,
helper: AlgoliaSearchHelper
) {
// clear all filters first
const attributesToClear = getAttributesToClear({
results: helper.lastResults!,
helper,
});

helper.setState(
clearRefinements({
helper,
attributesToClear,
})
);

if (params.facetFilters) {
const attributes = flat(params.facetFilters).map((filter) => {
const [attribute, value] = filter.split(':');

return { attribute, value };
});

attributes.forEach(({ attribute, value }) => {
if (
!helper.state.isConjunctiveFacet(attribute) &&
!helper.state.isHierarchicalFacet(attribute) &&
!helper.state.isDisjunctiveFacet(attribute)
) {
const s = helper.state.addDisjunctiveFacet(attribute);
helper.setState(s);
helper.toggleFacetRefinement(attribute, value);
} else {
const attr =
helper.state.hierarchicalFacets.find(
(facet) => facet.name === attribute
)?.name || attribute;

helper.toggleFacetRefinement(attr, value);
}
});
}

if (params.query) {
helper.setQuery(params.query);
}

helper.search();

return helper.state;
}

export default (function connectChat<TWidgetParams extends UnknownWidgetParams>(
renderFn: Renderer<ChatRenderState, TWidgetParams & ChatConnectorParams>,
unmountFn: Unmounter = noop
Expand Down Expand Up @@ -421,7 +498,7 @@ export default (function connectChat<TWidgetParams extends UnknownWidgetParams>(
},

getWidgetRenderState(renderOptions) {
const { instantSearchInstance, parent } = renderOptions;
const { instantSearchInstance, parent, helper } = renderOptions;
if (!_chatInstance) {
this.init!({ ...renderOptions, uiState: {}, results: undefined });
}
Expand All @@ -434,11 +511,16 @@ export default (function connectChat<TWidgetParams extends UnknownWidgetParams>(
});
}

function applyFilters(params: ApplyFiltersParams) {
return updateStateFromSearchToolInput(params, helper);
}

const toolsWithAddToolResult: ClientSideTools = {};
Object.entries(tools).forEach(([key, tool]) => {
const toolWithAddToolResult: ClientSideTool = {
...tool,
addToolResult: _chatInstance.addToolResult,
applyFilters,
};
toolsWithAddToolResult[key] = toolWithAddToolResult;
});
Expand Down
34 changes: 34 additions & 0 deletions packages/instantsearch.js/src/lib/utils/__tests__/flat-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { flat } from '../flat';

describe('flat', () => {
test('flattens a 2D array into a 1D array', () => {
const input = [
['a', 'b'],
['c', 'd'],
['e', 'f'],
];
const expectedOutput = ['a', 'b', 'c', 'd', 'e', 'f'];

const actualOutput = flat(input);

expect(actualOutput).toEqual(expectedOutput);
});

test('returns an empty array when given an empty array', () => {
const input: never[][] = [];
const expectedOutput: never[] = [];

const actualOutput = flat(input);

expect(actualOutput).toEqual(expectedOutput);
});

test('handles arrays with empty sub-arrays', () => {
const input = [['a', 'b'], [], ['c'], [], ['d', 'e']];
const expectedOutput = ['a', 'b', 'c', 'd', 'e'];

const actualOutput = flat(input);

expect(actualOutput).toEqual(expectedOutput);
});
});
3 changes: 3 additions & 0 deletions packages/instantsearch.js/src/lib/utils/flat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function flat<T>(arr: T[][]): T[] {
return arr.reduce((acc, array) => acc.concat(array), []);
}
Loading