@constructor-io/constructorio-ui-pia 2.10.3 → 2.11.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 (70) hide show
  1. package/README.md +7 -0
  2. package/dist/constructorio-ui-pia-bundled.js +50 -50
  3. package/lib/cjs/components/CioPia/CioPia.js +8 -96
  4. package/lib/cjs/components/CioPia/CioPiaQna.js +110 -0
  5. package/lib/cjs/components/CioPia/CioPiaStyles.css +19 -0
  6. package/lib/cjs/components/CioPia/types.js +2 -0
  7. package/lib/cjs/components/CioPiaRecs/CioPiaRecs.js +73 -0
  8. package/lib/cjs/components/CioPiaRecs/CioPiaRecsStyles.css +138 -0
  9. package/lib/cjs/components/CioPiaRecs/RecsCarouselSkeleton.js +58 -0
  10. package/lib/cjs/components/CioPiaRecs/RecsOptionsSkeleton.js +18 -0
  11. package/lib/cjs/components/CioPiaRecs/RecsPodRefinement.js +27 -0
  12. package/lib/cjs/components/Input/Input.js +27 -9
  13. package/lib/cjs/components/Input/InputStyles.css +27 -0
  14. package/lib/cjs/components/SuggestedQuestion/SuggestedQuestion.js +2 -3
  15. package/lib/cjs/components/SuggestedQuestionsContainer/SuggestedQuestionsContainerStyles.css +1 -1
  16. package/lib/cjs/constants.js +12 -1
  17. package/lib/cjs/hooks/mocks/agent.js +17 -0
  18. package/lib/cjs/hooks/useCioPia.js +6 -14
  19. package/lib/cjs/hooks/usePiaClient.js +26 -0
  20. package/lib/cjs/hooks/useRecsPod.js +129 -0
  21. package/lib/cjs/hooks/useViewportTracking.js +3 -2
  22. package/lib/cjs/styles.css +1 -1
  23. package/lib/cjs/utils/classNames.js +13 -0
  24. package/lib/cjs/utils/recs.js +13 -0
  25. package/lib/cjs/utils/translate.js +5 -0
  26. package/lib/cjs/version.js +1 -1
  27. package/lib/mjs/components/CioPia/CioPia.js +7 -95
  28. package/lib/mjs/components/CioPia/CioPiaQna.js +105 -0
  29. package/lib/mjs/components/CioPia/CioPiaStyles.css +19 -0
  30. package/lib/mjs/components/CioPia/types.js +1 -0
  31. package/lib/mjs/components/CioPiaRecs/CioPiaRecs.js +69 -0
  32. package/lib/mjs/components/CioPiaRecs/CioPiaRecsStyles.css +138 -0
  33. package/lib/mjs/components/CioPiaRecs/RecsCarouselSkeleton.js +54 -0
  34. package/lib/mjs/components/CioPiaRecs/RecsOptionsSkeleton.js +14 -0
  35. package/lib/mjs/components/CioPiaRecs/RecsPodRefinement.js +23 -0
  36. package/lib/mjs/components/Input/Input.js +27 -9
  37. package/lib/mjs/components/Input/InputStyles.css +27 -0
  38. package/lib/mjs/components/SuggestedQuestion/SuggestedQuestion.js +2 -3
  39. package/lib/mjs/components/SuggestedQuestionsContainer/SuggestedQuestionsContainerStyles.css +1 -1
  40. package/lib/mjs/constants.js +11 -0
  41. package/lib/mjs/hooks/mocks/agent.js +15 -0
  42. package/lib/mjs/hooks/useCioPia.js +6 -14
  43. package/lib/mjs/hooks/usePiaClient.js +22 -0
  44. package/lib/mjs/hooks/useRecsPod.js +125 -0
  45. package/lib/mjs/hooks/useViewportTracking.js +3 -2
  46. package/lib/mjs/styles.css +1 -1
  47. package/lib/mjs/utils/classNames.js +9 -0
  48. package/lib/mjs/utils/recs.js +9 -0
  49. package/lib/mjs/utils/translate.js +6 -1
  50. package/lib/mjs/version.js +1 -1
  51. package/lib/types/components/CioPia/CioPia.d.ts +1 -36
  52. package/lib/types/components/CioPia/CioPiaQna.d.ts +4 -0
  53. package/lib/types/components/CioPia/index.d.ts +1 -0
  54. package/lib/types/components/CioPia/types.d.ts +40 -0
  55. package/lib/types/components/CioPiaRecs/CioPiaRecs.d.ts +13 -0
  56. package/lib/types/components/CioPiaRecs/RecsCarouselSkeleton.d.ts +16 -0
  57. package/lib/types/components/CioPiaRecs/RecsOptionsSkeleton.d.ts +16 -0
  58. package/lib/types/components/CioPiaRecs/RecsPodRefinement.d.ts +21 -0
  59. package/lib/types/components/Input/Input.d.ts +9 -1
  60. package/lib/types/components/SuggestedQuestion/SuggestedQuestion.d.ts +4 -2
  61. package/lib/types/constants.d.ts +11 -0
  62. package/lib/types/hooks/mocks/agent.d.ts +13 -0
  63. package/lib/types/hooks/usePiaClient.d.ts +17 -0
  64. package/lib/types/hooks/useRecsPod.d.ts +52 -0
  65. package/lib/types/hooks/useViewportTracking.d.ts +2 -1
  66. package/lib/types/types.d.ts +76 -1
  67. package/lib/types/utils/classNames.d.ts +9 -0
  68. package/lib/types/utils/recs.d.ts +6 -0
  69. package/lib/types/version.d.ts +1 -1
  70. package/package.json +1 -1
@@ -1,5 +1,4 @@
1
1
  @import '@constructor-io/constructorio-ui-components/styles.css';
2
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;700&display=swap');
3
2
  @import './components/SuggestedQuestion/SuggestedQuestionStyles.css';
4
3
  @import './components/SuggestedQuestionsContainer/SuggestedQuestionsContainerStyles.css';
5
4
  @import './components/Input/InputStyles.css';
@@ -10,6 +9,7 @@
10
9
  @import './components/CioPia/CioPiaStyles.css';
11
10
  @import './components/ConversationHistory/ConversationHistoryStyles.css';
12
11
  @import './components/PiaConversation/PiaConversationStyles.css';
12
+ @import './components/CioPiaRecs/CioPiaRecsStyles.css';
13
13
 
14
14
  .cio-pia-container {
15
15
  font-family: 'Inter', sans-serif;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Joins class names, dropping the falsy ones, so a conditional class can be written inline.
3
+ *
4
+ * Deliberately not `clsx` or `classnames`: neither is a dependency of this package, and the copy of
5
+ * `clsx` in the tree belongs to `@constructor-io/constructorio-ui-components`, which is a peer
6
+ * dependency - importing it here would work locally and break in a consumer that hoists
7
+ * differently. Worth revisiting if this ever needs to accept objects or nested arrays.
8
+ */
9
+ export const cx = (...parts) => parts.filter(Boolean).join(' ');
@@ -0,0 +1,9 @@
1
+ import { translate } from './translate';
2
+ import { RECS_REFINEMENT_LABEL } from '../constants';
3
+ /**
4
+ * The line that introduces the refinement options. Prefers the prompt the API sent, so our
5
+ * copy and theirs cannot drift.
6
+ */
7
+ export function resolveRefinementQuestion(refinement, translations) {
8
+ return refinement?.question || translate(RECS_REFINEMENT_LABEL, translations);
9
+ }
@@ -1,4 +1,4 @@
1
- import { DISCLAIMER_TEXT } from '../constants';
1
+ import { DISCLAIMER_TEXT, RECS_FALLBACK_TITLE, RECS_INPUT_PLACEHOLDER, RECS_LOADING_TITLE, RECS_REFINEMENT_LABEL, RECS_UNSUPPORTED_REQUEST, } from '../constants';
2
2
  const defaultTranslations = {
3
3
  'Any questions about this product?': 'Any questions about this product?',
4
4
  'Ask anything': 'Ask anything',
@@ -8,6 +8,11 @@ const defaultTranslations = {
8
8
  'Learn More.': 'Learn More.',
9
9
  'Ask about this product': 'Ask about this product',
10
10
  'Add to Cart': 'Add to Cart',
11
+ [RECS_LOADING_TITLE]: RECS_LOADING_TITLE,
12
+ [RECS_FALLBACK_TITLE]: RECS_FALLBACK_TITLE,
13
+ [RECS_UNSUPPORTED_REQUEST]: RECS_UNSUPPORTED_REQUEST,
14
+ [RECS_REFINEMENT_LABEL]: RECS_REFINEMENT_LABEL,
15
+ [RECS_INPUT_PLACEHOLDER]: RECS_INPUT_PLACEHOLDER,
11
16
  };
12
17
  /**
13
18
  * Translates a word using the provided translations object.
@@ -1 +1 @@
1
- export default '2.10.3';
1
+ export default '2.11.0';
@@ -1,38 +1,3 @@
1
1
  import React from 'react';
2
- import { IncludeComponentOverrides, IncludeRenderProps } from '@constructor-io/constructorio-ui-components';
3
- import MockConstructorIOClient from '../../hooks/mocks/MockConstructorIOClient';
4
- import { CioPiaRenderProps, CioPiaComponentOverrides, Callbacks, CioPiaDisplayConfigs, Translations, SuggestedQuestionsParameters, Formatters, ProductCardDisplayProps } from '../../types';
5
- export interface CioPiaProps extends IncludeRenderProps<CioPiaRenderProps>, IncludeComponentOverrides<CioPiaComponentOverrides> {
6
- /** Your Constructor.io API key. */
7
- apiKey: string;
8
- /** The product item ID to fetch insights for. */
9
- itemId: string;
10
- /** The product display name, sent with tracking events. */
11
- itemName: string;
12
- /** Thread ID for conversation context. Must be a valid UUID (e.g., "550e8400-e29b-41d4-a716-446655440000"). */
13
- threadId?: string;
14
- /** Optional variation ID for the product. */
15
- variationId?: string;
16
- /** Optional Constructor.io client instance. If not provided, one will be created internally. */
17
- cioClient?: MockConstructorIOClient;
18
- /** Display configuration options (mode, type, showFeedback, etc.). */
19
- displayConfigs?: CioPiaDisplayConfigs;
20
- /** Callback handlers for user interactions (onQuestionSubmit, onProductCardClick, onFeedback). */
21
- callbacks?: Callbacks;
22
- /** Custom component overrides via reactNode or render props functions. */
23
- componentOverrides?: CioPiaComponentOverrides;
24
- /** Formatter functions for transforming data before display. */
25
- formatters?: Formatters;
26
- /** Props forwarded to the ProductCard rendered inside the carousel. */
27
- productCardProps?: ProductCardDisplayProps;
28
- /** UI string translations for internationalization. */
29
- translations?: Translations;
30
- /** Parameters for the suggested questions request. */
31
- suggestedQuestionsParameters?: SuggestedQuestionsParameters;
32
- /**
33
- * Extra query parameters appended to PIA API requests (e.g. `ef-*` test cell params).
34
- * Define outside the component or wrap with useMemo to avoid unnecessary re-renders.
35
- */
36
- parameters?: Record<string, string | number | boolean>;
37
- }
2
+ import type { CioPiaProps } from './types';
38
3
  export default function CioPia(props: CioPiaProps): React.JSX.Element;
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ import type { CioPiaProps } from './types';
3
+ /** The question-and-answer experience: `mode: 'default'`, `mode: 'conversation'`, and the modal. */
4
+ export default function CioPiaQna(props: CioPiaProps): React.JSX.Element;
@@ -1,3 +1,4 @@
1
1
  import CioPia from './CioPia';
2
2
  export * from './CioPia';
3
+ export type { CioPiaProps } from './types';
3
4
  export default CioPia;
@@ -0,0 +1,40 @@
1
+ import { IncludeComponentOverrides, IncludeRenderProps } from '@constructor-io/constructorio-ui-components';
2
+ import MockConstructorIOClient from '../../hooks/mocks/MockConstructorIOClient';
3
+ import { CioPiaRenderProps, CioPiaComponentOverrides, Callbacks, CioPiaDisplayConfigs, CioPiaTrackingConfigs, Translations, SuggestedQuestionsParameters, RecsPodParameters, Formatters, ProductCardDisplayProps } from '../../types';
4
+ export interface CioPiaProps extends IncludeRenderProps<CioPiaRenderProps>, IncludeComponentOverrides<CioPiaComponentOverrides> {
5
+ /** Your Constructor.io API key. */
6
+ apiKey: string;
7
+ /** The product item ID to fetch insights for. */
8
+ itemId: string;
9
+ /** The product display name, sent with tracking events. */
10
+ itemName: string;
11
+ /** Thread ID for conversation context. Must be a valid UUID (e.g., "550e8400-e29b-41d4-a716-446655440000"). */
12
+ threadId?: string;
13
+ /** Optional variation ID for the product. */
14
+ variationId?: string;
15
+ /** Optional Constructor.io client instance. If not provided, one will be created internally. */
16
+ cioClient?: MockConstructorIOClient;
17
+ /** Display configuration options (mode, type, showFeedback, etc.). */
18
+ displayConfigs?: CioPiaDisplayConfigs;
19
+ /** Tracking configuration options (viewThreshold, etc.). */
20
+ trackingConfigs?: CioPiaTrackingConfigs;
21
+ /** Callback handlers for user interactions (onQuestionSubmit, onProductCardClick, onFeedback). */
22
+ callbacks?: Callbacks;
23
+ /** Custom component overrides via reactNode or render props functions. */
24
+ componentOverrides?: CioPiaComponentOverrides;
25
+ /** Formatter functions for transforming data before display. */
26
+ formatters?: Formatters;
27
+ /** Props forwarded to the ProductCard rendered inside the carousel. */
28
+ productCardProps?: ProductCardDisplayProps;
29
+ /** UI string translations for internationalization. */
30
+ translations?: Translations;
31
+ /** Parameters for the suggested questions request. */
32
+ suggestedQuestionsParameters?: SuggestedQuestionsParameters;
33
+ /** Parameters for the recommendations request, used by `mode: 'recommendations'`. */
34
+ recsPodParameters?: RecsPodParameters;
35
+ /**
36
+ * Extra query parameters appended to PIA API requests (e.g. `ef-*` test cell params).
37
+ * Define outside the component or wrap with useMemo to avoid unnecessary re-renders.
38
+ */
39
+ parameters?: Record<string, string | number | boolean>;
40
+ }
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import type { CioPiaProps } from '../CioPia/types';
3
+ /**
4
+ * A pod of AI-curated product suggestions: a personalized title, a row of products, and short
5
+ * options for narrowing them down. It asks no questions and makes no Q&A requests, so a retailer
6
+ * can use it on its own.
7
+ *
8
+ * Renders nothing at all once a request has settled with no products, so whatever the retailer
9
+ * already had in that slot shows through instead of an empty shell. That is the default: a caller
10
+ * that supplied its own root — `children` or `componentOverrides.reactNode` — still gets it in that
11
+ * state, with `error` available, and decides what belongs there.
12
+ */
13
+ export default function CioPiaRecs(props: CioPiaProps): React.JSX.Element | null;
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { ComponentOverrideProps } from '@constructor-io/constructorio-ui-components';
3
+ import { LoadingRenderProps } from '../../types';
4
+ interface RecsCarouselSkeletonProps {
5
+ /** How many cards to draw. Defaults to the widest band the carousel lays out. */
6
+ count?: number;
7
+ componentOverride?: ComponentOverrideProps<LoadingRenderProps>;
8
+ }
9
+ /**
10
+ * The product row, greyed out. It uses the real carousel rather than a set of measurements copied
11
+ * out of the package, so its padding, its gaps, how many cards it fits at the current width and its
12
+ * arrows are all right by construction. Matching the real heights is what keeps the page from
13
+ * jumping when the data lands.
14
+ */
15
+ export default function RecsCarouselSkeleton({ count, componentOverride, }: RecsCarouselSkeletonProps): React.JSX.Element;
16
+ export {};
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ interface RecsOptionsSkeletonProps {
3
+ /** How many pills to draw. Defaults to what the row usually holds. */
4
+ count?: number;
5
+ }
6
+ /**
7
+ * The refinement options, greyed out. Matching the real height is what keeps the row from
8
+ * collapsing and shifting the page when the options land.
9
+ *
10
+ * Takes no override, the same way `SuggestedQuestionsSkeleton` takes none: `componentOverrides.
11
+ * loading` stands in for the band above this one, and a caller who replaced it keeps these pills
12
+ * underneath, exactly as a caller who replaces the answer skeleton in question-and-answer keeps the
13
+ * question pills under theirs.
14
+ */
15
+ export default function RecsOptionsSkeleton({ count }: RecsOptionsSkeletonProps): React.JSX.Element;
16
+ export {};
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { CioPiaComponentOverrides, RecsRefinement, Translations } from '../../types';
3
+ interface RecsPodRefinementProps {
4
+ refinement: RecsRefinement | null;
5
+ isLoading: boolean;
6
+ /** Message for a value the shopper submitted that came back rejected. */
7
+ inputError: string | null;
8
+ showInput: boolean;
9
+ translations?: Translations;
10
+ componentOverrides?: CioPiaComponentOverrides;
11
+ onRefine: (text: string) => void;
12
+ onSubmit: (value: string) => void;
13
+ onInputFocus: () => void;
14
+ }
15
+ /**
16
+ * The row under the products: a prompt line, the short options the API suggested, and a box for
17
+ * anything it did not think of. The prompt line is held still while the rest reloads, so the row
18
+ * never changes height under the shopper's cursor.
19
+ */
20
+ export default function RecsPodRefinement({ refinement, isLoading, inputError, showInput, translations, componentOverrides, onRefine, onSubmit, onInputFocus, }: RecsPodRefinementProps): React.JSX.Element;
21
+ export {};
@@ -10,6 +10,14 @@ interface InputProps {
10
10
  onFocus?: () => void;
11
11
  translations?: Translations;
12
12
  componentOverride?: ComponentOverrideProps<InputRenderProps>;
13
+ error?: string;
14
+ placeholderKey?: string;
15
+ /**
16
+ * Render the Send button after the field.
17
+ *
18
+ * @default true
19
+ */
20
+ showSendButton?: boolean;
13
21
  }
14
- declare function Input({ value: providedValue, placeholder, disabled, onSubmit, onFocus, translations, componentOverride, }: InputProps): React.JSX.Element;
22
+ declare function Input({ value: providedValue, placeholder, disabled, onSubmit, onFocus, translations, componentOverride, error, placeholderKey, showSendButton, }: InputProps): React.JSX.Element;
15
23
  export default Input;
@@ -1,7 +1,9 @@
1
- import React from 'react';
1
+ import React, { ReactNode } from 'react';
2
2
  interface SuggestedQuestionProps {
3
3
  question: string;
4
4
  onClick?: () => void;
5
+ /** Replaces the leading icon. Defaults to a question mark. */
6
+ icon?: ReactNode;
5
7
  }
6
- declare function SuggestedQuestion({ question, onClick }: SuggestedQuestionProps): React.JSX.Element;
8
+ declare function SuggestedQuestion({ question, onClick, icon }: SuggestedQuestionProps): React.JSX.Element;
7
9
  export default SuggestedQuestion;
@@ -7,3 +7,14 @@ export declare const MOCK_QUESTIONS: {
7
7
  value: string;
8
8
  }[];
9
9
  export declare const DISCLAIMER_TEXT = "AI-generated answers aim to help, but they may occasionally miss details or be inaccurate. Double-check important information before purchasing.";
10
+ /**
11
+ * Recommendations pod strings the API cannot supply, because in these states there is either
12
+ * no response yet or no usable response at all. Everything else the pod shows comes from the
13
+ * API. All five are `Translations` keys, so reference the constants instead of retyping the
14
+ * literals - a mistyped key falls back to itself with no warning.
15
+ */
16
+ export declare const RECS_LOADING_TITLE = "Adapting recommendations to your preference";
17
+ export declare const RECS_FALLBACK_TITLE = "Best selling products";
18
+ export declare const RECS_UNSUPPORTED_REQUEST = "Unsupported request, try a different feature.";
19
+ export declare const RECS_REFINEMENT_LABEL = "Not what you're looking for? Try:";
20
+ export declare const RECS_INPUT_PLACEHOLDER = "Describe something else...";
@@ -1,10 +1,23 @@
1
1
  import { ConstructorClientOptions } from '@constructor-io/constructorio-client-javascript';
2
2
  import { QuestionResponse, GetSuggestedQuestionsProps, GetAnswerResultsStreamProps, GetAnswerResultsProps, GetAnswerResultsResponse } from './types';
3
+ import { GetRecsProps, RecsResult } from '../../types';
3
4
  declare class MockAgent {
4
5
  options: ConstructorClientOptions;
5
6
  constructor(options: ConstructorClientOptions);
6
7
  getSuggestedQuestions({ itemId, variationId, threadId, parameters, requestParameters, }: GetSuggestedQuestionsProps): Promise<QuestionResponse>;
7
8
  getAnswerResults({ itemId, variationId, threadId, question, parameters, }: GetAnswerResultsProps): Promise<GetAnswerResultsResponse>;
9
+ /**
10
+ * Fetches one set of recommendations, in the shape the pod renders.
11
+ *
12
+ * Recommendations are not available yet: there is no endpoint to ask, so nothing is requested and
13
+ * an empty result is returned. The pod renders nothing in that state, which leaves whatever the
14
+ * retailer already had in that slot showing through. Supply a `cioClient` with your own
15
+ * `agent.getRecs` to drive the pod from your own data in the meantime.
16
+ *
17
+ * `props` is still declared, because it is the contract a caller's own `getRecs` implements and
18
+ * `useRecsPod` passes it - there is simply nothing here to send it to yet.
19
+ */
20
+ getRecs(props: GetRecsProps): Promise<RecsResult>;
8
21
  getAnswerResultsStream({ itemId, threadId, variationId, question, parameters, onStart, onMessage, onEnd, }: GetAnswerResultsStreamProps): Promise<void>;
9
22
  }
10
23
  export default MockAgent;
@@ -0,0 +1,17 @@
1
+ import MockConstructorIOClient from './mocks/MockConstructorIOClient';
2
+ export interface UsePiaClientProps {
3
+ apiKey: string;
4
+ /** Thread ID for conversation context. Must be a valid UUID. One is generated when absent. */
5
+ threadId?: string;
6
+ cioClient?: MockConstructorIOClient;
7
+ }
8
+ export interface UsePiaClientReturn {
9
+ cioClient: MockConstructorIOClient;
10
+ threadId: string;
11
+ }
12
+ /**
13
+ * Resolves the client and thread ID every mode needs: the caller's client when they supplied
14
+ * one, otherwise a client built from the API key, plus a generated thread ID that stays stable
15
+ * for the life of the component.
16
+ */
17
+ export default function usePiaClient({ apiKey, threadId: providedThreadId, cioClient: providedClient, }: UsePiaClientProps): UsePiaClientReturn;
@@ -0,0 +1,52 @@
1
+ import MockConstructorIOClient from './mocks/MockConstructorIOClient';
2
+ import { Formatters, Item, RecsPodParameters, RecsRefinement, Translations } from '../types';
3
+ /**
4
+ * What the shopper did to ask for this refinement.
5
+ *
6
+ * Worth distinguishing because a rejected request is only the shopper's business when the text
7
+ * came from them. An option is a label the API itself suggested, so a rejection there is ours to
8
+ * answer for, not something to flag under their input.
9
+ */
10
+ export type RefinementSource = 'option' | 'input';
11
+ export interface UseRecsPodProps {
12
+ itemId: string;
13
+ variationId?: string;
14
+ threadId?: string;
15
+ cioClient?: MockConstructorIOClient;
16
+ parameters?: RecsPodParameters;
17
+ formatImageUrl?: Formatters['formatImageUrl'];
18
+ translations?: Translations;
19
+ }
20
+ export interface UseRecsPodReturn {
21
+ /** The title to show right now, already resolved for the current state. */
22
+ title: string;
23
+ /** The products to render, or `null` when there are none. Never an empty array. */
24
+ items: Array<Item> | null;
25
+ refinement: RecsRefinement | null;
26
+ /** True while a request is in flight. */
27
+ isLoading: boolean;
28
+ /**
29
+ * The failure behind the fallback title, for callers rendering their own error handling.
30
+ * Only set for an outright failure: a degraded response (`status: 'partial'`) shows the fallback
31
+ * title with `error` still null, because that response arrived and its products are usable.
32
+ */
33
+ error: Error | null;
34
+ /** Message to show under the input when the text the shopper sent was rejected. */
35
+ inputError: string | null;
36
+ /** The text behind what is currently on screen. Empty when nothing has been refined. */
37
+ lastShopperInput: string;
38
+ /**
39
+ * Fetches again, narrowed by `text`. Used by both the options and the free-text input, and
40
+ * `source` is how a rejected request finds the right place to be reported.
41
+ */
42
+ refine: (text: string, source: RefinementSource) => void;
43
+ }
44
+ /**
45
+ * Owns everything a recommendations pod shows: one request on mount, one on every refinement,
46
+ * and the title that belongs to whichever of those is happening right now.
47
+ *
48
+ * The previous items are deliberately kept while a new request is in flight, so a refinement
49
+ * swaps the products without collapsing the row they sit in. The title is the one thing that does
50
+ * change, so the shopper can see the pod reacting to what they asked for.
51
+ */
52
+ export default function useRecsPod({ itemId, variationId, threadId, cioClient, parameters, formatImageUrl, translations, }: UseRecsPodProps): UseRecsPodReturn;
@@ -3,8 +3,9 @@ import { UseTrackingReturn } from './useTracking';
3
3
  export interface UseViewportTrackingProps {
4
4
  tracking: UseTrackingReturn;
5
5
  questions: Question[];
6
+ viewThreshold?: number;
6
7
  }
7
8
  export interface UseViewportTrackingReturn {
8
9
  containerRef: (node: HTMLDivElement | null) => void;
9
10
  }
10
- export default function useViewportTracking({ tracking, questions, }: UseViewportTrackingProps): UseViewportTrackingReturn;
11
+ export default function useViewportTracking({ tracking, questions, viewThreshold, }: UseViewportTrackingProps): UseViewportTrackingReturn;
@@ -25,9 +25,16 @@ export interface CioPiaProviderProps {
25
25
  threadId?: string;
26
26
  cioClient?: Nullable<MockConstructorIOClient>;
27
27
  }
28
- export type CioPiaMode = 'default' | 'conversation';
28
+ export type CioPiaMode = 'default' | 'conversation' | 'recommendations';
29
29
  export type CioPiaType = 'inline' | 'modal';
30
30
  export type DisclaimerPosition = 'top' | 'bottom';
31
+ export type CioPiaTrackingConfigs = {
32
+ /**
33
+ * Fraction of the container (0–1) that must be visible before the
34
+ * `product_insights_agent.view` event fires. Defaults to `0.5`.
35
+ */
36
+ viewThreshold?: number;
37
+ };
31
38
  export type CioPiaDisplayConfigs = {
32
39
  learnMoreUrl?: string;
33
40
  showFeedback?: boolean;
@@ -60,6 +67,16 @@ export type Translations = {
60
67
  'Learn More.'?: string;
61
68
  'Ask about this product'?: string;
62
69
  'Add to Cart'?: string;
70
+ /** Recommendations pod title shown while a request is in flight. */
71
+ 'Adapting recommendations to your preference'?: string;
72
+ /** Recommendations pod title shown when a request fails or comes back degraded. */
73
+ 'Best selling products'?: string;
74
+ /** Shown under the recommendations pod input when the request was rejected as unsuitable. */
75
+ 'Unsupported request, try a different feature.'?: string;
76
+ /** Introduces the recommendations pod refinement options when the API sends no prompt of its own. */
77
+ "Not what you're looking for? Try:"?: string;
78
+ /** Placeholder for the recommendations pod refinement input. */
79
+ 'Describe something else...'?: string;
63
80
  };
64
81
  export type QuestionSource = 'user' | 'suggestion';
65
82
  export interface PiaCallbackContext {
@@ -115,6 +132,62 @@ export interface ConversationEntry {
115
132
  threadId?: string;
116
133
  qnaResultId?: string;
117
134
  }
135
+ /** Which kind of recommendations to fetch. */
136
+ export type RecsStrategy = 'complementary_items' | 'alternative_items' | 'bestsellers' | 'bundles' | 'buy_it_again' | 'recently_viewed_items' | 'visually_similar_items';
137
+ /** A prompt line plus the short labels the shopper can pick from to narrow the results. */
138
+ export interface RecsRefinement {
139
+ /** Introduces the options. Falls back to a translatable default when absent. */
140
+ question?: string;
141
+ options: string[];
142
+ }
143
+ /** One recommendations response, in the shape the pod renders. */
144
+ export interface RecsResult {
145
+ title: string;
146
+ items: Item[] | null;
147
+ refinement: RecsRefinement | null;
148
+ resultId?: string;
149
+ threadId?: string;
150
+ /** `'partial'` means the response is degraded, but any items it carries are still usable. */
151
+ status?: 'complete' | 'partial';
152
+ }
153
+ export interface RecsPodParameters {
154
+ /**
155
+ * Which kind of recommendations to fetch. Inert until the recommendations endpoint ships: it is
156
+ * forwarded to `agent.getRecs`, which has nothing to send it to yet, so only a caller's own
157
+ * `getRecs` acts on it today.
158
+ *
159
+ * @default 'complementary_items'
160
+ */
161
+ strategy?: RecsStrategy;
162
+ /** Last-resort title, used when the API returns items but no title of its own. */
163
+ defaultTitle?: string;
164
+ /**
165
+ * Render the free-text refinement input next to the refinement options.
166
+ *
167
+ * @default true
168
+ */
169
+ showInput?: boolean;
170
+ /** How many products to request. */
171
+ numResults?: number;
172
+ }
173
+ /** Arguments for one recommendations request. */
174
+ export interface GetRecsProps {
175
+ itemId: string;
176
+ variationId?: string;
177
+ /** Thread ID for conversation context. Must be a valid UUID. */
178
+ threadId?: string;
179
+ /** @default 'complementary_items' */
180
+ strategy?: RecsStrategy;
181
+ /** Free text the shopper submitted to narrow the results. Absent on the first request. */
182
+ shopperInput?: string;
183
+ numResults?: number;
184
+ /**
185
+ * Applied while the raw results are converted to Items. It lives on the request rather than
186
+ * in the component because the raw response shape is provisional, and keeping the conversion
187
+ * behind this one call is what makes the endpoint swappable later.
188
+ */
189
+ formatImageUrl?: Formatters['formatImageUrl'];
190
+ }
118
191
  /**
119
192
  * Render props passed to CioPia children function
120
193
  */
@@ -155,6 +228,8 @@ export interface InputRenderProps {
155
228
  onSubmit: (value: string) => void;
156
229
  onFocus?: () => void;
157
230
  translations?: Translations;
231
+ /** Validation message for the value that was just submitted, when there is one. */
232
+ error?: string;
158
233
  }
159
234
  /**
160
235
  * Component overrides for CioPia.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Joins class names, dropping the falsy ones, so a conditional class can be written inline.
3
+ *
4
+ * Deliberately not `clsx` or `classnames`: neither is a dependency of this package, and the copy of
5
+ * `clsx` in the tree belongs to `@constructor-io/constructorio-ui-components`, which is a peer
6
+ * dependency - importing it here would work locally and break in a consumer that hoists
7
+ * differently. Worth revisiting if this ever needs to accept objects or nested arrays.
8
+ */
9
+ export declare const cx: (...parts: (string | false | null | undefined)[]) => string;
@@ -0,0 +1,6 @@
1
+ import { RecsRefinement, Translations } from '../types';
2
+ /**
3
+ * The line that introduces the refinement options. Prefers the prompt the API sent, so our
4
+ * copy and theirs cannot drift.
5
+ */
6
+ export declare function resolveRefinementQuestion(refinement: RecsRefinement | null, translations?: Translations): string;
@@ -1,2 +1,2 @@
1
- declare const _default: "2.10.3";
1
+ declare const _default: "2.11.0";
2
2
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructor-io/constructorio-ui-pia",
3
- "version": "2.10.3",
3
+ "version": "2.11.0",
4
4
  "description": "Constructor.io AI Product Insights Agent UI library for web applications",
5
5
  "author": "Constructor.io Corporation",
6
6
  "license": "MIT",