@constructor-io/constructorio-ui-asa 1.8.0 → 1.10.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 (43) hide show
  1. package/README.md +8 -210
  2. package/dist/constructorio-ui-asa-bundled.js +79 -21
  3. package/lib/cjs/components/Button/Button.js +22 -3
  4. package/lib/cjs/components/Chat/AiMessage.css +52 -0
  5. package/lib/cjs/components/Chat/AiMessage.js +6 -2
  6. package/lib/cjs/components/Chat/Chat.js +18 -4
  7. package/lib/cjs/components/Chat/ChatInput.js +3 -3
  8. package/lib/cjs/hooks/asaStreamHandlers.js +1 -1
  9. package/lib/cjs/hooks/persistence/backgroundTurns.js +35 -0
  10. package/lib/cjs/hooks/persistence/useChatPersistence.js +35 -10
  11. package/lib/cjs/hooks/useAsaResults.js +54 -26
  12. package/lib/cjs/hooks/useAsaTracking.js +31 -16
  13. package/lib/cjs/index.js +3 -1
  14. package/lib/cjs/utils/chatThreads.js +15 -1
  15. package/lib/cjs/utils/localStoragePersistence.js +110 -19
  16. package/lib/cjs/utils/markdown.js +60 -0
  17. package/lib/cjs/version.js +1 -1
  18. package/lib/mjs/components/Button/Button.js +26 -3
  19. package/lib/mjs/components/Chat/AiMessage.css +52 -0
  20. package/lib/mjs/components/Chat/AiMessage.js +6 -2
  21. package/lib/mjs/components/Chat/Chat.js +18 -4
  22. package/lib/mjs/components/Chat/ChatInput.js +3 -3
  23. package/lib/mjs/hooks/asaStreamHandlers.js +1 -1
  24. package/lib/mjs/hooks/persistence/backgroundTurns.js +35 -1
  25. package/lib/mjs/hooks/persistence/useChatPersistence.js +35 -11
  26. package/lib/mjs/hooks/useAsaResults.js +53 -24
  27. package/lib/mjs/hooks/useAsaTracking.js +40 -16
  28. package/lib/mjs/index.js +1 -0
  29. package/lib/mjs/utils/chatThreads.js +12 -0
  30. package/lib/mjs/utils/localStoragePersistence.js +106 -14
  31. package/lib/mjs/utils/markdown.js +53 -0
  32. package/lib/mjs/version.js +1 -1
  33. package/lib/types/components/Button/Button.d.ts +3 -2
  34. package/lib/types/components/Chat/Chat.d.ts +13 -1
  35. package/lib/types/hooks/persistence/backgroundTurns.d.ts +6 -0
  36. package/lib/types/hooks/persistence/useChatPersistence.d.ts +4 -0
  37. package/lib/types/hooks/useAsaTracking.d.ts +4 -2
  38. package/lib/types/index.d.ts +3 -2
  39. package/lib/types/types.d.ts +57 -8
  40. package/lib/types/utils/chatThreads.d.ts +4 -0
  41. package/lib/types/utils/markdown.d.ts +8 -0
  42. package/lib/types/version.d.ts +1 -1
  43. package/package.json +6 -2
@@ -179,12 +179,24 @@ export interface ResultGroup {
179
179
  searchResultId?: string;
180
180
  intentResultId?: string;
181
181
  }
182
+ export interface SendMessageOptions {
183
+ /**
184
+ * Conversation to send into, e.g. one started elsewhere on the page. The chat moves to it
185
+ * first: loaded from storage when persistence has it, otherwise continued on the server
186
+ * with an empty screen. Omit to send into the conversation on screen.
187
+ */
188
+ threadId?: string;
189
+ }
182
190
  export interface UseChatReturn {
183
191
  messages: ChatMessage[];
184
- sendMessage: (text: string, source?: AssistantSubmitSource) => void;
192
+ /** Skipped while another answer streams. */
193
+ sendMessage: (text: string, source?: AssistantSubmitSource, options?: SendMessageOptions) => void;
185
194
  /** True while an answer is streaming, here or, with persistence on, in another tab on the same thread. */
186
195
  isStreaming: boolean;
187
- /** True while this tab's own answer is streaming, the one `abort` can stop. */
196
+ /**
197
+ * True while `abort` can stop the answer: this tab's own, or, when the store can reach it, one
198
+ * streaming in another tab.
199
+ */
188
200
  canAbort: boolean;
189
201
  /**
190
202
  * Cancel the in-flight request, keeping the conversation. The partial reply is settled
@@ -261,6 +273,18 @@ export interface ChatPersistence {
261
273
  * Returns an unsubscribe function.
262
274
  */
263
275
  subscribe?(listener: () => void): () => void;
276
+ /** Optional. Ask the tab streaming `messageId` into `threadId` to stop it. */
277
+ requestAbort?(threadId: string, messageId: string): void;
278
+ /**
279
+ * Optional. Notify when another tab asks to stop an answer, see `requestAbort`. Returns an
280
+ * unsubscribe function. Without both, an answer streaming in another tab cannot be stopped.
281
+ */
282
+ subscribeAbort?(listener: (request: AbortRequest) => void): () => void;
283
+ }
284
+ /** Another tab asks to stop the answer `messageId` streaming into `threadId`. */
285
+ export interface AbortRequest {
286
+ threadId: string;
287
+ messageId: string;
264
288
  }
265
289
  /** Which stored history `clearPersistedConversations` deletes; mirror what the provider was given. */
266
290
  export interface ClearPersistedConversationsOptions {
@@ -289,20 +313,44 @@ export interface LocalStoragePersistenceOptions {
289
313
  /** Storage to use instead of the browser's; takes precedence over `storageArea`. */
290
314
  storage?: Storage;
291
315
  }
292
- /** How an intent was submitted: typed input, a welcome-screen suggestion chip, or a refinement chip. */
293
- export type AssistantSubmitSource = 'input' | 'suggestion' | 'refinement';
316
+ /**
317
+ * How an intent was submitted: typed input, a welcome-screen suggestion chip, a refinement chip,
318
+ * or the host page (`ChatHandle.sendMessage` or the `initialPrompt` prop).
319
+ */
320
+ export type AssistantSubmitSource = 'input' | 'suggestion' | 'refinement' | 'external';
294
321
  /** An item within a viewed/clicked search_result pod. */
295
322
  export interface AssistantTrackedItem {
296
323
  itemId?: string;
297
324
  itemName?: string;
298
325
  variationId?: string;
299
326
  }
327
+ /** Agent mode a CTA opens or starts. */
328
+ export type AgentButtonClickMode = 'chat' | 'search' | 'qna' | 'recommendations' | 'query_refinement';
329
+ /** Page surface an agent CTA was clicked on. */
330
+ export type AgentButtonClickPageType = 'home' | 'plp' | 'pdp' | 'search' | 'collection' | 'email_campaign' | 'cart';
331
+ /** Where an agent CTA sits on the page. All fields are optional. */
332
+ export interface AgentButtonClickPlacement {
333
+ /** Stable label describing where the CTA is placed, e.g. `'header'` or `'search_bar'`. */
334
+ positionOnPage?: string;
335
+ /** Page surface where the CTA was clicked. */
336
+ pageType?: AgentButtonClickPageType;
337
+ /** 1-based index distinguishing CTA instances with the same mode and position during one page view. */
338
+ instanceId?: number;
339
+ }
340
+ /** Fields sent with `ai_agent_button_click` and passed to `onAgentButtonClick`. */
341
+ export interface AgentButtonClickPayload extends AgentButtonClickPlacement {
342
+ mode: AgentButtonClickMode;
343
+ /** Agent experience the CTA opens, sent as `agent_domain`. */
344
+ agentDomain: string;
345
+ }
300
346
  /**
301
347
  * Optional consumer callbacks fired alongside the built-in behavioral tracking.
302
348
  * Each fires immediately after its corresponding `trackAssistant*` beacon is sent,
303
349
  * so consumers can mirror ASA analytics into their own systems. All are optional.
304
350
  */
305
351
  export interface AsaCallbacks {
352
+ /** A CTA that opens the assistant (the built-in `Button`) was clicked. */
353
+ onAgentButtonClick?: (payload: AgentButtonClickPayload) => void;
306
354
  /** User submitted an intent (typed) or clicked a suggestion / refinement chip. */
307
355
  onAssistantSubmit?: (payload: {
308
356
  intent: string;
@@ -383,12 +431,13 @@ export interface ChatInputRenderProps {
383
431
  onSubmit: () => void;
384
432
  placeholder: string;
385
433
  isDisabled: boolean;
434
+ /** Whether a reply is currently streaming, here or in another tab; for status and disabled state. */
435
+ isStreaming: boolean;
386
436
  /**
387
- * Whether a reply is currently streaming. Pair it with `onAbort` to offer a cancel
388
- * control while it is true — the built-in stop button is off by default, so an override
389
- * is often the only way a user can cancel.
437
+ * Whether `onAbort` can stop the reply streaming now; show a cancel control only then. The
438
+ * built-in stop button is off by default, so an override is often the only way a user can cancel.
390
439
  */
391
- isStreaming: boolean;
440
+ canAbort: boolean;
392
441
  /** Cancel the in-flight reply. Keeps the conversation and the thread. */
393
442
  onAbort: () => void;
394
443
  }
@@ -8,6 +8,8 @@ export declare const DEFAULT_PERSISTENCE_KEY = "cio-asa:chat";
8
8
  export declare const DEFAULT_PERSISTENCE_TTL_MS: number;
9
9
  /** How long a stored answer written by another tab is still shown as "typing" before it is settled. */
10
10
  export declare const IN_FLIGHT_GRACE_MS: number;
11
+ /** How long to wait for the tab streaming an answer to store it stopped, before settling it here. */
12
+ export declare const FOREIGN_ABORT_TIMEOUT_MS: number;
11
13
  /** Random token for the ids that must not collide between tabs: threads, tabs and messages. */
12
14
  export declare function randomId(): string;
13
15
  /** Client-side thread id for a conversation the server has not named yet. */
@@ -28,6 +30,8 @@ export declare function nextMessageCounter(messages: ChatMessage[]): number;
28
30
  export declare function isInFlight(messages: ChatMessage[]): boolean;
29
31
  /** Settles answers stored mid-stream: `done` when they have content, an interrupted `error` otherwise. */
30
32
  export declare function normalizeHydratedMessages(messages: ChatMessage[]): ChatMessage[];
33
+ /** `messages` with the cancelled reply settled: `done` when it has content, dropped otherwise. */
34
+ export declare function settleCancelledReply(messages: ChatMessage[], assistantId: string): ChatMessage[];
31
35
  /** Makes the product card for a raw search-result item. */
32
36
  export type ItemNormalizer = (item: Record<string, unknown>) => Product;
33
37
  /** Messages as stored: raw items replaced by the cards `normalize` makes; a restored group keeps its cards. */
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Turns the agent's markdown into sanitized HTML: paragraphs, bold, links, lists and tables.
3
+ * Links to other sites become plain text. Browser only: without a DOM to sanitize with (server
4
+ * rendering) it returns `''`, so render the plain text instead.
5
+ */
6
+ export declare function renderMarkdown(markdown: string): string;
7
+ /** The agent's markdown as plain text, one space between blocks and table cells, for screen readers. */
8
+ export declare function markdownToText(markdown: string): string;
@@ -1,2 +1,2 @@
1
- declare const _default: "1.8.0";
1
+ declare const _default: "1.10.0";
2
2
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructor-io/constructorio-ui-asa",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "Constructor.io AI Shopping Agent UI library for web applications",
5
5
  "author": "constructor.io",
6
6
  "license": "MIT",
@@ -82,7 +82,7 @@
82
82
  "@babel/preset-env": "^7.28.3",
83
83
  "@babel/preset-react": "^7.27.1",
84
84
  "@babel/preset-typescript": "^7.27.1",
85
- "@constructor-io/constructorio-client-javascript": "2.93.1",
85
+ "@constructor-io/constructorio-client-javascript": "2.94.0",
86
86
  "@constructor-io/constructorio-ui-components": "^1.6.0",
87
87
  "@cspell/eslint-plugin": "^6.18.1",
88
88
  "@storybook/addon-a11y": "^8.6.14",
@@ -148,5 +148,9 @@
148
148
  "volta": {
149
149
  "node": "22.18.0",
150
150
  "npm": "10.9.3"
151
+ },
152
+ "dependencies": {
153
+ "dompurify": "3.4.17",
154
+ "marked": "15.0.12"
151
155
  }
152
156
  }