vdb-ai-chat 1.0.14 → 1.0.16

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 (40) hide show
  1. package/dist/chat-widget.js +1 -1
  2. package/lib/commonjs/api.js +44 -3
  3. package/lib/commonjs/api.js.map +1 -1
  4. package/lib/commonjs/components/ChatWidget.js +129 -34
  5. package/lib/commonjs/components/ChatWidget.js.map +1 -1
  6. package/lib/commonjs/components/MessageBubble.js +3 -1
  7. package/lib/commonjs/components/MessageBubble.js.map +1 -1
  8. package/lib/commonjs/components/ProductsListView.js +2 -3
  9. package/lib/commonjs/components/ProductsListView.js.map +1 -1
  10. package/lib/commonjs/components/SuggestionsRow.js.map +1 -1
  11. package/lib/commonjs/components/utils.js +436 -5
  12. package/lib/commonjs/components/utils.js.map +1 -1
  13. package/lib/module/api.js +43 -3
  14. package/lib/module/api.js.map +1 -1
  15. package/lib/module/components/ChatWidget.js +131 -36
  16. package/lib/module/components/ChatWidget.js.map +1 -1
  17. package/lib/module/components/MessageBubble.js +3 -1
  18. package/lib/module/components/MessageBubble.js.map +1 -1
  19. package/lib/module/components/ProductsListView.js +2 -3
  20. package/lib/module/components/ProductsListView.js.map +1 -1
  21. package/lib/module/components/SuggestionsRow.js.map +1 -1
  22. package/lib/module/components/utils.js +434 -4
  23. package/lib/module/components/utils.js.map +1 -1
  24. package/lib/module/hooks/useAnalytics.js.map +1 -1
  25. package/lib/typescript/api.d.ts +12 -0
  26. package/lib/typescript/api.d.ts.map +1 -1
  27. package/lib/typescript/components/ChatWidget.d.ts.map +1 -1
  28. package/lib/typescript/components/MessageBubble.d.ts.map +1 -1
  29. package/lib/typescript/components/ProductsListView.d.ts.map +1 -1
  30. package/lib/typescript/components/SuggestionsRow.d.ts.map +1 -1
  31. package/lib/typescript/components/utils.d.ts +6 -0
  32. package/lib/typescript/components/utils.d.ts.map +1 -1
  33. package/package.json +1 -1
  34. package/src/api.ts +69 -7
  35. package/src/components/ChatWidget.tsx +187 -36
  36. package/src/components/MessageBubble.tsx +15 -4
  37. package/src/components/ProductsListView.tsx +32 -17
  38. package/src/components/SuggestionsRow.tsx +1 -7
  39. package/src/components/utils.ts +545 -7
  40. package/src/hooks/useAnalytics.tsx +2 -2
@@ -2,7 +2,19 @@ import { FeedbackAction } from "./components/utils";
2
2
  import type { ChatMessage } from "./types";
3
3
  export type ChatApiParams = Record<string, any>;
4
4
  export declare function normaliseMessages(raw: any): ChatMessage[];
5
+ export interface PaginatedMessagesResponse {
6
+ messages: any[];
7
+ conversation_id?: string | number;
8
+ has_more?: boolean;
9
+ next_cursor?: string | number;
10
+ total_count?: number;
11
+ }
5
12
  export declare function fetchInitialMessages(apiUrl: string, _params?: ChatApiParams, priceMode?: string | null): Promise<any>;
13
+ /**
14
+ * Fetches older messages for pagination when user scrolls to top.
15
+ * Uses cursor-based pagination if available, falls back to page number.
16
+ */
17
+ export declare function fetchOlderMessages(apiUrl: string, priceMode: string | null, cursor?: string | number, pageNumber?: number): Promise<PaginatedMessagesResponse>;
6
18
  export declare function sendUserMessage(apiUrl: string, userMessage: string, _params: ChatApiParams, _history: ChatMessage[], priceMode: any): Promise<any>;
7
19
  export declare function clearChatHistory(apiUrl: string, priceMode: any): Promise<void>;
8
20
  export declare function getProducts(params: ChatApiParams, priceMode: string | null): Promise<any>;
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAe,MAAM,oBAAoB,CAAC;AAEjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAG3C,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAqEhD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,WAAW,EAAE,CAkBzD;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,aAAa,EACvB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GACxB,OAAO,CAAC,GAAG,CAAC,CAmCd;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,WAAW,EAAE,EACvB,SAAS,EAAE,GAAG,GACb,OAAO,CAAC,GAAG,CAAC,CA6Cd;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,GAAG,GACb,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED,wBAAsB,WAAW,CAC/B,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,OAAO,CAAC,GAAG,CAAC,CA6Fd;AAED,eAAO,MAAM,uBAAuB,GAClC,QAAQ,cAAc,EACtB,iBAAiB,MAAM,EACvB,YAAY,MAAM,sBAanB,CAAC"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAe,MAAM,oBAAoB,CAAC;AAEjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAG3C,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAqEhD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,GAAG,WAAW,EAAE,CAkBzD;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,GAAG,EAAE,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,aAAa,EACvB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GACxB,OAAO,CAAC,GAAG,CAAC,CAmCd;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EACxB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,yBAAyB,CAAC,CA0CpC;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,WAAW,EAAE,EACvB,SAAS,EAAE,GAAG,GACb,OAAO,CAAC,GAAG,CAAC,CAyCd;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,GAAG,GACb,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED,wBAAsB,WAAW,CAC/B,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,OAAO,CAAC,GAAG,CAAC,CAkGd;AAED,eAAO,MAAM,uBAAuB,GAClC,QAAQ,cAAc,EACtB,iBAAiB,MAAM,EACvB,YAAY,MAAM,sBAanB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ChatWidget.d.ts","sourceRoot":"","sources":["../../../src/components/ChatWidget.tsx"],"names":[],"mappings":"AAAA,OAAO,KAQN,MAAM,OAAO,CAAC;AAef,OAAO,KAAK,EAAe,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AA2B5E,eAAO,MAAM,UAAU,uFA2yBtB,CAAC"}
1
+ {"version":3,"file":"ChatWidget.d.ts","sourceRoot":"","sources":["../../../src/components/ChatWidget.tsx"],"names":[],"mappings":"AAAA,OAAO,KAQN,MAAM,OAAO,CAAC;AAgBf,OAAO,KAAK,EAAe,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AA4B5E,eAAO,MAAM,UAAU,uFAk7BtB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"MessageBubble.d.ts","sourceRoot":"","sources":["../../../src/components/MessageBubble.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAe,MAAM,OAAO,CAAC;AAGpC,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAMvD,UAAU,KAAK;IACb,OAAO,EAAE,WAAW,CAAC;IACrB,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAChD,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACjD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAmDD,eAAO,MAAM,aAAa,mCAA+B,CAAC"}
1
+ {"version":3,"file":"MessageBubble.d.ts","sourceRoot":"","sources":["../../../src/components/MessageBubble.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAe,MAAM,OAAO,CAAC;AAGpC,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAOvD,UAAU,KAAK;IACb,OAAO,EAAE,WAAW,CAAC;IACrB,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAChD,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACjD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA6DD,eAAO,MAAM,aAAa,mCAA+B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProductsListView.d.ts","sourceRoot":"","sources":["../../../src/components/ProductsListView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoC,MAAM,OAAO,CAAC;AAsHzD,UAAU,qBAAqB;IAC7B,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/B,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAClC,IAAI,EAAE,GAAG,CAAC;CACX;AA0PD,QAAA,MAAM,gBAAgB,mDAAkC,CAAC;AACzD,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"ProductsListView.d.ts","sourceRoot":"","sources":["../../../src/components/ProductsListView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoC,MAAM,OAAO,CAAC;AA0HzD,UAAU,qBAAqB;IAC7B,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/B,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAClC,IAAI,EAAE,GAAG,CAAC;CACX;AAqQD,QAAA,MAAM,gBAAgB,mDAAkC,CAAC;AACzD,eAAe,gBAAgB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"SuggestionsRow.d.ts","sourceRoot":"","sources":["../../../src/components/SuggestionsRow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAe,MAAM,OAAO,CAAC;AAYpC,UAAU,mBAAmB;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,uFAAuF;IACvF,OAAO,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;CAChC;AAkGD,QAAA,MAAM,cAAc,iDAAgC,CAAC;AACrD,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"SuggestionsRow.d.ts","sourceRoot":"","sources":["../../../src/components/SuggestionsRow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAe,MAAM,OAAO,CAAC;AAMpC,UAAU,mBAAmB;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,uFAAuF;IACvF,OAAO,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;CAChC;AAkGD,QAAA,MAAM,cAAc,iDAAgC,CAAC;AACrD,eAAe,cAAc,CAAC"}
@@ -43,4 +43,10 @@ export declare const useDeviceType: () => {
43
43
  isDesktop: boolean;
44
44
  isLargeScreen: boolean;
45
45
  };
46
+ export declare const generateSavedSearchName: (search_params: any) => {
47
+ title: string;
48
+ options: {
49
+ [k: string]: any;
50
+ };
51
+ };
46
52
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/components/utils.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,mCAA2B,CAAC;AAEpD,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAatD;AAED,oBAAY,cAAc;IACxB,IAAI,MAAM;IACV,OAAO,MAAM;IACb,KAAK,MAAM;CACZ;AAcD,eAAO,MAAM,mBAAmB,GAC9B,YAAY,MAAM,KACjB,OAAO,CAAC,MAAM,GAAG,IAAI,CAqBvB,CAAC;AAEF,eAAO,MAAM,qBAAqB,QAAa,OAAO,CAAC,MAAM,GAAG,IAAI,CAInE,CAAC;AAGF,eAAO,MAAM,aAAa,QAAa,OAAO,CAAC,OAAO,CAMrD,CAAC;AAGF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAI/D;AAGD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,OAAO,UAiB7E;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,YAAY,CAAC,EAAE,GAAG,CAAC;IACnB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,cAAc,QAAa,OAAO,CAAC,WAAW,GAAG,IAAI,CAqBjE,CAAC;AAEF,oBAAY,UAAU;IACpB,GAAG,QAAQ;IACX,GAAG,QAAQ;IACX,OAAO,YAAY;CACpB;AAED,eAAO,MAAM,aAAa,GAAI,WAAW,MAAM,cAW9C,CAAC;AAEF,oBAAY,mBAAmB;IAC7B,aAAa,0BAA0B;IACvC,aAAa,0BAA0B;IACvC,YAAY,oBAAoB;CACjC;AAED,eAAO,MAAM,MAAM,eAclB,CAAC;AAEF,eAAO,MAAM,aAAa;;;;;CAUzB,CAAC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/components/utils.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,mCAA2B,CAAC;AAEpD,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAatD;AAED,oBAAY,cAAc;IACxB,IAAI,MAAM;IACV,OAAO,MAAM;IACb,KAAK,MAAM;CACZ;AAcD,eAAO,MAAM,mBAAmB,GAC9B,YAAY,MAAM,KACjB,OAAO,CAAC,MAAM,GAAG,IAAI,CAqBvB,CAAC;AAEF,eAAO,MAAM,qBAAqB,QAAa,OAAO,CAAC,MAAM,GAAG,IAAI,CAInE,CAAC;AAEF,eAAO,MAAM,aAAa,QAAa,OAAO,CAAC,OAAO,CAMrD,CAAC;AAGF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAI/D;AAGD,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,GAAG,EACV,WAAW,EAAE,OAAO,UAgBrB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,YAAY,CAAC,EAAE,GAAG,CAAC;IACnB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,cAAc,QAAa,OAAO,CAAC,WAAW,GAAG,IAAI,CAqBjE,CAAC;AAEF,oBAAY,UAAU;IACpB,GAAG,QAAQ;IACX,GAAG,QAAQ;IACX,OAAO,YAAY;CACpB;AAED,eAAO,MAAM,aAAa,GAAI,WAAW,MAAM,cAW9C,CAAC;AAEF,oBAAY,mBAAmB;IAC7B,aAAa,0BAA0B;IACvC,aAAa,0BAA0B;IACvC,YAAY,oBAAoB;CACjC;AAED,eAAO,MAAM,MAAM,eAclB,CAAC;AAEF,eAAO,MAAM,aAAa;;;;;CAUzB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,eAAe,GAAG;;;;;CAuhBzD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vdb-ai-chat",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "Cross-platform AI chat widget for React Native and Web",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
package/src/api.ts CHANGED
@@ -92,6 +92,14 @@ export function normaliseMessages(raw: any): ChatMessage[] {
92
92
  }));
93
93
  }
94
94
 
95
+ export interface PaginatedMessagesResponse {
96
+ messages: any[];
97
+ conversation_id?: string | number;
98
+ has_more?: boolean;
99
+ next_cursor?: string | number;
100
+ total_count?: number;
101
+ }
102
+
95
103
  export async function fetchInitialMessages(
96
104
  apiUrl: string,
97
105
  _params?: ChatApiParams,
@@ -133,6 +141,59 @@ export async function fetchInitialMessages(
133
141
  return data;
134
142
  }
135
143
 
144
+ /**
145
+ * Fetches older messages for pagination when user scrolls to top.
146
+ * Uses cursor-based pagination if available, falls back to page number.
147
+ */
148
+ export async function fetchOlderMessages(
149
+ apiUrl: string,
150
+ priceMode: string | null,
151
+ cursor?: string | number,
152
+ pageNumber?: number
153
+ ): Promise<PaginatedMessagesResponse> {
154
+ const conversations = await getValidConversations();
155
+ const storedId = conversations?.[priceMode ?? ""]?.conversation_id ?? null;
156
+
157
+ if (!storedId) {
158
+ return { messages: [], has_more: false };
159
+ }
160
+
161
+ const body: any = {
162
+ conversation_id: Number.isNaN(Number(storedId))
163
+ ? storedId
164
+ : Number(storedId),
165
+ fetch_history: true,
166
+ };
167
+
168
+ // Support both cursor-based and page-based pagination
169
+ if (cursor !== undefined) {
170
+ body.cursor = cursor;
171
+ }
172
+ if (pageNumber !== undefined) {
173
+ body.page = pageNumber;
174
+ }
175
+
176
+ const res = await fetch(apiUrl, {
177
+ method: "POST",
178
+ headers: await buildHeaders(),
179
+ body: JSON.stringify(body),
180
+ });
181
+
182
+ if (!res.ok) {
183
+ throw new Error(`POST ${apiUrl} failed with ${res.status}`);
184
+ }
185
+
186
+ const data = await res.json();
187
+
188
+ return {
189
+ messages: data.messages || [],
190
+ conversation_id: data.conversation_id,
191
+ has_more: data.has_more ?? false,
192
+ next_cursor: data.next_cursor,
193
+ total_count: data.total_count,
194
+ };
195
+ }
196
+
136
197
  export async function sendUserMessage(
137
198
  apiUrl: string,
138
199
  userMessage: string,
@@ -149,10 +210,6 @@ export async function sendUserMessage(
149
210
  }
150
211
 
151
212
  const external_context = await Storage.getJSON<string>("external_context");
152
- if(external_context) {
153
- await Storage.removeItem("external_context");
154
- }
155
-
156
213
  const body: any = {
157
214
  query: userMessage,
158
215
  external_context: external_context || null,
@@ -217,7 +274,12 @@ export async function getProducts(
217
274
  params: ChatApiParams,
218
275
  priceMode: string | null
219
276
  ): Promise<any> {
220
- const isLabgrown = (typeof params.lab_grown === "string" ? params.lab_grown === "true" : typeof params.lab_grown === "boolean" ? params.lab_grown : false);
277
+ const isLabgrown =
278
+ typeof params.lab_grown === "string"
279
+ ? params.lab_grown === "true"
280
+ : typeof params.lab_grown === "boolean"
281
+ ? params.lab_grown
282
+ : false;
221
283
  const userInfo = await Storage.getJSON<UserDetails>("persist:userInfo", {});
222
284
  const activeProduct = isLabgrown ? "lab_grown_diamond" : "diamond";
223
285
  const searchResultViewTypeMap = JSON.parse(
@@ -242,11 +304,11 @@ export async function getProducts(
242
304
  vdbSetting = true;
243
305
  mGroupIds = mGroupIds.filter((groupId: number) => groupId !== 0);
244
306
  }
245
- } else if(!mGroupIds || mGroupIds.length === 0) {
307
+ } else if (!mGroupIds || mGroupIds.length === 0) {
246
308
  vdbSetting = true;
247
309
  }
248
310
 
249
- if(params.three_x) {
311
+ if (params.three_x) {
250
312
  delete params.three_x;
251
313
  }
252
314
 
@@ -17,6 +17,7 @@ import {
17
17
  Keyboard,
18
18
  DeviceEventEmitter,
19
19
  Platform,
20
+ ActivityIndicator,
20
21
  } from "react-native";
21
22
  import { ChatInput } from "./ChatInput";
22
23
  import { MessageBubble } from "./MessageBubble";
@@ -26,6 +27,7 @@ import { mergeTheme } from "../theme";
26
27
  import {
27
28
  ChatApiParams,
28
29
  fetchInitialMessages,
30
+ fetchOlderMessages,
29
31
  getProducts,
30
32
  handleFeedbackActionApi,
31
33
  normaliseMessages,
@@ -75,8 +77,6 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
75
77
  );
76
78
  const [typingMessageId, setTypingMessageId] = useState<string | null>(null);
77
79
  const [typingFullText, setTypingFullText] = useState<string>("");
78
- const [scrollY, setScrollY] = useState(0);
79
- const [autoScroll, setAutoScroll] = useState(true);
80
80
  const [productsByMsg, setProductsByMsg] = useState<Record<string, any>>({});
81
81
  const [reloadLoadingIds, setReloadLoadingIds] = useState<Set<string>>(
82
82
  new Set()
@@ -85,8 +85,21 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
85
85
  priceModeProp || null
86
86
  );
87
87
  const [userToken, setUserToken] = useState<string>(userTokenProp || "");
88
+ // Pagination state
89
+ const [loadingOlder, setLoadingOlder] = useState(false);
90
+ const [hasMoreMessages, setHasMoreMessages] = useState(true);
91
+ const [nextCursor, setNextCursor] = useState<string | number | undefined>(
92
+ undefined
93
+ );
94
+ const [currentPage, setCurrentPage] = useState(1);
88
95
  const scrollRef = useRef<ScrollView | null>(null);
89
96
  const inputRef = useRef<TextInput | null>(null);
97
+ // Scroll management refs - using refs instead of state to avoid re-renders
98
+ const scrollYRef = useRef(0);
99
+ const contentHeightRef = useRef(0);
100
+ const isUserScrollingRef = useRef(false);
101
+ const isPaginatingRef = useRef(false);
102
+ const initialScrollDoneRef = useRef(false);
90
103
  const themeProps = useMemo(
91
104
  () => mergeTheme(themeOverrides),
92
105
  [themeOverrides]
@@ -123,17 +136,24 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
123
136
  if (activeProductType) {
124
137
  await Storage.setJSON("external_context", activeProductType);
125
138
  }
126
- }
139
+ };
127
140
  setActiveProduct();
128
141
  }, [activeProductType]);
129
142
 
130
143
  const onViewAll = (item: any) => {
131
144
  let searchPayload = item?.search_payload;
132
- if(searchPayload?.cut_from || searchPayload?.cut_to || searchPayload?.polish_from || searchPayload?.polish_to || searchPayload?.symmetry_from || searchPayload?.symmetry_to) {
145
+ if (
146
+ searchPayload?.cut_from ||
147
+ searchPayload?.cut_to ||
148
+ searchPayload?.polish_from ||
149
+ searchPayload?.polish_to ||
150
+ searchPayload?.symmetry_from ||
151
+ searchPayload?.symmetry_to
152
+ ) {
133
153
  searchPayload.cutGrades = [];
134
154
  searchPayload.polishes = [];
135
155
  searchPayload.symmetries = [];
136
- if(!searchPayload?.shapes) {
156
+ if (!searchPayload?.shapes) {
137
157
  searchPayload.shapes = [];
138
158
  }
139
159
  }
@@ -155,14 +175,19 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
155
175
  if (searchPayload?.symmetry_to) {
156
176
  searchPayload.symmetries.push(searchPayload?.symmetry_to);
157
177
  }
158
- if(searchPayload?.three_x) {
178
+ if (searchPayload?.three_x) {
159
179
  delete searchPayload.three_x;
160
180
  }
161
181
 
162
182
  const stringifiedSearchPayload = JSON.stringify(searchPayload);
163
183
  const payload = item?.search_payload;
164
184
  if (!payload) return;
165
- const isLabgrown = (typeof payload.lab_grown === "string" ? payload.lab_grown === "true" : typeof payload.lab_grown === "boolean" ? payload.lab_grown : false);
185
+ const isLabgrown =
186
+ typeof payload.lab_grown === "string"
187
+ ? payload.lab_grown === "true"
188
+ : typeof payload.lab_grown === "boolean"
189
+ ? payload.lab_grown
190
+ : false;
166
191
  const domain = apiUrl.split("v3");
167
192
  const deepLinkUrl = `${domain[0]}webapp/${
168
193
  isLabgrown ? "lab-grown-diamonds" : "natural-diamonds"
@@ -258,11 +283,23 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
258
283
  );
259
284
  if (!cancelled) {
260
285
  const normalised = normaliseMessages(initial).reverse();
286
+ // Capture pagination info from initial response
287
+ if (initial?.has_more !== undefined) {
288
+ setHasMoreMessages(initial.has_more);
289
+ }
290
+ if (initial?.next_cursor !== undefined) {
291
+ setNextCursor(initial.next_cursor);
292
+ }
293
+
294
+ // Reset scroll tracking for fresh load
295
+ initialScrollDoneRef.current = false;
296
+ isUserScrollingRef.current = false;
297
+
261
298
  if (normalised.length === 0) {
262
299
  const initialAssistant: ChatMessage = {
263
300
  id: "",
264
301
  role: "assistant",
265
- text: "Describe the diamond youre looking for — for example, 2ct F VS2 under $10,000 or 1–2ct D–F VS pear — and Ill take it from there.",
302
+ text: 'Describe the diamond you\'re looking for — for example, "2ct F VS2 under $10,000" or "1–2ct D–F VS pear" — and I\'ll take it from there.',
266
303
  createdAt: Date.now(),
267
304
  isLoading: false,
268
305
  suggestions: [],
@@ -270,15 +307,11 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
270
307
  reaction: "0",
271
308
  };
272
309
  setMessages([initialAssistant]);
273
- setTimeout(() => {
274
- scrollRef.current?.scrollToEnd({ animated: false });
275
- }, 0);
310
+ setHasMoreMessages(false);
276
311
  } else {
277
312
  setMessages(normalised);
278
- setTimeout(() => {
279
- scrollRef.current?.scrollToEnd({ animated: false });
280
- }, 0);
281
313
  }
314
+ // onContentSizeChange will handle scrollToEnd
282
315
  }
283
316
  } catch (error) {
284
317
  console.error("Failed to fetch initial messages", error);
@@ -298,14 +331,16 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
298
331
  async (rawText: string) => {
299
332
  const trimmed = rawText.trim();
300
333
  if (!trimmed || loading) return;
301
- const external_context = await Storage.getJSON<string>("external_context");
334
+ const external_context = await Storage.getJSON<string>(
335
+ "external_context"
336
+ );
302
337
 
303
338
  const userMessage: ChatMessage = {
304
339
  id: `user-${Date.now()}`,
305
340
  role: "user",
306
341
  text: trimmed,
307
342
  createdAt: Date.now(),
308
- external_context: external_context || null
343
+ external_context: external_context || null,
309
344
  };
310
345
 
311
346
  const loadingId = `bot-loading-${Date.now()}`;
@@ -496,6 +531,71 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
496
531
  }
497
532
  }, []);
498
533
 
534
+ // Load older messages when user scrolls to top
535
+ const loadOlderMessages = useCallback(async () => {
536
+ if (loadingOlder || !hasMoreMessages || !hasAuth) return;
537
+
538
+ // Mark that we're paginating - this prevents auto-scroll to bottom
539
+ isPaginatingRef.current = true;
540
+ const prevHeight = contentHeightRef.current;
541
+ setLoadingOlder(true);
542
+
543
+ try {
544
+ const result = await fetchOlderMessages(
545
+ apiUrl,
546
+ priceMode,
547
+ nextCursor,
548
+ currentPage + 1
549
+ );
550
+
551
+ const olderMessages = normaliseMessages(result);
552
+ if (olderMessages.length > 0) {
553
+ // Prepend older messages to the beginning
554
+ setMessages((prev) => {
555
+ // Filter out duplicates by id
556
+ const existingIds = new Set(prev.map((m) => m.id));
557
+ const newMessages = olderMessages.filter(
558
+ (m) => !existingIds.has(m.id)
559
+ );
560
+ return [...newMessages, ...prev];
561
+ });
562
+ setCurrentPage((prev) => prev + 1);
563
+
564
+ // After state update, adjust scroll to maintain position
565
+ // Use requestAnimationFrame to wait for layout
566
+ requestAnimationFrame(() => {
567
+ const newHeight = contentHeightRef.current;
568
+ const heightDiff = newHeight - prevHeight;
569
+ if (heightDiff > 0 && scrollRef.current) {
570
+ scrollRef.current.scrollTo({
571
+ y: scrollYRef.current + heightDiff,
572
+ animated: false,
573
+ });
574
+ }
575
+ isPaginatingRef.current = false;
576
+ });
577
+ } else {
578
+ isPaginatingRef.current = false;
579
+ }
580
+
581
+ setHasMoreMessages(result.has_more ?? olderMessages.length > 0);
582
+ setNextCursor(result.next_cursor);
583
+ } catch (e) {
584
+ console.error("Failed to load older messages", e);
585
+ isPaginatingRef.current = false;
586
+ } finally {
587
+ setLoadingOlder(false);
588
+ }
589
+ }, [
590
+ loadingOlder,
591
+ hasMoreMessages,
592
+ hasAuth,
593
+ apiUrl,
594
+ priceMode,
595
+ nextCursor,
596
+ currentPage,
597
+ ]);
598
+
499
599
  useEffect(() => {
500
600
  const scrollToBottom = () => {
501
601
  setTimeout(() => {
@@ -505,8 +605,10 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
505
605
  }, 100);
506
606
  };
507
607
 
508
- const showEvent = Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow";
509
- const hideEvent = Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide";
608
+ const showEvent =
609
+ Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow";
610
+ const hideEvent =
611
+ Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide";
510
612
 
511
613
  const subShow = Keyboard.addListener(showEvent, scrollToBottom);
512
614
  const subHide = Keyboard.addListener(hideEvent, scrollToBottom);
@@ -574,6 +676,14 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
574
676
  setLoadingMessageId(null);
575
677
  setTypingMessageId(null);
576
678
  setTypingFullText("");
679
+ // Reset pagination state
680
+ setHasMoreMessages(true);
681
+ setNextCursor(undefined);
682
+ setCurrentPage(1);
683
+ // Reset scroll tracking
684
+ initialScrollDoneRef.current = false;
685
+ isUserScrollingRef.current = false;
686
+ isPaginatingRef.current = false;
577
687
 
578
688
  const fresh = await fetchInitialMessages(apiUrl, undefined, priceMode);
579
689
  const normalised = normaliseMessages(fresh).reverse();
@@ -581,7 +691,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
581
691
  const initialAssistant: ChatMessage = {
582
692
  id: "",
583
693
  role: "assistant",
584
- text: "Describe the diamond youre looking for — for example, 2ct F VS2 under $10,000 or 1–2ct D–F VS pear — and Ill take it from there.",
694
+ text: 'Describe the diamond you\'re looking for — for example, "2ct F VS2 under $10,000" or "1–2ct D–F VS pear" — and I\'ll take it from there.',
585
695
  createdAt: Date.now(),
586
696
  isLoading: false,
587
697
  suggestions: [],
@@ -592,9 +702,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
592
702
  } else {
593
703
  setMessages(normalised);
594
704
  }
595
- setTimeout(() => {
596
- scrollRef.current?.scrollToEnd({ animated: false });
597
- }, 500);
705
+ // onContentSizeChange will handle scrollToEnd
598
706
  onClearChat?.();
599
707
  } catch (err) {
600
708
  console.error("Failed to clear chat", err);
@@ -698,12 +806,22 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
698
806
  onScroll={(e) => {
699
807
  const { contentOffset, contentSize, layoutMeasurement } =
700
808
  e.nativeEvent;
701
- setScrollY(contentOffset.y);
809
+ scrollYRef.current = contentOffset.y;
702
810
  const distanceFromBottom =
703
811
  contentSize.height -
704
812
  (layoutMeasurement.height + contentOffset.y);
705
- // Enable auto-scroll when user is near the bottom; disable when scrolled up
706
- setAutoScroll(distanceFromBottom < 48);
813
+ // Track if user has scrolled away from bottom
814
+ isUserScrollingRef.current = distanceFromBottom > 100;
815
+
816
+ // Load older messages when scrolled near the top (within 50px)
817
+ if (
818
+ contentOffset.y < 50 &&
819
+ hasMoreMessages &&
820
+ !loadingOlder &&
821
+ !isPaginatingRef.current
822
+ ) {
823
+ loadOlderMessages();
824
+ }
707
825
  }}
708
826
  scrollEventThrottle={16}
709
827
  style={
@@ -726,12 +844,38 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
726
844
  ? { maxWidth: 608, alignSelf: "center", width: "100%" }
727
845
  : {}),
728
846
  }}
729
- onContentSizeChange={() => {
730
- if (autoScroll) {
847
+ onContentSizeChange={(_width, height) => {
848
+ contentHeightRef.current = height;
849
+
850
+ // Skip auto-scroll during pagination - scroll is handled in loadOlderMessages
851
+ if (isPaginatingRef.current || loadingOlder) {
852
+ return;
853
+ }
854
+
855
+ // Only auto-scroll to bottom if:
856
+ // 1. Initial scroll hasn't been done yet, OR
857
+ // 2. User is near the bottom (not scrolled up to view history)
858
+ if (!initialScrollDoneRef.current) {
859
+ scrollRef.current?.scrollToEnd({ animated: false });
860
+ initialScrollDoneRef.current = true;
861
+ } else if (!isUserScrollingRef.current) {
862
+ // User is near bottom, auto-scroll for new messages
731
863
  scrollRef.current?.scrollToEnd({ animated: false });
732
864
  }
733
865
  }}
734
866
  >
867
+ {/* Loading indicator for pagination */}
868
+ {loadingOlder && (
869
+ <LoadingOlderContainer>
870
+ <ActivityIndicator
871
+ size="small"
872
+ color={theme["core-06"] || "#4F4E57"}
873
+ />
874
+ <LoadingOlderText theme={theme}>
875
+ Loading older messages...
876
+ </LoadingOlderText>
877
+ </LoadingOlderContainer>
878
+ )}
735
879
  {messages?.[0]?.createdAt && (
736
880
  <EmptyContainer theme={theme}>
737
881
  <EmptyText theme={theme}>
@@ -787,15 +931,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
787
931
  const diamonds = result?.response?.body?.diamonds;
788
932
  const hasAny =
789
933
  Array.isArray(diamonds) && diamonds.length > 0;
790
- if (hasAny) {
791
- try {
792
- scrollRef.current?.scrollTo({
793
- x: 0,
794
- y: scrollY + 120,
795
- animated: true,
796
- });
797
- } catch (_) {}
798
- } else {
934
+ if (!hasAny) {
799
935
  setMessages((prev) =>
800
936
  prev.map((msg) =>
801
937
  msg.id === id
@@ -809,6 +945,7 @@ export const ChatWidget = forwardRef<ChatWidgetRef, ChatWidgetProps>(
809
945
  )
810
946
  );
811
947
  }
948
+ // Let onContentSizeChange handle scrolling naturally
812
949
  }}
813
950
  />
814
951
  )}
@@ -908,6 +1045,20 @@ const LineView = styled.View<{ theme: DefaultTheme }>`
908
1045
  border-top-width: 1;
909
1046
  `;
910
1047
 
1048
+ const LoadingOlderContainer = styled.View`
1049
+ padding: 12px;
1050
+ align-items: center;
1051
+ justify-content: center;
1052
+ flex-direction: row;
1053
+ gap: 8px;
1054
+ `;
1055
+
1056
+ const LoadingOlderText = styled.Text<{ theme: DefaultTheme }>`
1057
+ font-size: 12px;
1058
+ color: ${({ theme }: { theme: DefaultTheme }) =>
1059
+ theme["core-06"] || "#4F4E57"};
1060
+ `;
1061
+
911
1062
  const styles = StyleSheet.create({
912
1063
  listContent: {
913
1064
  paddingVertical: 8,
@@ -6,6 +6,7 @@ import { useTheme } from "styled-components/native";
6
6
  import styled from "styled-components/native";
7
7
  import { DefaultTheme } from "styled-components/native";
8
8
  import { css } from "styled-components/native";
9
+ import { generateSavedSearchName } from "./utils";
9
10
 
10
11
  interface Props {
11
12
  message: ChatMessage;
@@ -34,17 +35,27 @@ const MessageBubbleComponent: React.FC<Props> = ({
34
35
  }) => {
35
36
  const isUser = message.role === "user";
36
37
  const theme = useTheme();
38
+ const isLabgrown =
39
+ typeof message?.search_payload?.lab_grown === "string"
40
+ ? message?.search_payload?.lab_grown === "true"
41
+ : typeof message?.search_payload?.lab_grown === "boolean"
42
+ ? message?.search_payload?.lab_grown
43
+ : false;
44
+
37
45
  return (
38
46
  <Container theme={theme} userTheme={userTheme} isUser={isUser}>
39
47
  <BubbleContainer theme={theme} userTheme={userTheme} isUser={isUser}>
40
48
  <MessageText isUser={isUser} userTheme={userTheme} theme={theme}>
41
- {message.text || ""}
49
+ {!(message.role === "assistant" && hasResults) &&
50
+ (message.text || "")}
42
51
  {message.role === "assistant" &&
43
52
  hasResults &&
44
53
  typeof totalResults === "number"
45
- ? ` Found ${Number(
46
- totalResults
47
- ).toLocaleString()} diamonds (showing first ${shownResults})`
54
+ ? `Found ${Number(totalResults).toLocaleString()} ${
55
+ isLabgrown ? "Lab-Grown" : "Natural"
56
+ } Diamond matches for ${
57
+ generateSavedSearchName(message.search_payload)?.title
58
+ } (showing first ${shownResults})`
48
59
  : null}
49
60
  </MessageText>
50
61
  {message.role === "assistant" &&