@bikdotai/bik-component-library 0.0.862 → 0.0.863-beta.1

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 (92) hide show
  1. package/dist/cjs/components/bik-copilot/BIKCopilot.js +2 -0
  2. package/dist/cjs/components/bik-copilot/BIKCopilot.js.map +1 -0
  3. package/dist/cjs/components/bik-copilot/ControlledCopilot.js +2 -0
  4. package/dist/cjs/components/bik-copilot/ControlledCopilot.js.map +1 -0
  5. package/dist/cjs/components/bik-copilot/ControlledCopilot.styled.js +27 -0
  6. package/dist/cjs/components/bik-copilot/ControlledCopilot.styled.js.map +1 -0
  7. package/dist/cjs/components/bik-copilot/CopilotBall.js +2 -0
  8. package/dist/cjs/components/bik-copilot/CopilotBall.js.map +1 -0
  9. package/dist/cjs/components/bik-copilot/CopilotBall.styled.js +42 -0
  10. package/dist/cjs/components/bik-copilot/CopilotBall.styled.js.map +1 -0
  11. package/dist/cjs/components/bik-copilot/CopilotPanel.js +2 -0
  12. package/dist/cjs/components/bik-copilot/CopilotPanel.js.map +1 -0
  13. package/dist/cjs/components/bik-copilot/CopilotPanel.styled.js +460 -0
  14. package/dist/cjs/components/bik-copilot/CopilotPanel.styled.js.map +1 -0
  15. package/dist/cjs/components/bik-copilot/copilotPlacement.js +2 -0
  16. package/dist/cjs/components/bik-copilot/copilotPlacement.js.map +1 -0
  17. package/dist/cjs/components/bik-copilot/services/copilotDb.js +2 -0
  18. package/dist/cjs/components/bik-copilot/services/copilotDb.js.map +1 -0
  19. package/dist/cjs/components/bik-copilot/services/copilotIcebreakersClient.js +2 -0
  20. package/dist/cjs/components/bik-copilot/services/copilotIcebreakersClient.js.map +1 -0
  21. package/dist/cjs/components/bik-copilot/services/copilotMessagesClient.js +2 -0
  22. package/dist/cjs/components/bik-copilot/services/copilotMessagesClient.js.map +1 -0
  23. package/dist/cjs/components/bik-copilot/services/copilotPageSession.js +2 -0
  24. package/dist/cjs/components/bik-copilot/services/copilotPageSession.js.map +1 -0
  25. package/dist/cjs/components/bik-copilot/services/copilotSseParser.js +7 -0
  26. package/dist/cjs/components/bik-copilot/services/copilotSseParser.js.map +1 -0
  27. package/dist/cjs/components/bik-copilot/services/copilotStreamClient.js +2 -0
  28. package/dist/cjs/components/bik-copilot/services/copilotStreamClient.js.map +1 -0
  29. package/dist/cjs/components/bik-copilot/useCopilotIcebreakers.js +2 -0
  30. package/dist/cjs/components/bik-copilot/useCopilotIcebreakers.js.map +1 -0
  31. package/dist/cjs/components/bik-copilot/useCopilotStreamChat.js +2 -0
  32. package/dist/cjs/components/bik-copilot/useCopilotStreamChat.js.map +1 -0
  33. package/dist/cjs/components/bik-copilot/useDraggableFloater.js +2 -0
  34. package/dist/cjs/components/bik-copilot/useDraggableFloater.js.map +1 -0
  35. package/dist/cjs/index.js +1 -1
  36. package/dist/esm/components/bik-copilot/BIKCopilot.d.ts +32 -0
  37. package/dist/esm/components/bik-copilot/BIKCopilot.js +114 -0
  38. package/dist/esm/components/bik-copilot/BIKCopilot.js.map +1 -0
  39. package/dist/esm/components/bik-copilot/ControlledCopilot.d.ts +14 -0
  40. package/dist/esm/components/bik-copilot/ControlledCopilot.js +170 -0
  41. package/dist/esm/components/bik-copilot/ControlledCopilot.js.map +1 -0
  42. package/dist/esm/components/bik-copilot/ControlledCopilot.styled.d.ts +7 -0
  43. package/dist/esm/components/bik-copilot/ControlledCopilot.styled.js +31 -0
  44. package/dist/esm/components/bik-copilot/ControlledCopilot.styled.js.map +1 -0
  45. package/dist/esm/components/bik-copilot/CopilotBall.d.ts +13 -0
  46. package/dist/esm/components/bik-copilot/CopilotBall.js +50 -0
  47. package/dist/esm/components/bik-copilot/CopilotBall.js.map +1 -0
  48. package/dist/esm/components/bik-copilot/CopilotBall.styled.d.ts +9 -0
  49. package/dist/esm/components/bik-copilot/CopilotBall.styled.js +46 -0
  50. package/dist/esm/components/bik-copilot/CopilotBall.styled.js.map +1 -0
  51. package/dist/esm/components/bik-copilot/CopilotPanel.d.ts +9 -0
  52. package/dist/esm/components/bik-copilot/CopilotPanel.js +379 -0
  53. package/dist/esm/components/bik-copilot/CopilotPanel.js.map +1 -0
  54. package/dist/esm/components/bik-copilot/CopilotPanel.styled.d.ts +64 -0
  55. package/dist/esm/components/bik-copilot/CopilotPanel.styled.js +496 -0
  56. package/dist/esm/components/bik-copilot/CopilotPanel.styled.js.map +1 -0
  57. package/dist/esm/components/bik-copilot/copilotPlacement.d.ts +24 -0
  58. package/dist/esm/components/bik-copilot/copilotPlacement.js +38 -0
  59. package/dist/esm/components/bik-copilot/copilotPlacement.js.map +1 -0
  60. package/dist/esm/components/bik-copilot/index.d.ts +16 -0
  61. package/dist/esm/components/bik-copilot/model.d.ts +674 -0
  62. package/dist/esm/components/bik-copilot/services/copilotDb.d.ts +90 -0
  63. package/dist/esm/components/bik-copilot/services/copilotDb.js +96 -0
  64. package/dist/esm/components/bik-copilot/services/copilotDb.js.map +1 -0
  65. package/dist/esm/components/bik-copilot/services/copilotIcebreakersClient.d.ts +11 -0
  66. package/dist/esm/components/bik-copilot/services/copilotIcebreakersClient.js +54 -0
  67. package/dist/esm/components/bik-copilot/services/copilotIcebreakersClient.js.map +1 -0
  68. package/dist/esm/components/bik-copilot/services/copilotMessagesClient.d.ts +23 -0
  69. package/dist/esm/components/bik-copilot/services/copilotMessagesClient.js +78 -0
  70. package/dist/esm/components/bik-copilot/services/copilotMessagesClient.js.map +1 -0
  71. package/dist/esm/components/bik-copilot/services/copilotPageSession.d.ts +37 -0
  72. package/dist/esm/components/bik-copilot/services/copilotPageSession.js +35 -0
  73. package/dist/esm/components/bik-copilot/services/copilotPageSession.js.map +1 -0
  74. package/dist/esm/components/bik-copilot/services/copilotSseParser.d.ts +22 -0
  75. package/dist/esm/components/bik-copilot/services/copilotSseParser.js +52 -0
  76. package/dist/esm/components/bik-copilot/services/copilotSseParser.js.map +1 -0
  77. package/dist/esm/components/bik-copilot/services/copilotStreamClient.d.ts +31 -0
  78. package/dist/esm/components/bik-copilot/services/copilotStreamClient.js +103 -0
  79. package/dist/esm/components/bik-copilot/services/copilotStreamClient.js.map +1 -0
  80. package/dist/esm/components/bik-copilot/useCopilotIcebreakers.d.ts +17 -0
  81. package/dist/esm/components/bik-copilot/useCopilotIcebreakers.js +60 -0
  82. package/dist/esm/components/bik-copilot/useCopilotIcebreakers.js.map +1 -0
  83. package/dist/esm/components/bik-copilot/useCopilotStreamChat.d.ts +20 -0
  84. package/dist/esm/components/bik-copilot/useCopilotStreamChat.js +333 -0
  85. package/dist/esm/components/bik-copilot/useCopilotStreamChat.js.map +1 -0
  86. package/dist/esm/components/bik-copilot/useDraggableFloater.d.ts +9 -0
  87. package/dist/esm/components/bik-copilot/useDraggableFloater.js +118 -0
  88. package/dist/esm/components/bik-copilot/useDraggableFloater.js.map +1 -0
  89. package/dist/esm/index.d.ts +1 -0
  90. package/dist/esm/index.js +832 -782
  91. package/dist/esm/index.js.map +1 -1
  92. package/package.json +1 -1
@@ -0,0 +1,90 @@
1
+ import { CopilotMessage, CopilotSessionRecord } from '../model';
2
+ /**
3
+ * How long a conversation outlives its last message.
4
+ *
5
+ * Enforced twice on purpose: `expiresAt` is stamped on every write so a reader
6
+ * can tell a stale record from a live one on its own, and `pruneCopilotSessions`
7
+ * sweeps the expired ones out. Without the stamp, a record would stay readable
8
+ * until a sweep happened to run.
9
+ */
10
+ export declare const COPILOT_SESSION_TTL_MS: number;
11
+ /**
12
+ * Newest messages kept per conversation. Older ones are dropped on write — the
13
+ * backend is the archive, and `/bik-ai/copilot-messages` serves them back a page
14
+ * at a time when the reader scrolls up.
15
+ *
16
+ * Deliberately equal to the endpoint's page size: the cache then holds exactly
17
+ * what page 1 would return, so the first scroll-up asks for page 2 with no gap
18
+ * and no duplicates.
19
+ */
20
+ export declare const MAX_PERSISTED_MESSAGES = 20;
21
+ /**
22
+ * Key of one conversation: `{sessionId}:{route}`.
23
+ *
24
+ * Both halves are the identity. The route is in it because a conversation
25
+ * belongs to the page it was started on — coming back to that page continues it,
26
+ * while a different page gets its own. The session id leads because it is what
27
+ * the backend keys `/bik-ai/copilot-messages` by, so a key reads as "this
28
+ * conversation, on this page".
29
+ *
30
+ * The separator is `:` rather than `_`, which routes themselves can contain.
31
+ */
32
+ export declare const copilotSessionKey: (route: string, sessionId: string) => string;
33
+ export declare const createCopilotSessionId: () => string;
34
+ export declare const createCopilotSession: (route: string, sessionId: string, seed?: Partial<CopilotSessionRecord>) => CopilotSessionRecord;
35
+ /**
36
+ * Whether a record has outlived its TTL.
37
+ *
38
+ * Records written before `expiresAt` existed fall back to their last write plus
39
+ * the TTL, so an upgrade does not resurrect conversations that should be gone.
40
+ */
41
+ export declare const isCopilotSessionExpired: (record: CopilotSessionRecord, now?: number) => boolean;
42
+ /**
43
+ * Reads one conversation back, treating an expired record as absent — and
44
+ * deleting it on the way past, so the sweep is not the only thing keeping the
45
+ * store from growing.
46
+ */
47
+ export declare const loadCopilotSession: (route: string, sessionId: string) => Promise<CopilotSessionRecord | null>;
48
+ export declare const saveCopilotSession: (record: CopilotSessionRecord) => Promise<void>;
49
+ export declare const deleteCopilotSession: (route: string, sessionId: string) => Promise<void>;
50
+ export declare const listCopilotSessionKeys: () => Promise<string[]>;
51
+ /**
52
+ * Drops conversations whose TTL has run out.
53
+ *
54
+ * Without this the store keeps every conversation the merchant ever started, on
55
+ * every route. Expiring by last write rather than by page load keeps a second
56
+ * tab's live conversations intact.
57
+ *
58
+ * `maxAgeMs` overrides the stamped TTL, for callers that want a shorter sweep.
59
+ */
60
+ export declare const pruneCopilotSessions: (maxAgeMs?: number) => Promise<void>;
61
+ /**
62
+ * The session id of the most recently used conversation on a route, or `null`
63
+ * when that route has none stored.
64
+ *
65
+ * This is what lets a refresh resume rather than start over: the ids themselves
66
+ * only live in memory, so after a reload the stored records are the only place
67
+ * left that knows which conversation the reader was in. Most recent by
68
+ * `updatedAt` wins, which also means a conversation the reader reset — written
69
+ * empty with a fresh stamp — is the one that comes back, not the one they
70
+ * cleared.
71
+ *
72
+ * Scoped by store as well as route: two stores on the same route are two
73
+ * conversations, and resuming the wrong one would show another store's history.
74
+ */
75
+ export declare const findLatestCopilotSessionId: ({ route, storeId, }: {
76
+ route: string;
77
+ storeId?: string | undefined;
78
+ }) => Promise<string | null>;
79
+ /**
80
+ * Persists one turn's worth of state. Called after every message and after each
81
+ * completed stream, so navigating away mid-conversation loses nothing.
82
+ */
83
+ export declare const persistCopilotTurn: ({ route, sessionId, messages, previousResponseId, storeId, agent, }: {
84
+ route: string;
85
+ sessionId: string;
86
+ messages: CopilotMessage[];
87
+ previousResponseId?: string | undefined;
88
+ storeId?: string | undefined;
89
+ agent?: string | undefined;
90
+ }) => Promise<void>;
@@ -0,0 +1,96 @@
1
+ import { createStore as C, get as p, del as d, set as I, keys as x } from "idb-keyval";
2
+ import { COPILOT_HISTORY_PAGE_SIZE as D } from "./copilotMessagesClient.js";
3
+ const i = C("bik-copilot", "sessions"), u = 1440 * 60 * 1e3, E = D, S = (t, o) => `${o}:${t}`, r = async (t, o) => {
4
+ try {
5
+ return await t();
6
+ } catch {
7
+ return o;
8
+ }
9
+ }, g = () => typeof crypto < "u" && typeof crypto.randomUUID == "function" ? crypto.randomUUID() : `copilot-${Date.now()}-${Math.floor(Math.random() * 1e6)}`, _ = (t, o, n = {}) => {
10
+ const e = Date.now();
11
+ return {
12
+ sessionId: o,
13
+ route: t,
14
+ createdAt: e,
15
+ updatedAt: e,
16
+ expiresAt: e + u,
17
+ messages: [],
18
+ ...n
19
+ };
20
+ }, f = (t, o = Date.now()) => (t.expiresAt ?? t.updatedAt + u) <= o, T = (t, o) => r(async () => {
21
+ const n = S(t, o), e = await p(n, i);
22
+ return e ? f(e) ? (await d(n, i), null) : e : null;
23
+ }, null), K = (t) => r(
24
+ () => I(
25
+ S(t.route ?? "", t.sessionId),
26
+ t,
27
+ i
28
+ ),
29
+ void 0
30
+ ), v = (t, o) => r(
31
+ () => d(S(t, o), i),
32
+ void 0
33
+ ), y = () => r(async () => (await x(i)).filter((o) => typeof o == "string"), []), $ = (t) => r(async () => {
34
+ const o = Date.now(), n = await y();
35
+ await Promise.all(
36
+ n.map(async (e) => {
37
+ const a = await p(e, i);
38
+ if (!a)
39
+ return;
40
+ (t === void 0 ? f(a, o) : a.updatedAt + t <= o) && await d(e, i);
41
+ })
42
+ );
43
+ }, void 0), h = ({
44
+ route: t,
45
+ storeId: o
46
+ }) => r(async () => {
47
+ const n = `:${t}`, e = await y();
48
+ return (await Promise.all(
49
+ e.filter((s) => s.endsWith(n)).map((s) => p(s, i))
50
+ )).filter((s) => !!s).filter(
51
+ (s) => !o || !s.storeId || s.storeId === o
52
+ ).filter((s) => !f(s)).sort((s, c) => c.updatedAt - s.updatedAt)[0]?.sessionId ?? null;
53
+ }, null), L = async ({
54
+ route: t,
55
+ sessionId: o,
56
+ messages: n,
57
+ previousResponseId: e,
58
+ storeId: a,
59
+ agent: l
60
+ }) => {
61
+ const s = await T(t, o), c = Date.now(), m = {
62
+ ...s ?? _(t, o),
63
+ sessionId: o,
64
+ route: t,
65
+ updatedAt: c,
66
+ // Restamped every write, so a conversation stays alive as long as it is
67
+ // being used and starts its 24h only from the last message.
68
+ expiresAt: c + u,
69
+ // Streaming placeholders must not be restored as half-finished bubbles.
70
+ // Only the newest page survives; anything older is the backend's to serve.
71
+ messages: n.slice(-E).map((A) => {
72
+ const w = { ...A };
73
+ return delete w.isStreaming, w;
74
+ }),
75
+ previousResponseId: e,
76
+ storeId: a,
77
+ agent: l
78
+ };
79
+ await K(m);
80
+ };
81
+ export {
82
+ u as COPILOT_SESSION_TTL_MS,
83
+ E as MAX_PERSISTED_MESSAGES,
84
+ S as copilotSessionKey,
85
+ _ as createCopilotSession,
86
+ g as createCopilotSessionId,
87
+ v as deleteCopilotSession,
88
+ h as findLatestCopilotSessionId,
89
+ f as isCopilotSessionExpired,
90
+ y as listCopilotSessionKeys,
91
+ T as loadCopilotSession,
92
+ L as persistCopilotTurn,
93
+ $ as pruneCopilotSessions,
94
+ K as saveCopilotSession
95
+ };
96
+ //# sourceMappingURL=copilotDb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copilotDb.js","sources":["../../../../../src/components/bik-copilot/services/copilotDb.ts"],"sourcesContent":["import { createStore, del, get, keys, set } from 'idb-keyval';\nimport { CopilotMessage, CopilotSessionRecord } from '../model';\nimport { COPILOT_HISTORY_PAGE_SIZE } from './copilotMessagesClient';\n\n/**\n * Dedicated IndexedDB store. The chatbot's `db.ts` writes `ai-session:` keys\n * into idb-keyval's default store; the copilot keeps its own database so the two\n * transcripts can never overwrite each other and this schema can evolve freely.\n */\nconst copilotStore = createStore('bik-copilot', 'sessions');\n\n/**\n * How long a conversation outlives its last message.\n *\n * Enforced twice on purpose: `expiresAt` is stamped on every write so a reader\n * can tell a stale record from a live one on its own, and `pruneCopilotSessions`\n * sweeps the expired ones out. Without the stamp, a record would stay readable\n * until a sweep happened to run.\n */\nexport const COPILOT_SESSION_TTL_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Newest messages kept per conversation. Older ones are dropped on write — the\n * backend is the archive, and `/bik-ai/copilot-messages` serves them back a page\n * at a time when the reader scrolls up.\n *\n * Deliberately equal to the endpoint's page size: the cache then holds exactly\n * what page 1 would return, so the first scroll-up asks for page 2 with no gap\n * and no duplicates.\n */\nexport const MAX_PERSISTED_MESSAGES = COPILOT_HISTORY_PAGE_SIZE;\n\n/**\n * Key of one conversation: `{sessionId}:{route}`.\n *\n * Both halves are the identity. The route is in it because a conversation\n * belongs to the page it was started on — coming back to that page continues it,\n * while a different page gets its own. The session id leads because it is what\n * the backend keys `/bik-ai/copilot-messages` by, so a key reads as \"this\n * conversation, on this page\".\n *\n * The separator is `:` rather than `_`, which routes themselves can contain.\n */\nexport const copilotSessionKey = (route: string, sessionId: string): string =>\n\t`${sessionId}:${route}`;\n\n/**\n * Every idb call is wrapped: IndexedDB is unavailable in SSR, in private-mode\n * Safari and when the browser evicts the database. Chat must keep working in\n * memory when persistence fails, so a rejection is swallowed and reported as\n * \"nothing stored\".\n */\nconst safely = async <T>(\n\toperation: () => Promise<T>,\n\tfallback: T,\n): Promise<T> => {\n\ttry {\n\t\treturn await operation();\n\t} catch {\n\t\treturn fallback;\n\t}\n};\n\nexport const createCopilotSessionId = (): string => {\n\tif (\n\t\ttypeof crypto !== 'undefined' &&\n\t\ttypeof crypto.randomUUID === 'function'\n\t) {\n\t\treturn crypto.randomUUID();\n\t}\n\treturn `copilot-${Date.now()}-${Math.floor(Math.random() * 1_000_000)}`;\n};\n\nexport const createCopilotSession = (\n\troute: string,\n\tsessionId: string,\n\tseed: Partial<CopilotSessionRecord> = {},\n): CopilotSessionRecord => {\n\tconst now = Date.now();\n\treturn {\n\t\tsessionId,\n\t\troute,\n\t\tcreatedAt: now,\n\t\tupdatedAt: now,\n\t\texpiresAt: now + COPILOT_SESSION_TTL_MS,\n\t\tmessages: [],\n\t\t...seed,\n\t};\n};\n\n/**\n * Whether a record has outlived its TTL.\n *\n * Records written before `expiresAt` existed fall back to their last write plus\n * the TTL, so an upgrade does not resurrect conversations that should be gone.\n */\nexport const isCopilotSessionExpired = (\n\trecord: CopilotSessionRecord,\n\tnow: number = Date.now(),\n): boolean =>\n\t(record.expiresAt ?? record.updatedAt + COPILOT_SESSION_TTL_MS) <= now;\n\n/**\n * Reads one conversation back, treating an expired record as absent — and\n * deleting it on the way past, so the sweep is not the only thing keeping the\n * store from growing.\n */\nexport const loadCopilotSession = (\n\troute: string,\n\tsessionId: string,\n): Promise<CopilotSessionRecord | null> =>\n\tsafely(async () => {\n\t\tconst key = copilotSessionKey(route, sessionId);\n\t\tconst record = await get<CopilotSessionRecord>(key, copilotStore);\n\t\tif (!record) {\n\t\t\treturn null;\n\t\t}\n\t\tif (isCopilotSessionExpired(record)) {\n\t\t\tawait del(key, copilotStore);\n\t\t\treturn null;\n\t\t}\n\t\treturn record;\n\t}, null);\n\nexport const saveCopilotSession = (\n\trecord: CopilotSessionRecord,\n): Promise<void> =>\n\tsafely(\n\t\t() =>\n\t\t\tset(\n\t\t\t\tcopilotSessionKey(record.route ?? '', record.sessionId),\n\t\t\t\trecord,\n\t\t\t\tcopilotStore,\n\t\t\t),\n\t\tundefined,\n\t);\n\nexport const deleteCopilotSession = (\n\troute: string,\n\tsessionId: string,\n): Promise<void> =>\n\tsafely(\n\t\t() => del(copilotSessionKey(route, sessionId), copilotStore),\n\t\tundefined,\n\t);\n\nexport const listCopilotSessionKeys = (): Promise<string[]> =>\n\tsafely(async () => {\n\t\tconst allKeys = await keys(copilotStore);\n\t\treturn allKeys.filter((key): key is string => typeof key === 'string');\n\t}, []);\n\n/**\n * Drops conversations whose TTL has run out.\n *\n * Without this the store keeps every conversation the merchant ever started, on\n * every route. Expiring by last write rather than by page load keeps a second\n * tab's live conversations intact.\n *\n * `maxAgeMs` overrides the stamped TTL, for callers that want a shorter sweep.\n */\nexport const pruneCopilotSessions = (maxAgeMs?: number): Promise<void> =>\n\tsafely(async () => {\n\t\tconst now = Date.now();\n\t\tconst allKeys = await listCopilotSessionKeys();\n\t\tawait Promise.all(\n\t\t\tallKeys.map(async (key) => {\n\t\t\t\tconst record = await get<CopilotSessionRecord>(key, copilotStore);\n\t\t\t\tif (!record) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst expired =\n\t\t\t\t\tmaxAgeMs === undefined\n\t\t\t\t\t\t? isCopilotSessionExpired(record, now)\n\t\t\t\t\t\t: record.updatedAt + maxAgeMs <= now;\n\t\t\t\tif (expired) {\n\t\t\t\t\tawait del(key, copilotStore);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t}, undefined);\n\n/**\n * The session id of the most recently used conversation on a route, or `null`\n * when that route has none stored.\n *\n * This is what lets a refresh resume rather than start over: the ids themselves\n * only live in memory, so after a reload the stored records are the only place\n * left that knows which conversation the reader was in. Most recent by\n * `updatedAt` wins, which also means a conversation the reader reset — written\n * empty with a fresh stamp — is the one that comes back, not the one they\n * cleared.\n *\n * Scoped by store as well as route: two stores on the same route are two\n * conversations, and resuming the wrong one would show another store's history.\n */\nexport const findLatestCopilotSessionId = ({\n\troute,\n\tstoreId,\n}: {\n\troute: string;\n\tstoreId?: string;\n}): Promise<string | null> =>\n\tsafely(async () => {\n\t\t// The route is the tail of `{sessionId}:{route}`.\n\t\tconst suffix = `:${route}`;\n\t\tconst allKeys = await listCopilotSessionKeys();\n\t\tconst candidates = await Promise.all(\n\t\t\tallKeys\n\t\t\t\t.filter((key) => key.endsWith(suffix))\n\t\t\t\t.map((key) => get<CopilotSessionRecord>(key, copilotStore)),\n\t\t);\n\n\t\tconst newest = candidates\n\t\t\t.filter((record): record is CopilotSessionRecord => Boolean(record))\n\t\t\t// A record written before storeId was tracked has none; only reject on a\n\t\t\t// mismatch, so those stay resumable.\n\t\t\t.filter(\n\t\t\t\t(record) => !storeId || !record.storeId || record.storeId === storeId,\n\t\t\t)\n\t\t\t// An expired conversation must not be resumed, or a reader coming back\n\t\t\t// after a day would land in a session the sweep is about to delete.\n\t\t\t.filter((record) => !isCopilotSessionExpired(record))\n\t\t\t.sort((left, right) => right.updatedAt - left.updatedAt)[0];\n\n\t\treturn newest?.sessionId ?? null;\n\t}, null);\n\n/**\n * Persists one turn's worth of state. Called after every message and after each\n * completed stream, so navigating away mid-conversation loses nothing.\n */\nexport const persistCopilotTurn = async ({\n\troute,\n\tsessionId,\n\tmessages,\n\tpreviousResponseId,\n\tstoreId,\n\tagent,\n}: {\n\troute: string;\n\tsessionId: string;\n\tmessages: CopilotMessage[];\n\tpreviousResponseId?: string;\n\tstoreId?: string;\n\tagent?: string;\n}): Promise<void> => {\n\tconst existing = await loadCopilotSession(route, sessionId);\n\tconst now = Date.now();\n\tconst record: CopilotSessionRecord = {\n\t\t...(existing ?? createCopilotSession(route, sessionId)),\n\t\tsessionId,\n\t\troute,\n\t\tupdatedAt: now,\n\t\t// Restamped every write, so a conversation stays alive as long as it is\n\t\t// being used and starts its 24h only from the last message.\n\t\texpiresAt: now + COPILOT_SESSION_TTL_MS,\n\t\t// Streaming placeholders must not be restored as half-finished bubbles.\n\t\t// Only the newest page survives; anything older is the backend's to serve.\n\t\tmessages: messages.slice(-MAX_PERSISTED_MESSAGES).map((message) => {\n\t\t\tconst stored: CopilotMessage = { ...message };\n\t\t\tdelete stored.isStreaming;\n\t\t\treturn stored;\n\t\t}),\n\t\tpreviousResponseId,\n\t\tstoreId,\n\t\tagent,\n\t};\n\tawait saveCopilotSession(record);\n};\n"],"names":["copilotStore","createStore","COPILOT_SESSION_TTL_MS","MAX_PERSISTED_MESSAGES","COPILOT_HISTORY_PAGE_SIZE","copilotSessionKey","route","sessionId","safely","operation","fallback","createCopilotSessionId","createCopilotSession","seed","now","isCopilotSessionExpired","record","loadCopilotSession","key","get","del","saveCopilotSession","set","deleteCopilotSession","listCopilotSessionKeys","keys","pruneCopilotSessions","maxAgeMs","allKeys","findLatestCopilotSessionId","storeId","suffix","left","right","persistCopilotTurn","messages","previousResponseId","agent","existing","message","stored"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { CopilotIcebreakersResult, FetchCopilotIcebreakersOptions } from '../model';
2
+ /** Default path of the ice-breaker endpoint. */
3
+ export declare const COPILOT_ICEBREAKERS_PATH = "/bik-ai/copilot-icebreakers";
4
+ /**
5
+ * Fetches the suggested opening prompts for a route.
6
+ *
7
+ * A route with no knowledge file behind it returns an empty list, and the
8
+ * backend is explicit that this is a success rather than a failure — so an empty
9
+ * result must render as "no chips", never as an error.
10
+ */
11
+ export declare const fetchCopilotIcebreakers: ({ baseUrl, endpointPath, headers, authToken, getAuthToken, fetchImpl, agent, route, storeId, signal, }: FetchCopilotIcebreakersOptions) => Promise<CopilotIcebreakersResult>;
@@ -0,0 +1,54 @@
1
+ const k = "/bik-ai/copilot-icebreakers", g = async ({
2
+ baseUrl: p,
3
+ endpointPath: l = k,
4
+ headers: d,
5
+ authToken: h,
6
+ getAuthToken: r,
7
+ fetchImpl: f,
8
+ agent: s,
9
+ route: i,
10
+ storeId: c,
11
+ signal: w
12
+ }) => {
13
+ const b = `${p.replace(/\/$/, "")}${l}`, n = r ? await r() : h, a = await (f ?? fetch)(b, {
14
+ method: "POST",
15
+ headers: {
16
+ "Content-Type": "application/json",
17
+ ...n ? { authorization: n } : {},
18
+ ...d
19
+ },
20
+ body: JSON.stringify({
21
+ agent: s,
22
+ route: i,
23
+ ...c ? { store_id: c } : {}
24
+ }),
25
+ signal: w
26
+ });
27
+ if (!a.ok) {
28
+ const t = await a.text().catch(() => "");
29
+ throw new Error(
30
+ `Ice-breaker request failed with ${a.status}${t ? `: ${t}` : ""}`
31
+ );
32
+ }
33
+ const o = await a.json();
34
+ if (o.success === !1)
35
+ throw new Error(
36
+ o.errors?.[0]?.message || "Could not resolve icebreakers"
37
+ );
38
+ const e = o.data ?? {};
39
+ return {
40
+ agent: e.agent ?? s,
41
+ // The backend returns the *normalized* route it matched, which can differ
42
+ // from the one requested.
43
+ route: e.route ?? i,
44
+ pageName: e.page_name ?? e.pageName ?? void 0,
45
+ icebreakers: (e.icebreakers ?? []).filter(
46
+ (t) => typeof t == "string" && t.trim() !== ""
47
+ )
48
+ };
49
+ };
50
+ export {
51
+ k as COPILOT_ICEBREAKERS_PATH,
52
+ g as fetchCopilotIcebreakers
53
+ };
54
+ //# sourceMappingURL=copilotIcebreakersClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copilotIcebreakersClient.js","sources":["../../../../../src/components/bik-copilot/services/copilotIcebreakersClient.ts"],"sourcesContent":["import {\n\tCopilotIcebreakersResult,\n\tFetchCopilotIcebreakersOptions,\n} from '../model';\n\n/** Default path of the ice-breaker endpoint. */\nexport const COPILOT_ICEBREAKERS_PATH = '/bik-ai/copilot-icebreakers';\n\n/** The `ApiResponse<T>` envelope every non-streaming BIK AI endpoint returns. */\ninterface ApiEnvelope<T> {\n\tsuccess?: boolean;\n\tdata?: T;\n\terrors?: { code?: string; message?: string }[];\n}\n\ninterface IcebreakersPayload {\n\tagent?: string;\n\troute?: string;\n\tpage_name?: string | null;\n\t/** Tolerated in case the endpoint switches to camelCase serialization. */\n\tpageName?: string | null;\n\ticebreakers?: string[];\n}\n\n/**\n * Fetches the suggested opening prompts for a route.\n *\n * A route with no knowledge file behind it returns an empty list, and the\n * backend is explicit that this is a success rather than a failure — so an empty\n * result must render as \"no chips\", never as an error.\n */\nexport const fetchCopilotIcebreakers = async ({\n\tbaseUrl,\n\tendpointPath = COPILOT_ICEBREAKERS_PATH,\n\theaders,\n\tauthToken,\n\tgetAuthToken,\n\tfetchImpl,\n\tagent,\n\troute,\n\tstoreId,\n\tsignal,\n}: FetchCopilotIcebreakersOptions): Promise<CopilotIcebreakersResult> => {\n\tconst url = `${baseUrl.replace(/\\/$/, '')}${endpointPath}`;\n\tconst token = getAuthToken ? await getAuthToken() : authToken;\n\tconst doFetch = fetchImpl ?? fetch;\n\n\tconst response = await doFetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: {\n\t\t\t'Content-Type': 'application/json',\n\t\t\t...(token ? { authorization: token } : {}),\n\t\t\t...headers,\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tagent,\n\t\t\troute,\n\t\t\t...(storeId ? { store_id: storeId } : {}),\n\t\t}),\n\t\tsignal,\n\t});\n\n\tif (!response.ok) {\n\t\tconst detail = await response.text().catch(() => '');\n\t\tthrow new Error(\n\t\t\t`Ice-breaker request failed with ${response.status}${\n\t\t\t\tdetail ? `: ${detail}` : ''\n\t\t\t}`,\n\t\t);\n\t}\n\n\tconst envelope = (await response.json()) as ApiEnvelope<IcebreakersPayload>;\n\tif (envelope.success === false) {\n\t\tthrow new Error(\n\t\t\tenvelope.errors?.[0]?.message || 'Could not resolve icebreakers',\n\t\t);\n\t}\n\n\tconst payload = envelope.data ?? {};\n\treturn {\n\t\tagent: payload.agent ?? agent,\n\t\t// The backend returns the *normalized* route it matched, which can differ\n\t\t// from the one requested.\n\t\troute: payload.route ?? route,\n\t\tpageName: payload.page_name ?? payload.pageName ?? undefined,\n\t\ticebreakers: (payload.icebreakers ?? []).filter(\n\t\t\t(text): text is string => typeof text === 'string' && text.trim() !== '',\n\t\t),\n\t};\n};\n"],"names":["COPILOT_ICEBREAKERS_PATH","fetchCopilotIcebreakers","baseUrl","endpointPath","headers","authToken","getAuthToken","fetchImpl","agent","route","storeId","signal","url","token","response","detail","envelope","payload","text"],"mappings":"AAMO,MAAMA,IAA2B,+BAyB3BC,IAA0B,OAAO;AAAA,EAC7C,SAAAC;AAAA,EACA,cAAAC,IAAeH;AAAA,EACf,SAAAI;AAAA,EACA,WAAAC;AAAA,EACA,cAAAC;AAAA,EACA,WAAAC;AAAA,EACA,OAAAC;AAAA,EACA,OAAAC;AAAA,EACA,SAAAC;AAAA,EACA,QAAAC;AACD,MAAyE;AACxE,QAAMC,IAAM,GAAGV,EAAQ,QAAQ,OAAO,EAAE,CAAC,GAAGC,CAAY,IAClDU,IAAQP,IAAe,MAAMA,EAAA,IAAiBD,GAG9CS,IAAW,OAFDP,KAAa,OAEEK,GAAK;AAAA,IACnC,QAAQ;AAAA,IACR,SAAS;AAAA,MACR,gBAAgB;AAAA,MAChB,GAAIC,IAAQ,EAAE,eAAeA,EAAA,IAAU,CAAA;AAAA,MACvC,GAAGT;AAAA,IAAA;AAAA,IAEJ,MAAM,KAAK,UAAU;AAAA,MACpB,OAAAI;AAAA,MACA,OAAAC;AAAA,MACA,GAAIC,IAAU,EAAE,UAAUA,MAAY,CAAA;AAAA,IAAC,CACvC;AAAA,IACD,QAAAC;AAAA,EAAA,CACA;AAED,MAAI,CAACG,EAAS,IAAI;AACjB,UAAMC,IAAS,MAAMD,EAAS,OAAO,MAAM,MAAM,EAAE;AACnD,UAAM,IAAI;AAAA,MACT,mCAAmCA,EAAS,MAAM,GACjDC,IAAS,KAAKA,CAAM,KAAK,EAC1B;AAAA,IAAA;AAAA,EAEF;AAEA,QAAMC,IAAY,MAAMF,EAAS,KAAA;AACjC,MAAIE,EAAS,YAAY;AACxB,UAAM,IAAI;AAAA,MACTA,EAAS,SAAS,CAAC,GAAG,WAAW;AAAA,IAAA;AAInC,QAAMC,IAAUD,EAAS,QAAQ,CAAA;AACjC,SAAO;AAAA,IACN,OAAOC,EAAQ,SAAST;AAAA;AAAA;AAAA,IAGxB,OAAOS,EAAQ,SAASR;AAAA,IACxB,UAAUQ,EAAQ,aAAaA,EAAQ,YAAY;AAAA,IACnD,cAAcA,EAAQ,eAAe,CAAA,GAAI;AAAA,MACxC,CAACC,MAAyB,OAAOA,KAAS,YAAYA,EAAK,WAAW;AAAA,IAAA;AAAA,EACvE;AAEF;"}
@@ -0,0 +1,23 @@
1
+ import { CopilotMessagesPage, FetchCopilotMessagesOptions } from '../model';
2
+ /** Default path of the stored-history endpoint. */
3
+ export declare const COPILOT_MESSAGES_PATH = "/bik-ai/copilot-messages";
4
+ /**
5
+ * How many messages one page holds — and, deliberately, the cap on what
6
+ * IndexedDB keeps.
7
+ *
8
+ * The two are the same number on purpose: the local cache holds exactly what
9
+ * page 1 would return, so the first scroll-up can ask for page 2 without an
10
+ * overlap or a gap. Changing one without the other would tear the transcript.
11
+ */
12
+ export declare const COPILOT_HISTORY_PAGE_SIZE = 20;
13
+ /**
14
+ * Reads one page of a conversation's stored history.
15
+ *
16
+ * Page 1 is the newest `pageSize` messages and page 2 the ones before that, so
17
+ * scrolling up walks the pages forward until `hasMore` goes false. Within a page
18
+ * the messages come back oldest-first regardless of the order the backend used,
19
+ * which is the order a transcript is rendered in.
20
+ *
21
+ * Rows with no text are dropped: a blank bubble is worse than a missing one.
22
+ */
23
+ export declare const fetchCopilotMessages: ({ baseUrl, endpointPath, headers, authToken, getAuthToken, fetchImpl, storeId, sessionId, page, pageSize, signal, }: FetchCopilotMessagesOptions) => Promise<CopilotMessagesPage>;
@@ -0,0 +1,78 @@
1
+ const O = "/bik-ai/copilot-messages", b = 20, y = (e) => e && /user|merchant|human|staff/i.test(e) ? "user" : "assistant", E = (e) => {
2
+ if (typeof e == "number")
3
+ return Number.isFinite(e) ? e : 0;
4
+ if (typeof e == "string" && e.trim().length > 0) {
5
+ const s = Number(e);
6
+ if (Number.isFinite(s))
7
+ return s;
8
+ const t = Date.parse(e);
9
+ if (Number.isFinite(t))
10
+ return t;
11
+ }
12
+ return 0;
13
+ }, $ = (e, s) => {
14
+ const t = e.message_id;
15
+ return {
16
+ // `message_id` is the server's own id, so a row cannot be prepended twice.
17
+ // The index fallback only matters for a row that arrives without one.
18
+ id: t ?? `copilot-history-${e.created_at ?? 0}-${s}`,
19
+ role: y(e.owner),
20
+ content: e.message ?? "",
21
+ timestamp: E(e.created_at),
22
+ ...t ? { messageId: t } : {}
23
+ };
24
+ }, w = async ({
25
+ baseUrl: e,
26
+ endpointPath: s = O,
27
+ headers: t,
28
+ authToken: h,
29
+ getAuthToken: i,
30
+ fetchImpl: g,
31
+ storeId: f,
32
+ sessionId: d,
33
+ page: c = 1,
34
+ pageSize: m = 20,
35
+ signal: u
36
+ }) => {
37
+ const _ = new URLSearchParams({
38
+ store_id: f,
39
+ session_id: d,
40
+ page: String(c),
41
+ page_size: String(m)
42
+ }), l = `${e.replace(
43
+ /\/$/,
44
+ ""
45
+ )}${s}?${_.toString()}`, p = i ? await i() : h, a = await (g ?? fetch)(l, {
46
+ method: "GET",
47
+ headers: {
48
+ "Content-Type": "application/json",
49
+ ...p ? { authorization: p } : {},
50
+ ...t
51
+ },
52
+ signal: u
53
+ });
54
+ if (!a.ok) {
55
+ const o = await a.text().catch(() => "");
56
+ throw new Error(
57
+ `Copilot history request failed with ${a.status}${o ? `: ${o}` : ""}`
58
+ );
59
+ }
60
+ const n = await a.json();
61
+ if (n.success === !1)
62
+ throw new Error(
63
+ n.errors?.[0]?.message || "Could not load the conversation history"
64
+ );
65
+ const r = n.data ?? {};
66
+ return {
67
+ messages: (r.messages ?? []).map($).filter((o) => o.content.length > 0).reverse().sort((o, S) => o.timestamp - S.timestamp),
68
+ page: r.page ?? c,
69
+ pageSize: r.page_size ?? r.pageSize ?? m,
70
+ hasMore: r.has_more ?? r.hasMore ?? !1
71
+ };
72
+ };
73
+ export {
74
+ b as COPILOT_HISTORY_PAGE_SIZE,
75
+ O as COPILOT_MESSAGES_PATH,
76
+ w as fetchCopilotMessages
77
+ };
78
+ //# sourceMappingURL=copilotMessagesClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copilotMessagesClient.js","sources":["../../../../../src/components/bik-copilot/services/copilotMessagesClient.ts"],"sourcesContent":["import {\n\tCopilotMessage,\n\tCopilotMessagesPage,\n\tCopilotStoredMessageRow,\n\tFetchCopilotMessagesOptions,\n} from '../model';\n\n/** Default path of the stored-history endpoint. */\nexport const COPILOT_MESSAGES_PATH = '/bik-ai/copilot-messages';\n\n/**\n * How many messages one page holds — and, deliberately, the cap on what\n * IndexedDB keeps.\n *\n * The two are the same number on purpose: the local cache holds exactly what\n * page 1 would return, so the first scroll-up can ask for page 2 without an\n * overlap or a gap. Changing one without the other would tear the transcript.\n */\nexport const COPILOT_HISTORY_PAGE_SIZE = 20;\n\n/** The `ApiResponse<T>` envelope every non-streaming BIK AI endpoint returns. */\ninterface ApiEnvelope<T> {\n\tsuccess?: boolean;\n\tdata?: T;\n\terrors?: { code?: string; message?: string }[];\n}\n\ninterface MessagesPayload {\n\tmessages?: CopilotStoredMessageRow[];\n\tpage?: number;\n\tpage_size?: number;\n\tpageSize?: number;\n\thas_more?: boolean;\n\thasMore?: boolean;\n}\n\n/**\n * Maps the backend's `owner` onto the transcript's `role`.\n *\n * Read as a pattern rather than an exact enum member so a new owner the backend\n * adds lands on a sensible side instead of throwing. Anything that is not\n * recognisably the merchant renders as the assistant, because a mislabelled\n * assistant bubble reads better than a merchant one attributed to the AI.\n */\nconst roleOfOwner = (owner: string | undefined): CopilotMessage['role'] => {\n\tif (owner && /user|merchant|human|staff/i.test(owner)) {\n\t\treturn 'user';\n\t}\n\treturn 'assistant';\n};\n\n/**\n * Coerces a row's `created_at` to epoch millis for ordering.\n *\n * The field is declared as millis, but real responses have arrived as numeric\n * strings and ISO timestamps. Reading all three here keeps the transcript\n * ordered instead of silently falling back to the response order — which is\n * newest-first and would render upside down.\n */\nconst toTimestamp = (value: number | string | undefined): number => {\n\tif (typeof value === 'number') {\n\t\treturn Number.isFinite(value) ? value : 0;\n\t}\n\tif (typeof value === 'string' && value.trim().length > 0) {\n\t\tconst asNumber = Number(value);\n\t\tif (Number.isFinite(asNumber)) {\n\t\t\treturn asNumber;\n\t\t}\n\t\tconst asDate = Date.parse(value);\n\t\tif (Number.isFinite(asDate)) {\n\t\t\treturn asDate;\n\t\t}\n\t}\n\treturn 0;\n};\n\nconst toMessage = (\n\trow: CopilotStoredMessageRow,\n\tindex: number,\n): CopilotMessage => {\n\tconst messageId = row.message_id;\n\treturn {\n\t\t// `message_id` is the server's own id, so a row cannot be prepended twice.\n\t\t// The index fallback only matters for a row that arrives without one.\n\t\tid: messageId ?? `copilot-history-${row.created_at ?? 0}-${index}`,\n\t\trole: roleOfOwner(row.owner),\n\t\tcontent: row.message ?? '',\n\t\ttimestamp: toTimestamp(row.created_at),\n\t\t...(messageId ? { messageId } : {}),\n\t};\n};\n\n/**\n * Reads one page of a conversation's stored history.\n *\n * Page 1 is the newest `pageSize` messages and page 2 the ones before that, so\n * scrolling up walks the pages forward until `hasMore` goes false. Within a page\n * the messages come back oldest-first regardless of the order the backend used,\n * which is the order a transcript is rendered in.\n *\n * Rows with no text are dropped: a blank bubble is worse than a missing one.\n */\nexport const fetchCopilotMessages = async ({\n\tbaseUrl,\n\tendpointPath = COPILOT_MESSAGES_PATH,\n\theaders,\n\tauthToken,\n\tgetAuthToken,\n\tfetchImpl,\n\tstoreId,\n\tsessionId,\n\tpage = 1,\n\tpageSize = COPILOT_HISTORY_PAGE_SIZE,\n\tsignal,\n}: FetchCopilotMessagesOptions): Promise<CopilotMessagesPage> => {\n\tconst query = new URLSearchParams({\n\t\tstore_id: storeId,\n\t\tsession_id: sessionId,\n\t\tpage: String(page),\n\t\tpage_size: String(pageSize),\n\t});\n\tconst url = `${baseUrl.replace(\n\t\t/\\/$/,\n\t\t'',\n\t)}${endpointPath}?${query.toString()}`;\n\tconst token = getAuthToken ? await getAuthToken() : authToken;\n\tconst doFetch = fetchImpl ?? fetch;\n\n\tconst response = await doFetch(url, {\n\t\tmethod: 'GET',\n\t\theaders: {\n\t\t\t'Content-Type': 'application/json',\n\t\t\t...(token ? { authorization: token } : {}),\n\t\t\t...headers,\n\t\t},\n\t\tsignal,\n\t});\n\n\tif (!response.ok) {\n\t\tconst detail = await response.text().catch(() => '');\n\t\tthrow new Error(\n\t\t\t`Copilot history request failed with ${response.status}${\n\t\t\t\tdetail ? `: ${detail}` : ''\n\t\t\t}`,\n\t\t);\n\t}\n\n\tconst envelope = (await response.json()) as ApiEnvelope<MessagesPayload>;\n\tif (envelope.success === false) {\n\t\tthrow new Error(\n\t\t\tenvelope.errors?.[0]?.message ||\n\t\t\t\t'Could not load the conversation history',\n\t\t);\n\t}\n\n\tconst payload = envelope.data ?? {};\n\tconst messages = (payload.messages ?? [])\n\t\t.map(toMessage)\n\t\t.filter((message) => message.content.length > 0)\n\t\t// Render order is oldest-first. The endpoint serves each page newest-first\n\t\t// (page 1 is the newest page), so the base order is reversed first; the\n\t\t// stable sort then corrects any within-page disorder when the rows carry a\n\t\t// usable timestamp, and is a no-op when they do not — leaving the reversed,\n\t\t// oldest-first order intact instead of echoing the newest-first response.\n\t\t.reverse()\n\t\t.sort((left, right) => left.timestamp - right.timestamp);\n\n\treturn {\n\t\tmessages,\n\t\tpage: payload.page ?? page,\n\t\tpageSize: payload.page_size ?? payload.pageSize ?? pageSize,\n\t\thasMore: payload.has_more ?? payload.hasMore ?? false,\n\t};\n};\n"],"names":["COPILOT_MESSAGES_PATH","COPILOT_HISTORY_PAGE_SIZE","roleOfOwner","owner","toTimestamp","value","asNumber","asDate","toMessage","row","index","messageId","fetchCopilotMessages","baseUrl","endpointPath","headers","authToken","getAuthToken","fetchImpl","storeId","sessionId","page","pageSize","signal","query","url","token","response","detail","envelope","payload","message","left","right"],"mappings":"AAQO,MAAMA,IAAwB,4BAUxBC,IAA4B,IA0BnCC,IAAc,CAACC,MAChBA,KAAS,6BAA6B,KAAKA,CAAK,IAC5C,SAED,aAWFC,IAAc,CAACC,MAA+C;AACnE,MAAI,OAAOA,KAAU;AACpB,WAAO,OAAO,SAASA,CAAK,IAAIA,IAAQ;AAEzC,MAAI,OAAOA,KAAU,YAAYA,EAAM,KAAA,EAAO,SAAS,GAAG;AACzD,UAAMC,IAAW,OAAOD,CAAK;AAC7B,QAAI,OAAO,SAASC,CAAQ;AAC3B,aAAOA;AAER,UAAMC,IAAS,KAAK,MAAMF,CAAK;AAC/B,QAAI,OAAO,SAASE,CAAM;AACzB,aAAOA;AAAA,EAET;AACA,SAAO;AACR,GAEMC,IAAY,CACjBC,GACAC,MACoB;AACpB,QAAMC,IAAYF,EAAI;AACtB,SAAO;AAAA;AAAA;AAAA,IAGN,IAAIE,KAAa,mBAAmBF,EAAI,cAAc,CAAC,IAAIC,CAAK;AAAA,IAChE,MAAMR,EAAYO,EAAI,KAAK;AAAA,IAC3B,SAASA,EAAI,WAAW;AAAA,IACxB,WAAWL,EAAYK,EAAI,UAAU;AAAA,IACrC,GAAIE,IAAY,EAAE,WAAAA,MAAc,CAAA;AAAA,EAAC;AAEnC,GAYaC,IAAuB,OAAO;AAAA,EAC1C,SAAAC;AAAA,EACA,cAAAC,IAAed;AAAA,EACf,SAAAe;AAAA,EACA,WAAAC;AAAA,EACA,cAAAC;AAAA,EACA,WAAAC;AAAA,EACA,SAAAC;AAAA,EACA,WAAAC;AAAA,EACA,MAAAC,IAAO;AAAA,EACP,UAAAC,IAAW;AAAA,EACX,QAAAC;AACD,MAAiE;AAChE,QAAMC,IAAQ,IAAI,gBAAgB;AAAA,IACjC,UAAUL;AAAA,IACV,YAAYC;AAAA,IACZ,MAAM,OAAOC,CAAI;AAAA,IACjB,WAAW,OAAOC,CAAQ;AAAA,EAAA,CAC1B,GACKG,IAAM,GAAGZ,EAAQ;AAAA,IACtB;AAAA,IACA;AAAA,EAAA,CACA,GAAGC,CAAY,IAAIU,EAAM,UAAU,IAC9BE,IAAQT,IAAe,MAAMA,EAAA,IAAiBD,GAG9CW,IAAW,OAFDT,KAAa,OAEEO,GAAK;AAAA,IACnC,QAAQ;AAAA,IACR,SAAS;AAAA,MACR,gBAAgB;AAAA,MAChB,GAAIC,IAAQ,EAAE,eAAeA,EAAA,IAAU,CAAA;AAAA,MACvC,GAAGX;AAAA,IAAA;AAAA,IAEJ,QAAAQ;AAAA,EAAA,CACA;AAED,MAAI,CAACI,EAAS,IAAI;AACjB,UAAMC,IAAS,MAAMD,EAAS,OAAO,MAAM,MAAM,EAAE;AACnD,UAAM,IAAI;AAAA,MACT,uCAAuCA,EAAS,MAAM,GACrDC,IAAS,KAAKA,CAAM,KAAK,EAC1B;AAAA,IAAA;AAAA,EAEF;AAEA,QAAMC,IAAY,MAAMF,EAAS,KAAA;AACjC,MAAIE,EAAS,YAAY;AACxB,UAAM,IAAI;AAAA,MACTA,EAAS,SAAS,CAAC,GAAG,WACrB;AAAA,IAAA;AAIH,QAAMC,IAAUD,EAAS,QAAQ,CAAA;AAYjC,SAAO;AAAA,IACN,WAZiBC,EAAQ,YAAY,CAAA,GACpC,IAAItB,CAAS,EACb,OAAO,CAACuB,MAAYA,EAAQ,QAAQ,SAAS,CAAC,EAM9C,QAAA,EACA,KAAK,CAACC,GAAMC,MAAUD,EAAK,YAAYC,EAAM,SAAS;AAAA,IAIvD,MAAMH,EAAQ,QAAQT;AAAA,IACtB,UAAUS,EAAQ,aAAaA,EAAQ,YAAYR;AAAA,IACnD,SAASQ,EAAQ,YAAYA,EAAQ,WAAW;AAAA,EAAA;AAElD;"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The session id for a route in this page load, created on first use and stable
3
+ * for the rest of the page load.
4
+ */
5
+ export declare const getCopilotSessionIdForRoute: ({ route, storeId, }: {
6
+ route: string;
7
+ storeId?: string | undefined;
8
+ }) => string;
9
+ /**
10
+ * The session id for a route, resuming the stored conversation when this page
11
+ * load has not seen the route yet.
12
+ *
13
+ * Order matters: the in-memory registry first, so a route already in play keeps
14
+ * its id without a database read; then the most recent stored conversation, which
15
+ * is how a refresh resumes; and only then a new id.
16
+ *
17
+ * Prefer this over `getCopilotSessionIdForRoute` anywhere persistence is on — the
18
+ * synchronous version cannot see the stored records, so it always starts fresh.
19
+ */
20
+ export declare const resolveCopilotSessionIdForRoute: ({ route, storeId, }: {
21
+ route: string;
22
+ storeId?: string | undefined;
23
+ }) => Promise<string>;
24
+ /**
25
+ * Starts a new conversation for one route, leaving other routes untouched. The
26
+ * new id sticks for the rest of the page load, so navigating away and back does
27
+ * not resurrect the conversation the user just cleared.
28
+ */
29
+ export declare const resetCopilotSessionForRoute: ({ route, storeId, }: {
30
+ route: string;
31
+ storeId?: string | undefined;
32
+ }) => string;
33
+ /**
34
+ * Forgets every session id, as a reload would. Call it on logout or when the
35
+ * signed-in user changes; tests use it to simulate a refresh.
36
+ */
37
+ export declare const clearCopilotPageSessions: () => void;
@@ -0,0 +1,35 @@
1
+ import { createCopilotSessionId as r, findLatestCopilotSessionId as d } from "./copilotDb.js";
2
+ const e = /* @__PURE__ */ new Map(), c = (s, o) => `${s ?? ""}:${o}`, p = ({
3
+ route: s,
4
+ storeId: o
5
+ }) => {
6
+ const t = c(o, s), n = e.get(t);
7
+ if (n)
8
+ return n;
9
+ const i = r();
10
+ return e.set(t, i), i;
11
+ }, u = async ({
12
+ route: s,
13
+ storeId: o
14
+ }) => {
15
+ const t = c(o, s), n = e.get(t);
16
+ if (n)
17
+ return n;
18
+ const a = await d({ route: s, storeId: o }) ?? r();
19
+ return e.set(t, a), a;
20
+ }, I = ({
21
+ route: s,
22
+ storeId: o
23
+ }) => {
24
+ const t = r();
25
+ return e.set(c(o, s), t), t;
26
+ }, S = () => {
27
+ e.clear();
28
+ };
29
+ export {
30
+ S as clearCopilotPageSessions,
31
+ p as getCopilotSessionIdForRoute,
32
+ I as resetCopilotSessionForRoute,
33
+ u as resolveCopilotSessionIdForRoute
34
+ };
35
+ //# sourceMappingURL=copilotPageSession.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copilotPageSession.js","sources":["../../../../../src/components/bik-copilot/services/copilotPageSession.ts"],"sourcesContent":["import {\n\tcreateCopilotSessionId,\n\tfindLatestCopilotSessionId,\n} from './copilotDb';\n\n/**\n * Registry of the session ids in play for the current page load.\n *\n * Module-level and in-memory, so **navigating away and back to the same route**\n * keeps the id and the copilot picks the conversation up where it was left.\n * Component state could not do this — the copilot unmounts on the way out.\n *\n * A **refresh** empties the registry, so the id has to be recovered from the\n * stored records instead: see `resolveCopilotSessionIdForRoute`, which reads the\n * route's most recent conversation out of IndexedDB before minting a new one.\n * That is what makes history survive a reload, and it is why the id must not be\n * regenerated per page load any more.\n *\n * The transcripts themselves live in IndexedDB under `{sessionId}:{route}`; this\n * registry only decides which of those keys the copilot reads.\n */\nconst routeSessionIds = new Map<string, string>();\n\n/**\n * Registry key.\n *\n * The store is part of it, and each entry gets its own session id rather than a\n * shared per-page one — otherwise two stores on the same route would resolve to\n * the same `{sessionId}:{route}` record and show each other's conversation.\n */\nconst registryKey = (storeId: string | undefined, route: string) =>\n\t`${storeId ?? ''}:${route}`;\n\n/**\n * The session id for a route in this page load, created on first use and stable\n * for the rest of the page load.\n */\nexport const getCopilotSessionIdForRoute = ({\n\troute,\n\tstoreId,\n}: {\n\troute: string;\n\tstoreId?: string;\n}): string => {\n\tconst key = registryKey(storeId, route);\n\tconst existing = routeSessionIds.get(key);\n\tif (existing) {\n\t\treturn existing;\n\t}\n\tconst sessionId = createCopilotSessionId();\n\trouteSessionIds.set(key, sessionId);\n\treturn sessionId;\n};\n\n/**\n * The session id for a route, resuming the stored conversation when this page\n * load has not seen the route yet.\n *\n * Order matters: the in-memory registry first, so a route already in play keeps\n * its id without a database read; then the most recent stored conversation, which\n * is how a refresh resumes; and only then a new id.\n *\n * Prefer this over `getCopilotSessionIdForRoute` anywhere persistence is on — the\n * synchronous version cannot see the stored records, so it always starts fresh.\n */\nexport const resolveCopilotSessionIdForRoute = async ({\n\troute,\n\tstoreId,\n}: {\n\troute: string;\n\tstoreId?: string;\n}): Promise<string> => {\n\tconst key = registryKey(storeId, route);\n\tconst inPlay = routeSessionIds.get(key);\n\tif (inPlay) {\n\t\treturn inPlay;\n\t}\n\n\tconst stored = await findLatestCopilotSessionId({ route, storeId });\n\tconst sessionId = stored ?? createCopilotSessionId();\n\t// Registered either way, so a second mount in this page load skips the read.\n\trouteSessionIds.set(key, sessionId);\n\treturn sessionId;\n};\n\n/**\n * Starts a new conversation for one route, leaving other routes untouched. The\n * new id sticks for the rest of the page load, so navigating away and back does\n * not resurrect the conversation the user just cleared.\n */\nexport const resetCopilotSessionForRoute = ({\n\troute,\n\tstoreId,\n}: {\n\troute: string;\n\tstoreId?: string;\n}): string => {\n\tconst sessionId = createCopilotSessionId();\n\trouteSessionIds.set(registryKey(storeId, route), sessionId);\n\treturn sessionId;\n};\n\n/**\n * Forgets every session id, as a reload would. Call it on logout or when the\n * signed-in user changes; tests use it to simulate a refresh.\n */\nexport const clearCopilotPageSessions = (): void => {\n\trouteSessionIds.clear();\n};\n"],"names":["routeSessionIds","registryKey","storeId","route","getCopilotSessionIdForRoute","key","existing","sessionId","createCopilotSessionId","resolveCopilotSessionIdForRoute","inPlay","findLatestCopilotSessionId","resetCopilotSessionForRoute","clearCopilotPageSessions"],"mappings":";AAqBA,MAAMA,wBAAsB,IAAA,GAStBC,IAAc,CAACC,GAA6BC,MACjD,GAAGD,KAAW,EAAE,IAAIC,CAAK,IAMbC,IAA8B,CAAC;AAAA,EAC3C,OAAAD;AAAA,EACA,SAAAD;AACD,MAGc;AACb,QAAMG,IAAMJ,EAAYC,GAASC,CAAK,GAChCG,IAAWN,EAAgB,IAAIK,CAAG;AACxC,MAAIC;AACH,WAAOA;AAER,QAAMC,IAAYC,EAAA;AAClB,SAAAR,EAAgB,IAAIK,GAAKE,CAAS,GAC3BA;AACR,GAaaE,IAAkC,OAAO;AAAA,EACrD,OAAAN;AAAA,EACA,SAAAD;AACD,MAGuB;AACtB,QAAMG,IAAMJ,EAAYC,GAASC,CAAK,GAChCO,IAASV,EAAgB,IAAIK,CAAG;AACtC,MAAIK;AACH,WAAOA;AAIR,QAAMH,IADS,MAAMI,EAA2B,EAAE,OAAAR,GAAO,SAAAD,GAAS,KACtCM,EAAA;AAE5B,SAAAR,EAAgB,IAAIK,GAAKE,CAAS,GAC3BA;AACR,GAOaK,IAA8B,CAAC;AAAA,EAC3C,OAAAT;AAAA,EACA,SAAAD;AACD,MAGc;AACb,QAAMK,IAAYC,EAAA;AAClB,SAAAR,EAAgB,IAAIC,EAAYC,GAASC,CAAK,GAAGI,CAAS,GACnDA;AACR,GAMaM,IAA2B,MAAY;AACnD,EAAAb,EAAgB,MAAA;AACjB;"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Incremental parser for the SSE frames emitted by `/bik-ai/copilot-chatbot`.
3
+ *
4
+ * The endpoint writes `data: {json}\n\n` frames and never sets an `event:` name
5
+ * — the discriminator lives in the JSON payload's `type`. A chunk off the
6
+ * network can split a frame anywhere, so the parser keeps a buffer between
7
+ * calls and only yields frames it has seen terminated.
8
+ */
9
+ export declare class CopilotSseParser {
10
+ private buffer;
11
+ /**
12
+ * Feeds one network chunk in and returns every complete `data:` payload it
13
+ * completed, as raw strings.
14
+ */
15
+ push(chunk: string): string[];
16
+ /**
17
+ * Returns any trailing frame left in the buffer when the stream ends without
18
+ * a final blank line, then empties the buffer.
19
+ */
20
+ flush(): string[];
21
+ private findBoundary;
22
+ }
@@ -0,0 +1,52 @@
1
+ class f {
2
+ buffer = "";
3
+ /**
4
+ * Feeds one network chunk in and returns every complete `data:` payload it
5
+ * completed, as raw strings.
6
+ */
7
+ push(t) {
8
+ this.buffer += t;
9
+ const n = [];
10
+ let e = this.findBoundary();
11
+ for (; e; ) {
12
+ const r = this.buffer.slice(0, e.index);
13
+ this.buffer = this.buffer.slice(e.index + e.length);
14
+ const i = l(r);
15
+ i !== null && n.push(i), e = this.findBoundary();
16
+ }
17
+ return n;
18
+ }
19
+ /**
20
+ * Returns any trailing frame left in the buffer when the stream ends without
21
+ * a final blank line, then empties the buffer.
22
+ */
23
+ flush() {
24
+ const t = this.buffer;
25
+ this.buffer = "";
26
+ const n = l(t);
27
+ return n === null ? [] : [n];
28
+ }
29
+ findBoundary() {
30
+ const t = this.buffer.indexOf(`
31
+
32
+ `), n = this.buffer.indexOf(`\r
33
+ \r
34
+ `);
35
+ return n !== -1 && (t === -1 || n < t) ? { index: n, length: 4 } : t !== -1 ? { index: t, length: 2 } : null;
36
+ }
37
+ }
38
+ const l = (s) => {
39
+ const t = s.split(/\r?\n/).filter((e) => e.startsWith("data:")).map((e) => {
40
+ const r = e.slice(5);
41
+ return r.startsWith(" ") ? r.slice(1) : r;
42
+ });
43
+ if (t.length === 0)
44
+ return null;
45
+ const n = t.join(`
46
+ `).trim();
47
+ return n.length === 0 ? null : n;
48
+ };
49
+ export {
50
+ f as CopilotSseParser
51
+ };
52
+ //# sourceMappingURL=copilotSseParser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copilotSseParser.js","sources":["../../../../../src/components/bik-copilot/services/copilotSseParser.ts"],"sourcesContent":["/**\n * Incremental parser for the SSE frames emitted by `/bik-ai/copilot-chatbot`.\n *\n * The endpoint writes `data: {json}\\n\\n` frames and never sets an `event:` name\n * — the discriminator lives in the JSON payload's `type`. A chunk off the\n * network can split a frame anywhere, so the parser keeps a buffer between\n * calls and only yields frames it has seen terminated.\n */\nexport class CopilotSseParser {\n\tprivate buffer = '';\n\n\t/**\n\t * Feeds one network chunk in and returns every complete `data:` payload it\n\t * completed, as raw strings.\n\t */\n\tpush(chunk: string): string[] {\n\t\tthis.buffer += chunk;\n\t\tconst payloads: string[] = [];\n\n\t\t// Frames are separated by a blank line. \\r\\n\\r\\n is tolerated because\n\t\t// proxies rewrite line endings.\n\t\tlet boundary = this.findBoundary();\n\t\twhile (boundary) {\n\t\t\tconst frame = this.buffer.slice(0, boundary.index);\n\t\t\tthis.buffer = this.buffer.slice(boundary.index + boundary.length);\n\t\t\tconst payload = extractDataPayload(frame);\n\t\t\tif (payload !== null) {\n\t\t\t\tpayloads.push(payload);\n\t\t\t}\n\t\t\tboundary = this.findBoundary();\n\t\t}\n\n\t\treturn payloads;\n\t}\n\n\t/**\n\t * Returns any trailing frame left in the buffer when the stream ends without\n\t * a final blank line, then empties the buffer.\n\t */\n\tflush(): string[] {\n\t\tconst remaining = this.buffer;\n\t\tthis.buffer = '';\n\t\tconst payload = extractDataPayload(remaining);\n\t\treturn payload === null ? [] : [payload];\n\t}\n\n\tprivate findBoundary(): { index: number; length: number } | null {\n\t\tconst lf = this.buffer.indexOf('\\n\\n');\n\t\tconst crlf = this.buffer.indexOf('\\r\\n\\r\\n');\n\t\tif (crlf !== -1 && (lf === -1 || crlf < lf)) {\n\t\t\treturn { index: crlf, length: 4 };\n\t\t}\n\t\tif (lf !== -1) {\n\t\t\treturn { index: lf, length: 2 };\n\t\t}\n\t\treturn null;\n\t}\n}\n\n/**\n * Pulls the payload out of one SSE frame, joining multi-line `data:` fields the\n * way the SSE spec requires. Returns `null` for comment-only or empty frames.\n */\nconst extractDataPayload = (frame: string): string | null => {\n\tconst dataLines = frame\n\t\t.split(/\\r?\\n/)\n\t\t.filter((line) => line.startsWith('data:'))\n\t\t.map((line) => {\n\t\t\tconst value = line.slice('data:'.length);\n\t\t\t// A single leading space after the colon is part of the framing.\n\t\t\treturn value.startsWith(' ') ? value.slice(1) : value;\n\t\t});\n\n\tif (dataLines.length === 0) {\n\t\treturn null;\n\t}\n\tconst payload = dataLines.join('\\n').trim();\n\treturn payload.length === 0 ? null : payload;\n};\n"],"names":["CopilotSseParser","chunk","payloads","boundary","frame","payload","extractDataPayload","remaining","lf","crlf","dataLines","line","value"],"mappings":"AAQO,MAAMA,EAAiB;AAAA,EACrB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,EAMjB,KAAKC,GAAyB;AAC7B,SAAK,UAAUA;AACf,UAAMC,IAAqB,CAAA;AAI3B,QAAIC,IAAW,KAAK,aAAA;AACpB,WAAOA,KAAU;AAChB,YAAMC,IAAQ,KAAK,OAAO,MAAM,GAAGD,EAAS,KAAK;AACjD,WAAK,SAAS,KAAK,OAAO,MAAMA,EAAS,QAAQA,EAAS,MAAM;AAChE,YAAME,IAAUC,EAAmBF,CAAK;AACxC,MAAIC,MAAY,QACfH,EAAS,KAAKG,CAAO,GAEtBF,IAAW,KAAK,aAAA;AAAA,IACjB;AAEA,WAAOD;AAAA,EACR;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAkB;AACjB,UAAMK,IAAY,KAAK;AACvB,SAAK,SAAS;AACd,UAAMF,IAAUC,EAAmBC,CAAS;AAC5C,WAAOF,MAAY,OAAO,CAAA,IAAK,CAACA,CAAO;AAAA,EACxC;AAAA,EAEQ,eAAyD;AAChE,UAAMG,IAAK,KAAK,OAAO,QAAQ;AAAA;AAAA,CAAM,GAC/BC,IAAO,KAAK,OAAO,QAAQ;AAAA;AAAA,CAAU;AAC3C,WAAIA,MAAS,OAAOD,MAAO,MAAMC,IAAOD,KAChC,EAAE,OAAOC,GAAM,QAAQ,EAAA,IAE3BD,MAAO,KACH,EAAE,OAAOA,GAAI,QAAQ,EAAA,IAEtB;AAAA,EACR;AACD;AAMA,MAAMF,IAAqB,CAACF,MAAiC;AAC5D,QAAMM,IAAYN,EAChB,MAAM,OAAO,EACb,OAAO,CAACO,MAASA,EAAK,WAAW,OAAO,CAAC,EACzC,IAAI,CAACA,MAAS;AACd,UAAMC,IAAQD,EAAK,MAAM,CAAc;AAEvC,WAAOC,EAAM,WAAW,GAAG,IAAIA,EAAM,MAAM,CAAC,IAAIA;AAAA,EACjD,CAAC;AAEF,MAAIF,EAAU,WAAW;AACxB,WAAO;AAER,QAAML,IAAUK,EAAU,KAAK;AAAA,CAAI,EAAE,KAAA;AACrC,SAAOL,EAAQ,WAAW,IAAI,OAAOA;AACtC;"}
@@ -0,0 +1,31 @@
1
+ import { CopilotStreamEvent, CopilotStreamHandlers, StreamCopilotChatOptions } from '../model';
2
+ /** Default path of the streaming copilot endpoint. */
3
+ export declare const COPILOT_ENDPOINT_PATH = "/bik-ai/copilot-chatbot";
4
+ /**
5
+ * Reads an event's `content`, which the backend sends two ways: a bare string on
6
+ * `STREAM` and `ERROR`, and an `{ type, message, followUps }` payload on `TEXT`.
7
+ *
8
+ * Anything unrecognised reads as empty text rather than throwing, so a shape the
9
+ * backend adds later cannot break a turn that is otherwise fine.
10
+ */
11
+ export declare const readCopilotEventContent: (event: CopilotStreamEvent) => {
12
+ text: string;
13
+ followUps?: string[] | undefined;
14
+ };
15
+ /**
16
+ * Opens one streaming copilot turn and drives the handlers until the server
17
+ * closes the stream.
18
+ *
19
+ * The endpoint's `STOP` never reaches the client as a frame — the server breaks
20
+ * out of its generator, which ends the HTTP stream. So "the reader is done" is
21
+ * what completion means here, and `onComplete` fires from there.
22
+ *
23
+ * `TEXT` carries the **whole** message, so it is authoritative: a caller should
24
+ * replace the text accumulated from `STREAM` chunks with it rather than append.
25
+ * When a turn fails halfway the server still sends `TEXT` with the partial
26
+ * answer but **without** `responseId` — that intentionally drops the thread, so
27
+ * the next turn must start fresh.
28
+ *
29
+ * @returns a function that aborts the in-flight stream.
30
+ */
31
+ export declare const streamCopilotChat: ({ baseUrl, endpointPath, headers, authToken, getAuthToken, fetchImpl, ...request }: StreamCopilotChatOptions, handlers: CopilotStreamHandlers) => (() => void);