instantsearch.js 4.86.1 → 4.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/cjs/connectors/autocomplete/connectAutocomplete.js +13 -5
  2. package/cjs/connectors/chat/connectChat.js +89 -17
  3. package/cjs/connectors/filter-suggestions/connectFilterSuggestions.js +255 -0
  4. package/cjs/connectors/index.js +7 -0
  5. package/cjs/connectors/index.umd.js +7 -0
  6. package/cjs/lib/chat/chat.js +3 -3
  7. package/cjs/lib/chat/index.js +5 -2
  8. package/cjs/lib/version.js +1 -1
  9. package/cjs/widgets/autocomplete/autocomplete.js +15 -6
  10. package/cjs/widgets/chat/chat.js +32 -3
  11. package/cjs/widgets/filter-suggestions/filter-suggestions.js +131 -0
  12. package/cjs/widgets/index.js +7 -0
  13. package/cjs/widgets/index.umd.js +7 -0
  14. package/dist/instantsearch.development.d.ts +885 -72
  15. package/dist/instantsearch.development.js +731 -184
  16. package/dist/instantsearch.development.js.map +1 -1
  17. package/dist/instantsearch.production.d.ts +885 -72
  18. package/dist/instantsearch.production.min.d.ts +885 -72
  19. package/dist/instantsearch.production.min.js +2 -2
  20. package/dist/instantsearch.production.min.js.map +1 -1
  21. package/es/connectors/autocomplete/connectAutocomplete.d.ts +10 -0
  22. package/es/connectors/autocomplete/connectAutocomplete.js +13 -5
  23. package/es/connectors/chat/connectChat.d.ts +10 -0
  24. package/es/connectors/chat/connectChat.js +89 -17
  25. package/es/connectors/filter-suggestions/connectFilterSuggestions.d.ts +95 -0
  26. package/es/connectors/filter-suggestions/connectFilterSuggestions.js +249 -0
  27. package/es/connectors/index.d.ts +1 -0
  28. package/es/connectors/index.js +2 -1
  29. package/es/connectors/index.umd.d.ts +1 -0
  30. package/es/connectors/index.umd.js +2 -1
  31. package/es/lib/chat/chat.d.ts +1 -1
  32. package/es/lib/chat/chat.js +3 -3
  33. package/es/lib/chat/index.d.ts +3 -0
  34. package/es/lib/chat/index.js +4 -1
  35. package/es/lib/version.d.ts +1 -1
  36. package/es/lib/version.js +1 -1
  37. package/es/widgets/autocomplete/autocomplete.d.ts +2 -1
  38. package/es/widgets/autocomplete/autocomplete.js +15 -6
  39. package/es/widgets/chat/chat.d.ts +8 -0
  40. package/es/widgets/chat/chat.js +33 -4
  41. package/es/widgets/filter-suggestions/filter-suggestions.d.ts +689 -0
  42. package/es/widgets/filter-suggestions/filter-suggestions.js +124 -0
  43. package/es/widgets/index.d.ts +1 -0
  44. package/es/widgets/index.js +2 -1
  45. package/es/widgets/index.umd.d.ts +1 -0
  46. package/es/widgets/index.umd.js +2 -1
  47. package/package.json +6 -6
@@ -1,6 +1,12 @@
1
1
  import type { SendEventForHits } from '../../lib/utils';
2
2
  import type { Hit, Connector, WidgetRenderState } from '../../types';
3
3
  import type { SearchResults } from 'algoliasearch-helper';
4
+ export type TransformItemsIndicesConfig = {
5
+ indexName: string;
6
+ indexId: string;
7
+ hits: Hit[];
8
+ results: SearchResults;
9
+ };
4
10
  export type AutocompleteConnectorParams = {
5
11
  /**
6
12
  * Escapes HTML entities from hits string values.
@@ -8,6 +14,10 @@ export type AutocompleteConnectorParams = {
8
14
  * @default `true`
9
15
  */
10
16
  escapeHTML?: boolean;
17
+ /**
18
+ * Transforms the items of all indices.
19
+ */
20
+ transformItems?: (indices: TransformItemsIndicesConfig[]) => TransformItemsIndicesConfig[];
11
21
  };
12
22
  export type AutocompleteRenderState = {
13
23
  /**
@@ -15,7 +15,11 @@ var connectAutocomplete = function connectAutocomplete(renderFn) {
15
15
  return function (widgetParams) {
16
16
  var _ref = widgetParams || {},
17
17
  _ref$escapeHTML = _ref.escapeHTML,
18
- escapeHTML = _ref$escapeHTML === void 0 ? true : _ref$escapeHTML;
18
+ escapeHTML = _ref$escapeHTML === void 0 ? true : _ref$escapeHTML,
19
+ _ref$transformItems = _ref.transformItems,
20
+ transformItems = _ref$transformItems === void 0 ? function (indices) {
21
+ return indices;
22
+ } : _ref$transformItems;
19
23
  process.env.NODE_ENV === 'development' ? warning(!widgetParams.indices, "\nThe option `indices` has been removed from the Autocomplete connector.\n\nThe indices to target are now inferred from the widgets tree.\n".concat(Array.isArray(widgetParams.indices) ? "\nAn alternative would be:\n\nconst autocomplete = connectAutocomplete(renderer);\n\nsearch.addWidgets([\n ".concat(widgetParams.indices.map(function (_ref2) {
20
24
  var value = _ref2.value;
21
25
  return "index({ indexName: '".concat(value, "' }),");
@@ -57,6 +61,7 @@ var connectAutocomplete = function connectAutocomplete(renderFn) {
57
61
  helper.setQuery(query).search();
58
62
  };
59
63
  }
64
+ var sendEventMap = {};
60
65
  var indices = scopedResults.map(function (scopedResult) {
61
66
  var _scopedResult$results, _scopedResult$results2;
62
67
  // We need to escape the hits because highlighting
@@ -64,7 +69,7 @@ var connectAutocomplete = function connectAutocomplete(renderFn) {
64
69
  if (scopedResult.results) {
65
70
  scopedResult.results.hits = escapeHTML ? escapeHits(scopedResult.results.hits) : scopedResult.results.hits;
66
71
  }
67
- var sendEvent = createSendEventForHits({
72
+ sendEventMap[scopedResult.indexId] = createSendEventForHits({
68
73
  instantSearchInstance: instantSearchInstance,
69
74
  helper: scopedResult.helper,
70
75
  widgetType: _this.$$type
@@ -73,13 +78,16 @@ var connectAutocomplete = function connectAutocomplete(renderFn) {
73
78
  indexId: scopedResult.indexId,
74
79
  indexName: ((_scopedResult$results = scopedResult.results) === null || _scopedResult$results === void 0 ? void 0 : _scopedResult$results.index) || '',
75
80
  hits: ((_scopedResult$results2 = scopedResult.results) === null || _scopedResult$results2 === void 0 ? void 0 : _scopedResult$results2.hits) || [],
76
- results: scopedResult.results || {},
77
- sendEvent: sendEvent
81
+ results: scopedResult.results || {}
78
82
  };
79
83
  });
80
84
  return {
81
85
  currentRefinement: state.query || '',
82
- indices: indices,
86
+ indices: transformItems(indices).map(function (transformedIndex) {
87
+ return _objectSpread(_objectSpread({}, transformedIndex), {}, {
88
+ sendEvent: sendEventMap[transformedIndex.indexId]
89
+ });
90
+ }),
83
91
  refine: connectorState.refine,
84
92
  widgetParams: widgetParams
85
93
  };
@@ -37,6 +37,10 @@ export type ChatRenderState<TUiMessage extends UIMessage = UIMessage> = {
37
37
  * Tools configuration with addToolResult bound, ready to be used by the UI.
38
38
  */
39
39
  tools: ClientSideTools;
40
+ /**
41
+ * Suggestions received from the AI model.
42
+ */
43
+ suggestions?: string[];
40
44
  } & Pick<AbstractChat<TUiMessage>, 'addToolResult' | 'clearError' | 'error' | 'id' | 'messages' | 'regenerate' | 'resumeStream' | 'sendMessage' | 'status' | 'stop'>;
41
45
  export type ChatInitWithoutTransport<TUiMessage extends UIMessage> = Omit<ChatInitAi<TUiMessage>, 'transport'>;
42
46
  export type ChatTransport = {
@@ -55,6 +59,11 @@ export type ChatConnectorParams<TUiMessage extends UIMessage = UIMessage> = ({
55
59
  * Configuration for client-side tools.
56
60
  */
57
61
  tools?: Record<string, Omit<UserClientSideTool, 'layoutComponent'>>;
62
+ /**
63
+ * Identifier of this type of chat widget. This is used for the key in renderState.
64
+ * @default 'chat'
65
+ */
66
+ type?: string;
58
67
  };
59
68
  export type ChatWidgetDescription<TUiMessage extends UIMessage = UIMessage> = {
60
69
  $$type: 'ais.chat';
@@ -343,6 +352,7 @@ declare const _default: <TWidgetParams extends UnknownWidgetParams>(renderFn: Re
343
352
  setInput: (input: string) => void;
344
353
  setOpen: (open: boolean) => void;
345
354
  setMessages: (messagesParam: TUiMessage[] | ((m: TUiMessage[]) => TUiMessage[])) => void;
355
+ suggestions: string[] | undefined;
346
356
  isClearing: boolean;
347
357
  clearMessages: () => void;
348
358
  onClearTransitionEnd: () => void;
@@ -1,5 +1,6 @@
1
1
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
- var _excluded = ["resume", "tools"];
2
+ var _excluded = ["resume", "tools", "type"],
3
+ _excluded2 = ["messages", "trigger"];
3
4
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
4
5
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
5
6
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
@@ -7,10 +8,14 @@ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol"
7
8
  function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
8
9
  function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
9
10
  function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
10
- function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
11
- function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
12
11
  function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
13
12
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
13
+ function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
14
+ function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
15
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
16
+ function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
17
+ function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
18
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
14
19
  function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
15
20
  function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
16
21
  import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls } from 'ai';
@@ -30,6 +35,8 @@ export default (function connectChat(renderFn) {
30
35
  resume = _ref$resume === void 0 ? false : _ref$resume,
31
36
  _ref$tools = _ref.tools,
32
37
  tools = _ref$tools === void 0 ? {} : _ref$tools,
38
+ _ref$type = _ref.type,
39
+ type = _ref$type === void 0 ? 'chat' : _ref$type,
33
40
  options = _objectWithoutProperties(_ref, _excluded);
34
41
  var _chatInstance;
35
42
  var input = '';
@@ -39,6 +46,25 @@ export default (function connectChat(renderFn) {
39
46
  var setInput;
40
47
  var setOpen;
41
48
  var setIsClearing;
49
+ var agentId = 'agentId' in options ? options.agentId : undefined;
50
+
51
+ // Extract suggestions from the last assistant message's data-suggestions part
52
+ var getSuggestionsFromMessages = function getSuggestionsFromMessages(messages) {
53
+ // Find the last assistant message (iterate from end)
54
+ var lastAssistantMessage = _toConsumableArray(messages).reverse().find(function (message) {
55
+ return message.role === 'assistant' && message.parts;
56
+ });
57
+ if (!(lastAssistantMessage !== null && lastAssistantMessage !== void 0 && lastAssistantMessage.parts)) {
58
+ return undefined;
59
+ }
60
+
61
+ // Find the data-suggestions part
62
+ var suggestionsPart = lastAssistantMessage.parts.find(function (part) {
63
+ var _data;
64
+ return 'type' in part && part.type === 'data-suggestions' && 'data' in part && Array.isArray((_data = part.data) === null || _data === void 0 ? void 0 : _data.suggestions);
65
+ });
66
+ return suggestionsPart === null || suggestionsPart === void 0 ? void 0 : suggestionsPart.data.suggestions;
67
+ };
42
68
  var setMessages = function setMessages(messagesParam) {
43
69
  if (typeof messagesParam === 'function') {
44
70
  messagesParam = messagesParam(_chatInstance.messages);
@@ -62,20 +88,67 @@ export default (function connectChat(renderFn) {
62
88
  _getAppIdAndApiKey2 = _slicedToArray(_getAppIdAndApiKey, 2),
63
89
  appId = _getAppIdAndApiKey2[0],
64
90
  apiKey = _getAppIdAndApiKey2[1];
91
+
92
+ // Filter out custom data parts (like data-suggestions) that the backend doesn't accept
93
+ var filterDataParts = function filterDataParts(messages) {
94
+ return messages.map(function (message) {
95
+ var _message$parts;
96
+ return _objectSpread(_objectSpread({}, message), {}, {
97
+ parts: (_message$parts = message.parts) === null || _message$parts === void 0 ? void 0 : _message$parts.filter(function (part) {
98
+ return !('type' in part && part.type.startsWith('data-'));
99
+ })
100
+ });
101
+ });
102
+ };
65
103
  if ('transport' in options && options.transport) {
66
- transport = new DefaultChatTransport(options.transport);
104
+ var originalPrepare = options.transport.prepareSendMessagesRequest;
105
+ transport = new DefaultChatTransport(_objectSpread(_objectSpread({}, options.transport), {}, {
106
+ prepareSendMessagesRequest: function prepareSendMessagesRequest(params) {
107
+ // Call the original prepareSendMessagesRequest if it exists,
108
+ // otherwise construct the default body
109
+ var preparedOrPromise = originalPrepare ? originalPrepare(params) : {
110
+ body: _objectSpread({}, params)
111
+ };
112
+ // Then filter out data-* parts
113
+ var applyFilter = function applyFilter(prepared) {
114
+ return _objectSpread(_objectSpread({}, prepared), {}, {
115
+ body: _objectSpread(_objectSpread({}, prepared.body), {}, {
116
+ messages: filterDataParts(prepared.body.messages)
117
+ })
118
+ });
119
+ };
120
+
121
+ // Handle both sync and async cases
122
+ if (preparedOrPromise && 'then' in preparedOrPromise) {
123
+ return preparedOrPromise.then(applyFilter);
124
+ }
125
+ return applyFilter(preparedOrPromise);
126
+ }
127
+ }));
67
128
  }
68
129
  if ('agentId' in options && options.agentId) {
69
- var agentId = options.agentId;
70
130
  if (!appId || !apiKey) {
71
131
  throw new Error(withUsage('Could not extract Algolia credentials from the search client.'));
72
132
  }
133
+ var baseApi = "https://".concat(appId, ".algolia.net/agent-studio/1/agents/").concat(agentId, "/completions?compatibilityMode=ai-sdk-5");
73
134
  transport = new DefaultChatTransport({
74
- api: "https://".concat(appId, ".algolia.net/agent-studio/1/agents/").concat(agentId, "/completions?compatibilityMode=ai-sdk-5"),
135
+ api: baseApi,
75
136
  headers: {
76
137
  'x-algolia-application-id': appId,
77
138
  'x-algolia-api-Key': apiKey,
78
139
  'x-algolia-agent': getAlgoliaAgent(instantSearchInstance.client)
140
+ },
141
+ prepareSendMessagesRequest: function prepareSendMessagesRequest(_ref2) {
142
+ var messages = _ref2.messages,
143
+ trigger = _ref2.trigger,
144
+ rest = _objectWithoutProperties(_ref2, _excluded2);
145
+ return {
146
+ // Bypass cache when regenerating to ensure fresh responses
147
+ api: trigger === 'regenerate-message' ? "".concat(baseApi, "&cache=false") : baseApi,
148
+ body: _objectSpread(_objectSpread({}, rest), {}, {
149
+ messages: filterDataParts(messages)
150
+ })
151
+ };
79
152
  }
80
153
  });
81
154
  }
@@ -88,8 +161,8 @@ export default (function connectChat(renderFn) {
88
161
  return new Chat(_objectSpread(_objectSpread({}, options), {}, {
89
162
  transport: transport,
90
163
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
91
- onToolCall: function onToolCall(_ref2) {
92
- var toolCall = _ref2.toolCall;
164
+ onToolCall: function onToolCall(_ref3) {
165
+ var toolCall = _ref3.toolCall;
93
166
  var tool = tools[toolCall.toolName];
94
167
  if (!tool) {
95
168
  if (process.env.NODE_ENV === 'development') {
@@ -102,8 +175,8 @@ export default (function connectChat(renderFn) {
102
175
  });
103
176
  }
104
177
  if (tool.onToolCall) {
105
- var addToolResult = function addToolResult(_ref3) {
106
- var output = _ref3.output;
178
+ var addToolResult = function addToolResult(_ref4) {
179
+ var output = _ref4.output;
107
180
  return _chatInstance.addToolResult({
108
181
  output: output,
109
182
  tool: toolCall.toolName,
@@ -159,9 +232,7 @@ export default (function connectChat(renderFn) {
159
232
  getRenderState: function getRenderState(renderState, renderOptions
160
233
  // Type is explicitly redefined, to avoid having the TWidgetParams type in the definition
161
234
  ) {
162
- return _objectSpread(_objectSpread({}, renderState), {}, {
163
- chat: this.getWidgetRenderState(renderOptions)
164
- });
235
+ return _objectSpread(_objectSpread({}, renderState), {}, _defineProperty({}, type, this.getWidgetRenderState(renderOptions)));
165
236
  },
166
237
  getWidgetRenderState: function getWidgetRenderState(renderOptions) {
167
238
  var instantSearchInstance = renderOptions.instantSearchInstance,
@@ -180,10 +251,10 @@ export default (function connectChat(renderFn) {
180
251
  });
181
252
  }
182
253
  var toolsWithAddToolResult = {};
183
- Object.entries(tools).forEach(function (_ref4) {
184
- var _ref5 = _slicedToArray(_ref4, 2),
185
- key = _ref5[0],
186
- tool = _ref5[1];
254
+ Object.entries(tools).forEach(function (_ref5) {
255
+ var _ref6 = _slicedToArray(_ref5, 2),
256
+ key = _ref6[0],
257
+ tool = _ref6[1];
187
258
  var toolWithAddToolResult = _objectSpread(_objectSpread({}, tool), {}, {
188
259
  addToolResult: _chatInstance.addToolResult
189
260
  });
@@ -198,6 +269,7 @@ export default (function connectChat(renderFn) {
198
269
  setInput: setInput,
199
270
  setOpen: setOpen,
200
271
  setMessages: setMessages,
272
+ suggestions: getSuggestionsFromMessages(_chatInstance.messages),
201
273
  isClearing: isClearing,
202
274
  clearMessages: clearMessages,
203
275
  onClearTransitionEnd: onClearTransitionEnd,
@@ -0,0 +1,95 @@
1
+ import type { Connector, TransformItems, WidgetRenderState } from '../../types';
2
+ export type Suggestion = {
3
+ /**
4
+ * The facet attribute name.
5
+ */
6
+ attribute: string;
7
+ /**
8
+ * The facet value to filter by.
9
+ */
10
+ value: string;
11
+ /**
12
+ * Human-readable display label.
13
+ */
14
+ label: string;
15
+ /**
16
+ * Number of records matching this filter.
17
+ */
18
+ count: number;
19
+ };
20
+ export type FilterSuggestionsTransport = {
21
+ /**
22
+ * The custom API endpoint URL.
23
+ */
24
+ api: string;
25
+ /**
26
+ * Custom headers to send with the request.
27
+ */
28
+ headers?: Record<string, string>;
29
+ /**
30
+ * Function to prepare the request body before sending.
31
+ * Receives the default body and returns the modified request options.
32
+ */
33
+ prepareSendMessagesRequest?: (body: Record<string, unknown>) => {
34
+ body: Record<string, unknown>;
35
+ };
36
+ };
37
+ export type FilterSuggestionsRenderState = {
38
+ /**
39
+ * The list of suggested filters.
40
+ */
41
+ suggestions: Suggestion[];
42
+ /**
43
+ * Whether suggestions are currently being fetched.
44
+ */
45
+ isLoading: boolean;
46
+ /**
47
+ * Applies a filter for the given attribute and value.
48
+ */
49
+ refine: (attribute: string, value: string) => void;
50
+ };
51
+ export type FilterSuggestionsConnectorParams = {
52
+ /**
53
+ * The ID of the agent configured in the Algolia dashboard.
54
+ * Required unless a custom `transport` is provided.
55
+ */
56
+ agentId?: string;
57
+ /**
58
+ * Limit to specific facet attributes.
59
+ */
60
+ attributes?: string[];
61
+ /**
62
+ * Maximum number of suggestions to return.
63
+ * @default 3
64
+ */
65
+ maxSuggestions?: number;
66
+ /**
67
+ * Debounce delay in milliseconds before fetching suggestions.
68
+ * @default 300
69
+ */
70
+ debounceMs?: number;
71
+ /**
72
+ * Number of hits to send for context.
73
+ * @default 5
74
+ */
75
+ hitsToSample?: number;
76
+ /**
77
+ * Function to transform the items passed to the templates.
78
+ */
79
+ transformItems?: TransformItems<Suggestion>;
80
+ /**
81
+ * Custom transport configuration for the API requests.
82
+ * When provided, allows using a custom endpoint, headers, and request body.
83
+ */
84
+ transport?: FilterSuggestionsTransport;
85
+ };
86
+ export type FilterSuggestionsWidgetDescription = {
87
+ $$type: 'ais.filterSuggestions';
88
+ renderState: FilterSuggestionsRenderState;
89
+ indexRenderState: {
90
+ filterSuggestions: WidgetRenderState<FilterSuggestionsRenderState, FilterSuggestionsConnectorParams>;
91
+ };
92
+ };
93
+ export type FilterSuggestionsConnector = Connector<FilterSuggestionsWidgetDescription, FilterSuggestionsConnectorParams>;
94
+ declare const connectFilterSuggestions: FilterSuggestionsConnector;
95
+ export default connectFilterSuggestions;
@@ -0,0 +1,249 @@
1
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
3
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
4
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
5
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
6
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
7
+ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
8
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
9
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
10
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
11
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
12
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
13
+ import { checkRendering, createDocumentationMessageGenerator, getAlgoliaAgent, getAppIdAndApiKey, getRefinements, noop } from "../../lib/utils/index.js";
14
+ var withUsage = createDocumentationMessageGenerator({
15
+ name: 'filter-suggestions',
16
+ connector: true
17
+ });
18
+ var connectFilterSuggestions = function connectFilterSuggestions(renderFn) {
19
+ var unmountFn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
20
+ checkRendering(renderFn, withUsage());
21
+ return function (widgetParams) {
22
+ var agentId = widgetParams.agentId,
23
+ attributes = widgetParams.attributes,
24
+ _widgetParams$maxSugg = widgetParams.maxSuggestions,
25
+ maxSuggestions = _widgetParams$maxSugg === void 0 ? 3 : _widgetParams$maxSugg,
26
+ _widgetParams$debounc = widgetParams.debounceMs,
27
+ debounceMs = _widgetParams$debounc === void 0 ? 300 : _widgetParams$debounc,
28
+ _widgetParams$hitsToS = widgetParams.hitsToSample,
29
+ hitsToSample = _widgetParams$hitsToS === void 0 ? 5 : _widgetParams$hitsToS,
30
+ _widgetParams$transfo = widgetParams.transformItems,
31
+ transformItems = _widgetParams$transfo === void 0 ? function (items) {
32
+ return items;
33
+ } : _widgetParams$transfo,
34
+ transport = widgetParams.transport;
35
+ if (!agentId && !transport) {
36
+ throw new Error(withUsage('The `agentId` option is required unless a custom `transport` is provided.'));
37
+ }
38
+ var endpoint;
39
+ var headers;
40
+ var suggestions = [];
41
+ var isLoading = false;
42
+ var debounceTimer;
43
+ var lastStateSignature = null; // null means never fetched
44
+ var refine;
45
+ var searchHelper = null;
46
+ var latestRenderOptions = null;
47
+
48
+ // Create a signature of the current search state (query + refinements)
49
+ var getStateSignature = function getStateSignature(results) {
50
+ var query = results.query || '';
51
+ var refinements = searchHelper ? JSON.stringify(searchHelper.state.facetsRefinements) + JSON.stringify(searchHelper.state.disjunctiveFacetsRefinements) + JSON.stringify(searchHelper.state.hierarchicalFacetsRefinements) : '';
52
+ return "".concat(query, "|").concat(refinements);
53
+ };
54
+ var _getWidgetRenderState = function getWidgetRenderState(renderOptions) {
55
+ var results = 'results' in renderOptions ? renderOptions.results : undefined;
56
+ var transformedSuggestions = transformItems(suggestions, {
57
+ results: results
58
+ });
59
+ return {
60
+ suggestions: transformedSuggestions,
61
+ isLoading: isLoading,
62
+ refine: refine,
63
+ widgetParams: widgetParams
64
+ };
65
+ };
66
+
67
+ // Minimum duration to show skeleton to avoid flash when results are cached
68
+ var MIN_SKELETON_DURATION_MS = 300;
69
+ var fetchSuggestions = function fetchSuggestions(results, renderOptions) {
70
+ var _results$hits, _rawResults$;
71
+ if (!(results !== null && results !== void 0 && (_results$hits = results.hits) !== null && _results$hits !== void 0 && _results$hits.length)) {
72
+ suggestions = [];
73
+ isLoading = false;
74
+ renderFn(_objectSpread(_objectSpread({}, _getWidgetRenderState(renderOptions)), {}, {
75
+ instantSearchInstance: renderOptions.instantSearchInstance
76
+ }), false);
77
+ return;
78
+ }
79
+ var loadingStartTime = Date.now();
80
+ isLoading = true;
81
+ renderFn(_objectSpread(_objectSpread({}, _getWidgetRenderState(renderOptions)), {}, {
82
+ instantSearchInstance: renderOptions.instantSearchInstance
83
+ }), false);
84
+
85
+ // Get facets from raw results (results.facets is processed differently)
86
+ var rawResults = results._rawResults;
87
+ var rawFacets = (rawResults === null || rawResults === void 0 ? void 0 : (_rawResults$ = rawResults[0]) === null || _rawResults$ === void 0 ? void 0 : _rawResults$.facets) || {};
88
+ var facetsToSend = attributes ? Object.fromEntries(Object.entries(rawFacets).filter(function (_ref) {
89
+ var _ref2 = _slicedToArray(_ref, 1),
90
+ key = _ref2[0];
91
+ return attributes.includes(key);
92
+ })) : rawFacets;
93
+
94
+ // Collect current refinements to exclude from suggestions
95
+ var currentRefinements = searchHelper ? getRefinements(results, searchHelper.state).map(function (refinement) {
96
+ return {
97
+ attribute: refinement.attribute,
98
+ value: refinement.name
99
+ };
100
+ }) : [];
101
+ var messageText = JSON.stringify({
102
+ query: results.query,
103
+ facets: facetsToSend,
104
+ hitsSample: results.hits.slice(0, hitsToSample),
105
+ currentRefinements: currentRefinements,
106
+ maxSuggestions: maxSuggestions
107
+ });
108
+ var payload = {
109
+ messages: [{
110
+ id: "sr-".concat(Date.now()),
111
+ createdAt: new Date().toISOString(),
112
+ role: 'user',
113
+ parts: [{
114
+ type: 'text',
115
+ text: messageText
116
+ }]
117
+ }]
118
+ };
119
+
120
+ // Apply custom body transformation if provided
121
+ var finalPayload = transport !== null && transport !== void 0 && transport.prepareSendMessagesRequest ? transport.prepareSendMessagesRequest(payload).body : payload;
122
+ fetch(endpoint, {
123
+ method: 'POST',
124
+ headers: _objectSpread(_objectSpread({}, headers), {}, {
125
+ 'Content-Type': 'application/json'
126
+ }),
127
+ body: JSON.stringify(finalPayload)
128
+ }).then(function (response) {
129
+ if (!response.ok) {
130
+ throw new Error("HTTP error ".concat(response.status));
131
+ }
132
+ return response.json();
133
+ }).then(function (data) {
134
+ var parsedSuggestions = JSON.parse(data.parts[1].text);
135
+ var validSuggestions = (Array.isArray(parsedSuggestions) ? parsedSuggestions : []).filter(function (suggestion) {
136
+ if (!(suggestion !== null && suggestion !== void 0 && suggestion.attribute) || !(suggestion !== null && suggestion !== void 0 && suggestion.value) || !(suggestion !== null && suggestion !== void 0 && suggestion.label)) {
137
+ return false;
138
+ }
139
+ // If attributes filter is specified, only allow suggestions for those attributes
140
+ if (attributes && !attributes.includes(suggestion.attribute)) {
141
+ return false;
142
+ }
143
+ return true;
144
+ }).slice(0, maxSuggestions);
145
+ suggestions = validSuggestions;
146
+ }).catch(function () {
147
+ suggestions = [];
148
+ }).finally(function () {
149
+ var elapsed = Date.now() - loadingStartTime;
150
+ var remainingDelay = Math.max(0, MIN_SKELETON_DURATION_MS - elapsed);
151
+ var finishLoading = function finishLoading() {
152
+ isLoading = false;
153
+ renderFn(_objectSpread(_objectSpread({}, _getWidgetRenderState(renderOptions)), {}, {
154
+ instantSearchInstance: renderOptions.instantSearchInstance
155
+ }), false);
156
+ };
157
+ if (remainingDelay > 0) {
158
+ setTimeout(finishLoading, remainingDelay);
159
+ } else {
160
+ finishLoading();
161
+ }
162
+ });
163
+ };
164
+ return {
165
+ $$type: 'ais.filterSuggestions',
166
+ init: function init(initOptions) {
167
+ var instantSearchInstance = initOptions.instantSearchInstance,
168
+ helper = initOptions.helper;
169
+ searchHelper = helper;
170
+ if (transport) {
171
+ // Use custom transport configuration
172
+ endpoint = transport.api;
173
+ headers = transport.headers || {};
174
+ } else {
175
+ // Use default Algolia agent endpoint
176
+ var _getAppIdAndApiKey = getAppIdAndApiKey(instantSearchInstance.client),
177
+ _getAppIdAndApiKey2 = _slicedToArray(_getAppIdAndApiKey, 2),
178
+ appId = _getAppIdAndApiKey2[0],
179
+ apiKey = _getAppIdAndApiKey2[1];
180
+ if (!appId || !apiKey) {
181
+ throw new Error(withUsage('Could not extract Algolia credentials from the search client.'));
182
+ }
183
+ endpoint = "https://".concat(appId, ".algolia.net/agent-studio/1/agents/").concat(agentId, "/completions?compatibilityMode=ai-sdk-5&stream=false");
184
+ headers = {
185
+ 'x-algolia-application-id': appId,
186
+ 'x-algolia-api-key': apiKey,
187
+ 'x-algolia-agent': getAlgoliaAgent(instantSearchInstance.client)
188
+ };
189
+ }
190
+ refine = function refine(attribute, value) {
191
+ var _helper$state$hierarc;
192
+ // Check if the attribute belongs to a hierarchical facet
193
+ // by finding a hierarchical facet that includes this attribute
194
+ var attr = ((_helper$state$hierarc = helper.state.hierarchicalFacets.find(function (facet) {
195
+ return facet.attributes.includes(attribute);
196
+ })) === null || _helper$state$hierarc === void 0 ? void 0 : _helper$state$hierarc.name) || attribute;
197
+ helper.toggleFacetRefinement(attr, value);
198
+ helper.search();
199
+ };
200
+ renderFn(_objectSpread(_objectSpread({}, _getWidgetRenderState(initOptions)), {}, {
201
+ instantSearchInstance: instantSearchInstance
202
+ }), true);
203
+ },
204
+ render: function render(renderOptions) {
205
+ var results = renderOptions.results,
206
+ instantSearchInstance = renderOptions.instantSearchInstance;
207
+
208
+ // Always store the latest render options
209
+ latestRenderOptions = renderOptions;
210
+ if (!results) {
211
+ renderFn(_objectSpread(_objectSpread({}, _getWidgetRenderState(renderOptions)), {}, {
212
+ instantSearchInstance: instantSearchInstance
213
+ }), false);
214
+ return;
215
+ }
216
+
217
+ // Debounce: only fetch if search state changed (query or refinements) and after delay
218
+ var stateSignature = getStateSignature(results);
219
+ if (stateSignature !== lastStateSignature) {
220
+ lastStateSignature = stateSignature;
221
+ clearTimeout(debounceTimer);
222
+ debounceTimer = setTimeout(function () {
223
+ var _latestRenderOptions;
224
+ // Use the latest render options when the timeout fires
225
+ if ((_latestRenderOptions = latestRenderOptions) !== null && _latestRenderOptions !== void 0 && _latestRenderOptions.results) {
226
+ fetchSuggestions(latestRenderOptions.results, latestRenderOptions);
227
+ }
228
+ }, debounceMs);
229
+ }
230
+ renderFn(_objectSpread(_objectSpread({}, _getWidgetRenderState(renderOptions)), {}, {
231
+ instantSearchInstance: instantSearchInstance
232
+ }), false);
233
+ },
234
+ dispose: function dispose() {
235
+ clearTimeout(debounceTimer);
236
+ unmountFn();
237
+ },
238
+ getRenderState: function getRenderState(renderState, renderOptions) {
239
+ return _objectSpread(_objectSpread({}, renderState), {}, {
240
+ filterSuggestions: this.getWidgetRenderState(renderOptions)
241
+ });
242
+ },
243
+ getWidgetRenderState: function getWidgetRenderState(renderOptions) {
244
+ return _getWidgetRenderState(renderOptions);
245
+ }
246
+ };
247
+ };
248
+ };
249
+ export default connectFilterSuggestions;
@@ -37,3 +37,4 @@ export { default as connectRelevantSort } from './relevant-sort/connectRelevantS
37
37
  export { default as connectFrequentlyBoughtTogether } from './frequently-bought-together/connectFrequentlyBoughtTogether';
38
38
  export { default as connectLookingSimilar } from './looking-similar/connectLookingSimilar';
39
39
  export { default as connectChat } from './chat/connectChat';
40
+ export { default as connectFilterSuggestions } from './filter-suggestions/connectFilterSuggestions';
@@ -42,4 +42,5 @@ export { default as connectVoiceSearch } from "./voice-search/connectVoiceSearch
42
42
  export { default as connectRelevantSort } from "./relevant-sort/connectRelevantSort.js";
43
43
  export { default as connectFrequentlyBoughtTogether } from "./frequently-bought-together/connectFrequentlyBoughtTogether.js";
44
44
  export { default as connectLookingSimilar } from "./looking-similar/connectLookingSimilar.js";
45
- export { default as connectChat } from "./chat/connectChat.js";
45
+ export { default as connectChat } from "./chat/connectChat.js";
46
+ export { default as connectFilterSuggestions } from "./filter-suggestions/connectFilterSuggestions.js";
@@ -37,3 +37,4 @@ export { default as connectRelevantSort } from './relevant-sort/connectRelevantS
37
37
  export { default as connectFrequentlyBoughtTogether } from './frequently-bought-together/connectFrequentlyBoughtTogether';
38
38
  export { default as connectLookingSimilar } from './looking-similar/connectLookingSimilar';
39
39
  export declare const connectChat: () => never;
40
+ export { default as connectFilterSuggestions } from './filter-suggestions/connectFilterSuggestions';