@bikdotai/bik-component-library 0.0.876 → 0.0.877-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("idb-keyval"),_=require("./copilotMessagesClient.js"),a=i.createStore("bik-copilot","sessions"),p=1440*60*1e3,f=_.COPILOT_HISTORY_PAGE_SIZE,d=(t,e)=>`${e}:${t}`,r=async(t,e)=>{try{return await t()}catch{return e}},m=()=>typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():`copilot-${Date.now()}-${Math.floor(Math.random()*1e6)}`,w=(t,e,n={})=>{const o=Date.now();return{sessionId:e,route:t,createdAt:o,updatedAt:o,expiresAt:o+p,messages:[],...n}},S=(t,e=Date.now())=>(t.expiresAt??t.updatedAt+p)<=e,I=(t,e)=>r(async()=>{const n=d(t,e),o=await i.get(n,a);return o?S(o)?(await i.del(n,a),null):o:null},null),A=t=>r(()=>i.set(d(t.route??"",t.sessionId),t,a),void 0),g=(t,e)=>r(()=>i.del(d(t,e),a),void 0),y=()=>r(async()=>(await i.keys(a)).filter(e=>typeof e=="string"),[]),O=t=>r(async()=>{const e=Date.now(),n=await y();await Promise.all(n.map(async o=>{const l=await i.get(o,a);if(!l)return;(t===void 0?S(l,e):l.updatedAt+t<=e)&&await i.del(o,a)}))},void 0),D=({route:t,storeId:e})=>r(async()=>{const n=`:${t}`,o=await y();return(await Promise.all(o.filter(s=>s.endsWith(n)).map(s=>i.get(s,a)))).filter(s=>!!s).filter(s=>!e||!s.storeId||s.storeId===e).filter(s=>!S(s)).sort((s,c)=>c.updatedAt-s.updatedAt)[0]?.sessionId??null},null),K=async({route:t,sessionId:e,messages:n,previousResponseId:o,storeId:l,agent:u})=>{const s=await I(t,e),c=Date.now(),E={...s??w(t,e),sessionId:e,route:t,updatedAt:c,expiresAt:c+p,messages:n.slice(-f).map(T=>{const C={...T};return delete C.isStreaming,C}),previousResponseId:o,storeId:l,agent:u};await A(E)};exports.COPILOT_SESSION_TTL_MS=p;exports.MAX_PERSISTED_MESSAGES=f;exports.copilotSessionKey=d;exports.createCopilotSession=w;exports.createCopilotSessionId=m;exports.deleteCopilotSession=g;exports.findLatestCopilotSessionId=D;exports.isCopilotSessionExpired=S;exports.listCopilotSessionKeys=y;exports.loadCopilotSession=I;exports.persistCopilotTurn=K;exports.pruneCopilotSessions=O;exports.saveCopilotSession=A;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("idb-keyval"),_=require("./copilotMessagesClient.js"),a=i.createStore("bik-copilot","sessions"),c=1440*60*1e3,C=_.COPILOT_HISTORY_PAGE_SIZE,p=t=>t,l=async(t,e)=>{try{return await t()}catch{return e}},m=()=>typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():`copilot-${Date.now()}-${Math.floor(Math.random()*1e6)}`,w=(t,e,s={})=>{const n=Date.now();return{sessionId:e,route:t,createdAt:n,updatedAt:n,expiresAt:n+c,messages:[],...s}},d=(t,e=Date.now())=>(t.expiresAt??t.updatedAt+c)<=e,f=t=>l(async()=>{const e=p(t),s=await i.get(e,a);return s?d(s)?(await i.del(e,a),null):s:null},null),I=t=>l(()=>i.set(p(t.sessionId),t,a),void 0),g=t=>l(()=>i.del(p(t),a),void 0),S=()=>l(async()=>(await i.keys(a)).filter(e=>typeof e=="string"),[]),O=t=>l(async()=>{const e=Date.now(),s=await S();await Promise.all(s.map(async n=>{const o=await i.get(n,a);if(!o)return;(t===void 0?d(o,e):o.updatedAt+t<=e)&&await i.del(n,a)}))},void 0),D=({storeId:t})=>l(async()=>{const e=await S();return(await Promise.all(e.map(o=>i.get(o,a)))).filter(o=>!!o).filter(o=>!t||!o.storeId||o.storeId===t).filter(o=>!d(o)).sort((o,r)=>r.updatedAt-o.updatedAt)[0]?.sessionId??null},null),K=async({route:t,sessionId:e,messages:s,previousResponseId:n,storeId:o,agent:r})=>{const A=await f(e),u=Date.now(),E={...A??w(t,e),sessionId:e,route:t,updatedAt:u,expiresAt:u+c,messages:s.slice(-C).map(T=>{const y={...T};return delete y.isStreaming,y}),previousResponseId:n,storeId:o,agent:r};await I(E)};exports.COPILOT_SESSION_TTL_MS=c;exports.MAX_PERSISTED_MESSAGES=C;exports.copilotSessionKey=p;exports.createCopilotSession=w;exports.createCopilotSessionId=m;exports.deleteCopilotSession=g;exports.findLatestCopilotSessionId=D;exports.isCopilotSessionExpired=d;exports.listCopilotSessionKeys=S;exports.loadCopilotSession=f;exports.persistCopilotTurn=K;exports.pruneCopilotSessions=O;exports.saveCopilotSession=I;
2
2
  //# sourceMappingURL=copilotDb.js.map
@@ -1 +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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"}
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: the session id alone.\n *\n * The id is already unique per conversation (`createCopilotSessionId`), so it is\n * sufficient as a storage key on its own. The record still carries a `route` —\n * the page the conversation was last used on — but nothing keys off it: a\n * conversation follows the merchant across routes.\n */\nexport const copilotSessionKey = (sessionId: string): string => sessionId;\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\tsessionId: string,\n): Promise<CopilotSessionRecord | null> =>\n\tsafely(async () => {\n\t\tconst key = copilotSessionKey(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() => set(copilotSessionKey(record.sessionId), record, copilotStore),\n\t\tundefined,\n\t);\n\nexport const deleteCopilotSession = (sessionId: string): Promise<void> =>\n\tsafely(() => del(copilotSessionKey(sessionId), copilotStore), undefined);\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 store's most recently used conversation, or `null` when\n * it 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 * Deliberately not filtered by route: one conversation follows the merchant\n * across the dashboard, so a reload on a different page than the one it started\n * on still resumes it. Still scoped by store — two stores are two conversations,\n * and resuming the wrong one would show another store's history.\n */\nexport const findLatestCopilotSessionId = ({\n\tstoreId,\n}: {\n\tstoreId?: string;\n}): Promise<string | null> =>\n\tsafely(async () => {\n\t\t// The key encodes only the session id, so every record has to be read to\n\t\t// find the store's.\n\t\tconst allKeys = await listCopilotSessionKeys();\n\t\tconst candidates = await Promise.all(\n\t\t\tallKeys.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(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","sessionId","safely","operation","fallback","createCopilotSessionId","createCopilotSession","route","seed","now","isCopilotSessionExpired","record","loadCopilotSession","key","get","del","saveCopilotSession","set","deleteCopilotSession","listCopilotSessionKeys","keys","pruneCopilotSessions","maxAgeMs","allKeys","findLatestCopilotSessionId","storeId","left","right","persistCopilotTurn","messages","previousResponseId","agent","existing","message","stored"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("./copilotDb.js"),t=new Map,c=(o,e)=>`${o??""}:${e}`,a=({route:o,storeId:e})=>{const s=c(e,o),n=t.get(s);if(n)return n;const r=i.createCopilotSessionId();return t.set(s,r),r},d=async({route:o,storeId:e})=>{const s=c(e,o),n=t.get(s);if(n)return n;const l=await i.findLatestCopilotSessionId({route:o,storeId:e})??i.createCopilotSessionId();return t.set(s,l),l},p=({route:o,storeId:e})=>{const s=i.createCopilotSessionId();return t.set(c(e,o),s),s},u=()=>{t.clear()};exports.clearCopilotPageSessions=u;exports.getCopilotSessionIdForRoute=a;exports.resetCopilotSessionForRoute=p;exports.resolveCopilotSessionIdForRoute=d;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("./copilotDb.js"),o=new Map,r=s=>s??"",l=({storeId:s})=>{const e=r(s),t=o.get(e);if(t)return t;const i=n.createCopilotSessionId();return o.set(e,i),i},a=async({storeId:s})=>{const e=r(s),t=o.get(e);if(t)return t;const c=await n.findLatestCopilotSessionId({storeId:s})??n.createCopilotSessionId();return o.set(e,c),c},d=({storeId:s})=>{const e=n.createCopilotSessionId();return o.set(r(s),e),e},p=()=>{o.clear()};exports.clearCopilotPageSessions=p;exports.getCopilotSessionId=l;exports.resetCopilotSession=d;exports.resolveCopilotSessionId=a;
2
2
  //# sourceMappingURL=copilotPageSession.js.map
@@ -1 +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":"kHAqBMA,MAAsB,IAStBC,EAAc,CAACC,EAA6BC,IACjD,GAAGD,GAAW,EAAE,IAAIC,CAAK,GAMbC,EAA8B,CAAC,CAC3C,MAAAD,EACA,QAAAD,CACD,IAGc,CACb,MAAMG,EAAMJ,EAAYC,EAASC,CAAK,EAChCG,EAAWN,EAAgB,IAAIK,CAAG,EACxC,GAAIC,EACH,OAAOA,EAER,MAAMC,EAAYC,EAAAA,uBAAA,EAClB,OAAAR,EAAgB,IAAIK,EAAKE,CAAS,EAC3BA,CACR,EAaaE,EAAkC,MAAO,CACrD,MAAAN,EACA,QAAAD,CACD,IAGuB,CACtB,MAAMG,EAAMJ,EAAYC,EAASC,CAAK,EAChCO,EAASV,EAAgB,IAAIK,CAAG,EACtC,GAAIK,EACH,OAAOA,EAIR,MAAMH,EADS,MAAMI,EAAAA,2BAA2B,CAAE,MAAAR,EAAO,QAAAD,EAAS,GACtCM,yBAAA,EAE5B,OAAAR,EAAgB,IAAIK,EAAKE,CAAS,EAC3BA,CACR,EAOaK,EAA8B,CAAC,CAC3C,MAAAT,EACA,QAAAD,CACD,IAGc,CACb,MAAMK,EAAYC,EAAAA,uBAAA,EAClB,OAAAR,EAAgB,IAAIC,EAAYC,EAASC,CAAK,EAAGI,CAAS,EACnDA,CACR,EAMaM,EAA2B,IAAY,CACnDb,EAAgB,MAAA,CACjB"}
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 the id survives the copilot unmounting —\n * **navigating between dashboard routes keeps one conversation**, and so does\n * closing the panel and opening it again. Component state could not do this: the\n * 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 `resolveCopilotSessionId`, which reads the store's\n * most recent conversation out of IndexedDB before minting a new one. That is\n * 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 keyed by session id, each record\n * carrying the route it was last used on; this registry only decides which\n * session id the copilot reads.\n */\nconst storeSessionIds = new Map<string, string>();\n\n/**\n * Registry key: the store.\n *\n * One conversation per store rather than per route — the merchant moving from\n * Campaigns to Segments is still the same conversation, and the route only\n * decides which POD, Area and page knowledge the backend loads into the *next*\n * turn. Two stores stay separate, or they would resolve to one record and show\n * each other's conversation.\n */\nconst registryKey = (storeId: string | undefined) => storeId ?? '';\n\n/**\n * The session id for a store in this page load, created on first use and stable\n * for the rest of the page load.\n */\nexport const getCopilotSessionId = ({\n\tstoreId,\n}: {\n\tstoreId?: string;\n}): string => {\n\tconst key = registryKey(storeId);\n\tconst existing = storeSessionIds.get(key);\n\tif (existing) {\n\t\treturn existing;\n\t}\n\tconst sessionId = createCopilotSessionId();\n\tstoreSessionIds.set(key, sessionId);\n\treturn sessionId;\n};\n\n/**\n * The session id for a store, resuming the stored conversation when this page\n * load has not seen the store yet.\n *\n * Order matters: the in-memory registry first, so a store already in play keeps\n * its id without a database read; then its most recent stored conversation, which\n * is how a refresh resumes; and only then a new id.\n *\n * Prefer this over `getCopilotSessionId` anywhere persistence is on — the\n * synchronous version cannot see the stored records, so it always starts fresh.\n */\nexport const resolveCopilotSessionId = async ({\n\tstoreId,\n}: {\n\tstoreId?: string;\n}): Promise<string> => {\n\tconst key = registryKey(storeId);\n\tconst inPlay = storeSessionIds.get(key);\n\tif (inPlay) {\n\t\treturn inPlay;\n\t}\n\n\tconst stored = await findLatestCopilotSessionId({ storeId });\n\tconst sessionId = stored ?? createCopilotSessionId();\n\t// Registered either way, so a second mount in this page load skips the read.\n\tstoreSessionIds.set(key, sessionId);\n\treturn sessionId;\n};\n\n/**\n * Starts a new conversation for one store, leaving other stores 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 resetCopilotSession = ({\n\tstoreId,\n}: {\n\tstoreId?: string;\n}): string => {\n\tconst sessionId = createCopilotSessionId();\n\tstoreSessionIds.set(registryKey(storeId), 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\tstoreSessionIds.clear();\n};\n"],"names":["storeSessionIds","registryKey","storeId","getCopilotSessionId","key","existing","sessionId","createCopilotSessionId","resolveCopilotSessionId","inPlay","findLatestCopilotSessionId","resetCopilotSession","clearCopilotPageSessions"],"mappings":"kHAuBMA,MAAsB,IAWtBC,EAAeC,GAAgCA,GAAW,GAMnDC,EAAsB,CAAC,CACnC,QAAAD,CACD,IAEc,CACb,MAAME,EAAMH,EAAYC,CAAO,EACzBG,EAAWL,EAAgB,IAAII,CAAG,EACxC,GAAIC,EACH,OAAOA,EAER,MAAMC,EAAYC,EAAAA,uBAAA,EAClB,OAAAP,EAAgB,IAAII,EAAKE,CAAS,EAC3BA,CACR,EAaaE,EAA0B,MAAO,CAC7C,QAAAN,CACD,IAEuB,CACtB,MAAME,EAAMH,EAAYC,CAAO,EACzBO,EAAST,EAAgB,IAAII,CAAG,EACtC,GAAIK,EACH,OAAOA,EAIR,MAAMH,EADS,MAAMI,6BAA2B,CAAE,QAAAR,EAAS,GAC/BK,yBAAA,EAE5B,OAAAP,EAAgB,IAAII,EAAKE,CAAS,EAC3BA,CACR,EAOaK,EAAsB,CAAC,CACnC,QAAAT,CACD,IAEc,CACb,MAAMI,EAAYC,EAAAA,uBAAA,EAClB,OAAAP,EAAgB,IAAIC,EAAYC,CAAO,EAAGI,CAAS,EAC5CA,CACR,EAMaM,EAA2B,IAAY,CACnDZ,EAAgB,MAAA,CACjB"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("react"),me=require("./copilotRegenerate.js"),F=require("./services/copilotDb.js"),he=require("./services/copilotFeedbackClient.js"),re=require("./services/copilotMessagesClient.js"),z=require("./services/copilotPageSession.js"),U=require("./services/copilotStreamClient.js"),se=require("./useCopilotIcebreakers.js"),ne="Something went wrong. Please try again.",oe=u=>u.map(R=>R.role==="assistant"?{...R,isRestored:!0}:R),Se=({storeId:u,agent:R=se.DEFAULT_COPILOT_AGENT,route:p=se.DEFAULT_COPILOT_ROUTE,messagesPath:X,feedbackPath:Y,user:Z,loadHistory:J=!0,systemPrompt:K,sessionId:I,persist:l=!0,onError:y,onResponseComplete:Q,onSessionIdChange:D,baseUrl:P,endpointPath:V,headers:M,authToken:O,getAuthToken:H,fetchImpl:_})=>{const[G,N]=n.useState([]),[ce,W]=n.useState(I??null),[k,E]=n.useState(!1),[ue,ee]=n.useState(!0),[ae,v]=n.useState(),[ie,le]=n.useState(),[fe,L]=n.useState(!1),[B,C]=n.useState(!1),f=n.useRef([]),$=n.useRef(void 0),m=n.useRef(null),a=n.useRef(null),h=n.useRef(I??null),q=n.useRef(p);q.current=p;const S=n.useRef(1),w=n.useRef(!1),i=n.useCallback(e=>{const t=e(f.current);f.current=t,N(t)},[]),T=n.useCallback(e=>{$.current=e,le(e)},[]),g=n.useCallback(()=>{const e=h.current;!l||!e||F.persistCopilotTurn({route:q.current,sessionId:e,messages:f.current,previousResponseId:$.current,storeId:u,agent:R})},[R,l,u]),x=n.useRef({baseUrl:P,messagesPath:X,feedbackPath:Y,headers:M,authToken:O,getAuthToken:H,fetchImpl:_,storeId:u,loadHistory:J,user:Z});x.current={baseUrl:P,messagesPath:X,feedbackPath:Y,headers:M,authToken:O,getAuthToken:H,fetchImpl:_,storeId:u,loadHistory:J,user:Z};const A=n.useCallback(async e=>{const t=x.current,s=h.current;if(!t.loadHistory||!s)return null;const r=await re.fetchCopilotMessages({baseUrl:t.baseUrl,endpointPath:t.messagesPath,headers:t.headers,authToken:t.authToken,getAuthToken:t.getAuthToken,fetchImpl:t.fetchImpl,storeId:t.storeId,sessionId:s,page:e,pageSize:re.COPILOT_HISTORY_PAGE_SIZE});return{...r,messages:oe(r.messages)}},[]);n.useEffect(()=>{let e=!1;return(async()=>{a.current?.(),a.current=null,m.current=null,E(!1),v(void 0),ee(!0),C(!1),w.current=!1,L(!1);const s=I??(l?await z.resolveCopilotSessionIdForRoute({route:p,storeId:u}):z.getCopilotSessionIdForRoute({route:p,storeId:u})),r=l?await F.loadCopilotSession(p,s):null;if(!e){if(h.current=s,W(s),f.current=oe(r?.messages??[]),N(f.current),T(r?.previousResponseId),ee(!1),D?.(s),f.current.length>0){S.current=2,C(x.current.loadHistory);return}if(!I){S.current=1,C(!1);return}S.current=1;try{const o=await A(1);if(e||!o)return;S.current=2,i(()=>o.messages),C(o.hasMore)}catch{C(!1)}}})(),()=>{e=!0}},[p,u,I,l,T,i,A]);const de=n.useCallback(()=>{if(w.current||!B)return;w.current=!0,L(!0);const e=S.current;(async()=>{try{const t=await A(e);if(!t){C(!1);return}S.current=e+1,C(t.hasMore),i(s=>{const r=new Set(s.flatMap(c=>c.messageId?[c.id,c.messageId]:[c.id])),o=t.messages.filter(c=>!r.has(c.id));return o.length>0?[...o,...s]:s})}catch(t){y?.(t instanceof Error?t:new Error(String(t)))}finally{w.current=!1,L(!1)}})()},[i,B,y,A]);n.useEffect(()=>{l&&F.pruneCopilotSessions()},[l]),n.useEffect(()=>()=>a.current?.(),[]);const b=n.useCallback(e=>{const t=m.current;m.current=null,t&&i(s=>s.map(r=>{if(r.id!==t)return r;const o={...r,isStreaming:!1};return e?e(o):o}).filter(r=>r.id!==t||r.content.length>0||r.wasInterrupted===!0))},[i]),te=n.useCallback(e=>{const t=m.current;if(e.type==="STREAM"){const s=U.readCopilotEventContent(e).text;if(!t||!s)return;i(r=>r.map(o=>o.id===t?{...o,content:o.content+s,messageId:e.messageId??o.messageId}:o));return}if(e.type==="TEXT"){const{text:s,followUps:r,toolResults:o,responseId:c}=U.readCopilotEventContent(e);b(d=>({...d,content:s,messageId:e.messageId??d.messageId,...r?{followUps:r}:{},...o?{toolResults:o}:{}})),T(c),g(),Q?.({text:s,responseId:c,followUps:r});return}e.type==="ERROR"&&(v(U.readCopilotEventContent(e).text||ne),b(),g())},[i,b,Q,g,T]),j=n.useCallback(e=>{const t=e.trim(),s=h.current;if(!t||!s||k)return;v(void 0);const r=Date.now(),o=`copilot-a-${r}`;m.current=o,i(c=>[...c,{id:`copilot-u-${r}`,role:"user",content:t,timestamp:r},{id:o,role:"assistant",content:"",timestamp:r+1,isStreaming:!0}]),E(!0),g(),a.current=U.streamCopilotChat({baseUrl:P,endpointPath:V,headers:M,authToken:O,getAuthToken:H,fetchImpl:_,storeId:u,agent:R,route:p,userPrompt:t,sessionId:s,previousResponseId:$.current,systemPrompt:K},{onEvent:te,onComplete:()=>{b(),E(!1),a.current=null,g()},onError:c=>{v(c.message||ne),b(),E(!1),a.current=null,g(),y?.(c)},onAbort:()=>{b(),E(!1),a.current=null,g()}})},[i,O,P,V,_,R,b,H,te,M,k,y,p,g,u,K]),Re=n.useCallback(()=>{f.current.some(t=>t.role==="assistant"&&t.content.length>0)&&j(me.COPILOT_REGENERATE_PROMPT)},[j]),pe=n.useCallback((e,t,s)=>{const r=x.current,o=h.current;if(!o)return;const c=f.current.find(d=>(d.messageId??d.id)===e);he.sendCopilotFeedback({baseUrl:r.baseUrl,endpointPath:r.feedbackPath,headers:r.headers,authToken:r.authToken,getAuthToken:r.getAuthToken,fetchImpl:r.fetchImpl,storeId:r.storeId,sessionId:o,messageId:e,feedback:t,route:q.current,reply:c?.content,user:r.user,reasons:s?.reasons,comment:s?.comment}).catch(d=>{y?.(d instanceof Error?d:new Error(String(d)))})},[y]),Ce=n.useCallback(()=>{if(!a.current)return;const e=m.current;e&&i(t=>t.map(s=>s.id===e?{...s,wasInterrupted:!0}:s)),a.current(),a.current=null},[i]),ge=n.useCallback(()=>{a.current?.(),a.current=null,m.current=null,E(!1),v(void 0),S.current=1,w.current=!1,L(!1),C(!1);const e=q.current,t=h.current,s=I??z.resetCopilotSessionForRoute({route:e,storeId:u});h.current=s,W(s),f.current=[],N([]),T(void 0),l&&t&&F.deleteCopilotSession(e,t),D?.(s)},[D,l,I,T,u]),Ie=n.useMemo(()=>k&&!G.some(e=>e.isStreaming&&e.content),[k,G]);return{messages:G,sessionId:ce,previousResponseId:ie,isStreaming:k,isThinking:Ie,isRestoring:ue,isLoadingHistory:fe,hasMoreHistory:B,error:ae,sendMessage:j,regenerate:Re,submitFeedback:pe,loadOlderMessages:de,stopStreaming:Ce,startNewConversation:ge}};exports.useCopilotStreamChat=Se;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("react"),me=require("./copilotRegenerate.js"),A=require("./services/copilotDb.js"),he=require("./services/copilotFeedbackClient.js"),re=require("./services/copilotMessagesClient.js"),z=require("./services/copilotPageSession.js"),F=require("./services/copilotStreamClient.js"),se=require("./useCopilotIcebreakers.js"),ne="Something went wrong. Please try again.",oe=u=>u.map(p=>p.role==="assistant"?{...p,isRestored:!0}:p),Se=({storeId:u,agent:p=se.DEFAULT_COPILOT_AGENT,route:w=se.DEFAULT_COPILOT_ROUTE,messagesPath:X,feedbackPath:Y,user:Z,loadHistory:J=!0,systemPrompt:K,sessionId:g,persist:l=!0,onError:b,onResponseComplete:Q,onSessionIdChange:U,baseUrl:P,endpointPath:V,headers:M,authToken:O,getAuthToken:H,fetchImpl:_})=>{const[D,G]=s.useState([]),[ce,W]=s.useState(g??null),[T,y]=s.useState(!1),[ue,ee]=s.useState(!0),[ae,k]=s.useState(),[ie,le]=s.useState(),[fe,L]=s.useState(!1),[N,R]=s.useState(!1),f=s.useRef([]),B=s.useRef(void 0),I=s.useRef(null),a=s.useRef(null),m=s.useRef(g??null),$=s.useRef(w);$.current=w;const h=s.useRef(1),v=s.useRef(!1),i=s.useCallback(e=>{const t=e(f.current);f.current=t,G(t)},[]),E=s.useCallback(e=>{B.current=e,le(e)},[]),C=s.useCallback(()=>{const e=m.current;!l||!e||A.persistCopilotTurn({route:$.current,sessionId:e,messages:f.current,previousResponseId:B.current,storeId:u,agent:p})},[p,l,u]),q=s.useRef({baseUrl:P,messagesPath:X,feedbackPath:Y,headers:M,authToken:O,getAuthToken:H,fetchImpl:_,storeId:u,loadHistory:J,user:Z});q.current={baseUrl:P,messagesPath:X,feedbackPath:Y,headers:M,authToken:O,getAuthToken:H,fetchImpl:_,storeId:u,loadHistory:J,user:Z};const x=s.useCallback(async e=>{const t=q.current,n=m.current;if(!t.loadHistory||!n)return null;const r=await re.fetchCopilotMessages({baseUrl:t.baseUrl,endpointPath:t.messagesPath,headers:t.headers,authToken:t.authToken,getAuthToken:t.getAuthToken,fetchImpl:t.fetchImpl,storeId:t.storeId,sessionId:n,page:e,pageSize:re.COPILOT_HISTORY_PAGE_SIZE});return{...r,messages:oe(r.messages)}},[]);s.useEffect(()=>{let e=!1;return(async()=>{a.current?.(),a.current=null,I.current=null,y(!1),k(void 0),ee(!0),R(!1),v.current=!1,L(!1);const n=g??(l?await z.resolveCopilotSessionId({storeId:u}):z.getCopilotSessionId({storeId:u})),r=l?await A.loadCopilotSession(n):null;if(!e){if(m.current=n,W(n),f.current=oe(r?.messages??[]),G(f.current),E(r?.previousResponseId),ee(!1),U?.(n),f.current.length>0){h.current=2,R(q.current.loadHistory);return}if(!g){h.current=1,R(!1);return}h.current=1;try{const o=await x(1);if(e||!o)return;h.current=2,i(()=>o.messages),R(o.hasMore)}catch{R(!1)}}})(),()=>{e=!0}},[u,g,l,E,i,x]);const de=s.useCallback(()=>{if(v.current||!N)return;v.current=!0,L(!0);const e=h.current;(async()=>{try{const t=await x(e);if(!t){R(!1);return}h.current=e+1,R(t.hasMore),i(n=>{const r=new Set(n.flatMap(c=>c.messageId?[c.id,c.messageId]:[c.id])),o=t.messages.filter(c=>!r.has(c.id));return o.length>0?[...o,...n]:n})}catch(t){b?.(t instanceof Error?t:new Error(String(t)))}finally{v.current=!1,L(!1)}})()},[i,N,b,x]);s.useEffect(()=>{l&&A.pruneCopilotSessions()},[l]),s.useEffect(()=>()=>a.current?.(),[]);const S=s.useCallback(e=>{const t=I.current;I.current=null,t&&i(n=>n.map(r=>{if(r.id!==t)return r;const o={...r,isStreaming:!1};return e?e(o):o}).filter(r=>r.id!==t||r.content.length>0||r.wasInterrupted===!0))},[i]),te=s.useCallback(e=>{const t=I.current;if(e.type==="STREAM"){const n=F.readCopilotEventContent(e).text;if(!t||!n)return;i(r=>r.map(o=>o.id===t?{...o,content:o.content+n,messageId:e.messageId??o.messageId}:o));return}if(e.type==="TEXT"){const{text:n,followUps:r,toolResults:o,responseId:c}=F.readCopilotEventContent(e);S(d=>({...d,content:n,messageId:e.messageId??d.messageId,...r?{followUps:r}:{},...o?{toolResults:o}:{}})),E(c),C(),Q?.({text:n,responseId:c,followUps:r});return}e.type==="ERROR"&&(k(F.readCopilotEventContent(e).text||ne),S(),C())},[i,S,Q,C,E]),j=s.useCallback(e=>{const t=e.trim(),n=m.current;if(!t||!n||T)return;k(void 0);const r=Date.now(),o=`copilot-a-${r}`;I.current=o,i(c=>[...c,{id:`copilot-u-${r}`,role:"user",content:t,timestamp:r},{id:o,role:"assistant",content:"",timestamp:r+1,isStreaming:!0}]),y(!0),C(),a.current=F.streamCopilotChat({baseUrl:P,endpointPath:V,headers:M,authToken:O,getAuthToken:H,fetchImpl:_,storeId:u,agent:p,route:w,userPrompt:t,sessionId:n,previousResponseId:B.current,systemPrompt:K},{onEvent:te,onComplete:()=>{S(),y(!1),a.current=null,C()},onError:c=>{k(c.message||ne),S(),y(!1),a.current=null,C(),b?.(c)},onAbort:()=>{S(),y(!1),a.current=null,C()}})},[i,O,P,V,_,p,S,H,te,M,T,b,w,C,u,K]),pe=s.useCallback(()=>{f.current.some(t=>t.role==="assistant"&&t.content.length>0)&&j(me.COPILOT_REGENERATE_PROMPT)},[j]),Re=s.useCallback((e,t,n)=>{const r=q.current,o=m.current;if(!o)return;const c=f.current.find(d=>(d.messageId??d.id)===e);he.sendCopilotFeedback({baseUrl:r.baseUrl,endpointPath:r.feedbackPath,headers:r.headers,authToken:r.authToken,getAuthToken:r.getAuthToken,fetchImpl:r.fetchImpl,storeId:r.storeId,sessionId:o,messageId:e,feedback:t,route:$.current,reply:c?.content,user:r.user,reasons:n?.reasons,comment:n?.comment}).catch(d=>{b?.(d instanceof Error?d:new Error(String(d)))})},[b]),Ce=s.useCallback(()=>{if(!a.current)return;const e=I.current;e&&i(t=>t.map(n=>n.id===e?{...n,wasInterrupted:!0}:n)),a.current(),a.current=null},[i]),ge=s.useCallback(()=>{a.current?.(),a.current=null,I.current=null,y(!1),k(void 0),h.current=1,v.current=!1,L(!1),R(!1);const e=m.current,t=g??z.resetCopilotSession({storeId:u});m.current=t,W(t),f.current=[],G([]),E(void 0),l&&e&&A.deleteCopilotSession(e),U?.(t)},[U,l,g,E,u]),Ie=s.useMemo(()=>T&&!D.some(e=>e.isStreaming&&e.content),[T,D]);return{messages:D,sessionId:ce,previousResponseId:ie,isStreaming:T,isThinking:Ie,isRestoring:ue,isLoadingHistory:fe,hasMoreHistory:N,error:ae,sendMessage:j,regenerate:pe,submitFeedback:Re,loadOlderMessages:de,stopStreaming:Ce,startNewConversation:ge}};exports.useCopilotStreamChat=Se;
2
2
  //# sourceMappingURL=useCopilotStreamChat.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useCopilotStreamChat.js","sources":["../../../../src/components/bik-copilot/useCopilotStreamChat.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { COPILOT_REGENERATE_PROMPT } from './copilotRegenerate';\nimport {\n\tCopilotFeedbackDetail,\n\tCopilotFeedbackVote,\n\tCopilotMessage,\n\tCopilotStreamEvent,\n\tUseCopilotStreamChatOptions,\n\tUseCopilotStreamChatResult,\n} from './model';\nimport {\n\tdeleteCopilotSession,\n\tloadCopilotSession,\n\tpersistCopilotTurn,\n\tpruneCopilotSessions,\n} from './services/copilotDb';\nimport { sendCopilotFeedback } from './services/copilotFeedbackClient';\nimport {\n\tCOPILOT_HISTORY_PAGE_SIZE,\n\tfetchCopilotMessages,\n} from './services/copilotMessagesClient';\nimport {\n\tgetCopilotSessionIdForRoute,\n\tresetCopilotSessionForRoute,\n\tresolveCopilotSessionIdForRoute,\n} from './services/copilotPageSession';\nimport {\n\treadCopilotEventContent,\n\tstreamCopilotChat,\n} from './services/copilotStreamClient';\nimport {\n\tDEFAULT_COPILOT_AGENT,\n\tDEFAULT_COPILOT_ROUTE,\n} from './useCopilotIcebreakers';\n\nconst GENERIC_ERROR = 'Something went wrong. Please try again.';\n\n/**\n * Runs a merchant copilot conversation against the streaming\n * `/bik-ai/copilot-chatbot` endpoint.\n *\n * Owns three things the endpoint's contract requires:\n *\n * 1. **The session id.** One per route. Navigating away and back to the same\n * route continues that conversation, and so does a refresh: the ids live in\n * memory but are recovered from the stored records, which is what keeps them\n * lined up with the history the backend keys by them (see\n * `copilotPageSession`).\n * 2. **`previousResponseId`.** Absent on the first turn; from then on it is the\n * `responseId` of the last `TEXT` event, which is what makes OpenAI replay the\n * earlier turns server side. A turn that breaks halfway returns no\n * `responseId`, and the backend means that — the next turn starts fresh.\n * 3. **The transcript**, stored in IndexedDB under `{sessionId}:{route}` and\n * written every turn, so leaving the page mid-conversation loses nothing.\n */\nconst asRestored = (messages: CopilotMessage[]): CopilotMessage[] =>\n\tmessages.map((message) =>\n\t\tmessage.role === 'assistant' ? { ...message, isRestored: true } : message,\n\t);\n\nexport const useCopilotStreamChat = ({\n\tstoreId,\n\tagent = DEFAULT_COPILOT_AGENT,\n\troute = DEFAULT_COPILOT_ROUTE,\n\tmessagesPath,\n\tfeedbackPath,\n\tuser,\n\tloadHistory = true,\n\tsystemPrompt,\n\tsessionId: pinnedSessionId,\n\tpersist = true,\n\tonError,\n\tonResponseComplete,\n\tonSessionIdChange,\n\tbaseUrl,\n\tendpointPath,\n\theaders,\n\tauthToken,\n\tgetAuthToken,\n\tfetchImpl,\n}: UseCopilotStreamChatOptions): UseCopilotStreamChatResult => {\n\tconst [messages, setMessages] = useState<CopilotMessage[]>([]);\n\tconst [sessionId, setSessionId] = useState<string | null>(\n\t\tpinnedSessionId ?? null,\n\t);\n\tconst [isStreaming, setIsStreaming] = useState(false);\n\tconst [isRestoring, setIsRestoring] = useState(true);\n\tconst [error, setError] = useState<string | undefined>();\n\tconst [previousResponseId, setPreviousResponseId] = useState<\n\t\tstring | undefined\n\t>();\n\tconst [isLoadingHistory, setIsLoadingHistory] = useState(false);\n\tconst [hasMoreHistory, setHasMoreHistory] = useState(false);\n\n\t// Async stream handlers run long after the render that created them, so the\n\t// live values live in refs and state is only the render mirror.\n\tconst messagesRef = useRef<CopilotMessage[]>([]);\n\tconst previousResponseIdRef = useRef<string | undefined>(undefined);\n\tconst streamingIdRef = useRef<string | null>(null);\n\tconst abortRef = useRef<(() => void) | null>(null);\n\tconst sessionIdRef = useRef<string | null>(pinnedSessionId ?? null);\n\t// The active route, for the async handlers that write the transcript.\n\tconst routeRef = useRef<string>(route);\n\trouteRef.current = route;\n\t// Next page of stored history to ask for. Starts at 2 when the local cache\n\t// already holds page 1's worth, which is the steady state.\n\tconst nextHistoryPageRef = useRef(1);\n\tconst isLoadingHistoryRef = useRef(false);\n\n\tconst applyMessages = useCallback(\n\t\t(updater: (previous: CopilotMessage[]) => CopilotMessage[]) => {\n\t\t\tconst next = updater(messagesRef.current);\n\t\t\tmessagesRef.current = next;\n\t\t\tsetMessages(next);\n\t\t},\n\t\t[],\n\t);\n\n\tconst setThreadResponseId = useCallback((responseId: string | undefined) => {\n\t\tpreviousResponseIdRef.current = responseId;\n\t\tsetPreviousResponseId(responseId);\n\t}, []);\n\n\tconst save = useCallback(() => {\n\t\tconst id = sessionIdRef.current;\n\t\tif (!persist || !id) {\n\t\t\treturn;\n\t\t}\n\t\tvoid persistCopilotTurn({\n\t\t\troute: routeRef.current,\n\t\t\tsessionId: id,\n\t\t\tmessages: messagesRef.current,\n\t\t\tpreviousResponseId: previousResponseIdRef.current,\n\t\t\tstoreId,\n\t\t\tagent,\n\t\t});\n\t}, [agent, persist, storeId]);\n\n\t// Transport config for the async readers below. Held in a ref rather than in\n\t// the effect's deps: a host that passes an inline `headers` object would\n\t// otherwise re-run restore on every render and wipe the transcript.\n\tconst transportRef = useRef({\n\t\tbaseUrl,\n\t\tmessagesPath,\n\t\tfeedbackPath,\n\t\theaders,\n\t\tauthToken,\n\t\tgetAuthToken,\n\t\tfetchImpl,\n\t\tstoreId,\n\t\tloadHistory,\n\t\tuser,\n\t});\n\ttransportRef.current = {\n\t\tbaseUrl,\n\t\tmessagesPath,\n\t\tfeedbackPath,\n\t\theaders,\n\t\tauthToken,\n\t\tgetAuthToken,\n\t\tfetchImpl,\n\t\tstoreId,\n\t\tloadHistory,\n\t\tuser,\n\t};\n\n\tconst readHistoryPage = useCallback(async (page: number) => {\n\t\tconst transport = transportRef.current;\n\t\tconst id = sessionIdRef.current;\n\t\tif (!transport.loadHistory || !id) {\n\t\t\treturn null;\n\t\t}\n\t\tconst historyPage = await fetchCopilotMessages({\n\t\t\tbaseUrl: transport.baseUrl,\n\t\t\tendpointPath: transport.messagesPath,\n\t\t\theaders: transport.headers,\n\t\t\tauthToken: transport.authToken,\n\t\t\tgetAuthToken: transport.getAuthToken,\n\t\t\tfetchImpl: transport.fetchImpl,\n\t\t\tstoreId: transport.storeId,\n\t\t\tsessionId: id,\n\t\t\tpage,\n\t\t\tpageSize: COPILOT_HISTORY_PAGE_SIZE,\n\t\t});\n\t\treturn { ...historyPage, messages: asRestored(historyPage.messages) };\n\t}, []);\n\n\t// Resolve the conversation for this route and read it back from IndexedDB.\n\t// Runs again when `route` changes, because the route is half of a\n\t// conversation's identity — a different page is a different chat.\n\tuseEffect(() => {\n\t\tlet cancelled = false;\n\n\t\tconst restore = async () => {\n\t\t\t// Chunks from the previous route's turn must not land in this one.\n\t\t\tabortRef.current?.();\n\t\t\tabortRef.current = null;\n\t\t\tstreamingIdRef.current = null;\n\t\t\tsetIsStreaming(false);\n\t\t\tsetError(undefined);\n\t\t\tsetIsRestoring(true);\n\t\t\tsetHasMoreHistory(false);\n\t\t\tisLoadingHistoryRef.current = false;\n\t\t\tsetIsLoadingHistory(false);\n\n\t\t\tconst id =\n\t\t\t\tpinnedSessionId ??\n\t\t\t\t(persist\n\t\t\t\t\t? await resolveCopilotSessionIdForRoute({ route, storeId })\n\t\t\t\t\t: getCopilotSessionIdForRoute({ route, storeId }));\n\t\t\tconst record = persist ? await loadCopilotSession(route, id) : null;\n\t\t\tif (cancelled) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tsessionIdRef.current = id;\n\t\t\tsetSessionId(id);\n\t\t\tmessagesRef.current = asRestored(record?.messages ?? []);\n\t\t\tsetMessages(messagesRef.current);\n\t\t\tsetThreadResponseId(record?.previousResponseId);\n\t\t\tsetIsRestoring(false);\n\t\t\tonSessionIdChange?.(id);\n\n\t\t\t// The cache holds at most one page, so what is on screen already *is*\n\t\t\t// page 1 and the reader's first scroll-up wants page 2.\n\t\t\tif (messagesRef.current.length > 0) {\n\t\t\t\tnextHistoryPageRef.current = 2;\n\t\t\t\tsetHasMoreHistory(transportRef.current.loadHistory);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Nothing cached and nothing to scroll, so page 1 has to be fetched for\n\t\t\t// the history to be reachable at all — but only for an id the host\n\t\t\t// pinned. An id this library resolved came out of the local records, so\n\t\t\t// an empty cache for it means an empty conversation, and a freshly\n\t\t\t// minted one has nothing behind it by definition. Asking anyway would\n\t\t\t// put a pointless request on the start of every new conversation.\n\t\t\tif (!pinnedSessionId) {\n\t\t\t\tnextHistoryPageRef.current = 1;\n\t\t\t\tsetHasMoreHistory(false);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tnextHistoryPageRef.current = 1;\n\t\t\ttry {\n\t\t\t\tconst firstPage = await readHistoryPage(1);\n\t\t\t\tif (cancelled || !firstPage) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tnextHistoryPageRef.current = 2;\n\t\t\t\tapplyMessages(() => firstPage.messages);\n\t\t\t\tsetHasMoreHistory(firstPage.hasMore);\n\t\t\t} catch {\n\t\t\t\t// A session the backend has never heard of is the normal case for a\n\t\t\t\t// host-pinned id on its first use; the empty state covers it.\n\t\t\t\tsetHasMoreHistory(false);\n\t\t\t}\n\t\t};\n\n\t\tvoid restore();\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t};\n\t\t// onSessionIdChange is event-like: re-running restore because the host\n\t\t// passed a new inline callback would wipe the transcript.\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [\n\t\troute,\n\t\tstoreId,\n\t\tpinnedSessionId,\n\t\tpersist,\n\t\tsetThreadResponseId,\n\t\tapplyMessages,\n\t\treadHistoryPage,\n\t]);\n\n\t/**\n\t * Prepends the page before the oldest message on screen.\n\t *\n\t * Guarded by a ref rather than the state flag because a scroll handler can\n\t * fire many times before React re-renders, and each of those would otherwise\n\t * start its own request for the same page.\n\t */\n\tconst loadOlderMessages = useCallback(() => {\n\t\tif (isLoadingHistoryRef.current || !hasMoreHistory) {\n\t\t\treturn;\n\t\t}\n\t\tisLoadingHistoryRef.current = true;\n\t\tsetIsLoadingHistory(true);\n\n\t\tconst page = nextHistoryPageRef.current;\n\t\tvoid (async () => {\n\t\t\ttry {\n\t\t\t\tconst older = await readHistoryPage(page);\n\t\t\t\tif (!older) {\n\t\t\t\t\tsetHasMoreHistory(false);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tnextHistoryPageRef.current = page + 1;\n\t\t\t\tsetHasMoreHistory(older.hasMore);\n\t\t\t\tapplyMessages((previous) => {\n\t\t\t\t\tconst known = new Set(\n\t\t\t\t\t\tprevious.flatMap((message) =>\n\t\t\t\t\t\t\tmessage.messageId\n\t\t\t\t\t\t\t\t? [message.id, message.messageId]\n\t\t\t\t\t\t\t\t: [message.id],\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tconst fresh = older.messages.filter(\n\t\t\t\t\t\t(message) => !known.has(message.id),\n\t\t\t\t\t);\n\t\t\t\t\treturn fresh.length > 0 ? [...fresh, ...previous] : previous;\n\t\t\t\t});\n\t\t\t} catch (historyError) {\n\t\t\t\t// Left silent in the transcript on purpose: failing to reach further\n\t\t\t\t// back is not a failure of the conversation on screen. Another\n\t\t\t\t// scroll-up retries the same page.\n\t\t\t\tonError?.(\n\t\t\t\t\thistoryError instanceof Error\n\t\t\t\t\t\t? historyError\n\t\t\t\t\t\t: new Error(String(historyError)),\n\t\t\t\t);\n\t\t\t} finally {\n\t\t\t\tisLoadingHistoryRef.current = false;\n\t\t\t\tsetIsLoadingHistory(false);\n\t\t\t}\n\t\t})();\n\t}, [applyMessages, hasMoreHistory, onError, readHistoryPage]);\n\n\t// Records pile up one per route per page load otherwise, since every refresh\n\t// writes under a new key.\n\tuseEffect(() => {\n\t\tif (persist) {\n\t\t\tvoid pruneCopilotSessions();\n\t\t}\n\t}, [persist]);\n\n\t// Abort an in-flight turn if the copilot unmounts.\n\tuseEffect(() => () => abortRef.current?.(), []);\n\n\tconst finishStreamingBubble = useCallback(\n\t\t(mutate?: (message: CopilotMessage) => CopilotMessage) => {\n\t\t\tconst streamingId = streamingIdRef.current;\n\t\t\tstreamingIdRef.current = null;\n\t\t\tif (!streamingId) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tapplyMessages((previous) =>\n\t\t\t\tprevious\n\t\t\t\t\t.map((message) => {\n\t\t\t\t\t\tif (message.id !== streamingId) {\n\t\t\t\t\t\t\treturn message;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst settled: CopilotMessage = {\n\t\t\t\t\t\t\t...message,\n\t\t\t\t\t\t\tisStreaming: false,\n\t\t\t\t\t\t};\n\t\t\t\t\t\treturn mutate ? mutate(settled) : settled;\n\t\t\t\t\t})\n\t\t\t\t\t// An assistant bubble that never received text would render as an\n\t\t\t\t\t// empty balloon; the error banner carries the information instead.\n\t\t\t\t\t// A stopped turn keeps its bubble either way — the interrupted note\n\t\t\t\t\t// is the only trace a turn the reader cut short ever happened.\n\t\t\t\t\t.filter(\n\t\t\t\t\t\t(message) =>\n\t\t\t\t\t\t\tmessage.id !== streamingId ||\n\t\t\t\t\t\t\tmessage.content.length > 0 ||\n\t\t\t\t\t\t\tmessage.wasInterrupted === true,\n\t\t\t\t\t),\n\t\t\t);\n\t\t},\n\t\t[applyMessages],\n\t);\n\n\tconst handleEvent = useCallback(\n\t\t(event: CopilotStreamEvent) => {\n\t\t\tconst streamingId = streamingIdRef.current;\n\n\t\t\tif (event.type === 'STREAM') {\n\t\t\t\tconst chunk = readCopilotEventContent(event).text;\n\t\t\t\tif (!streamingId || !chunk) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tapplyMessages((previous) =>\n\t\t\t\t\tprevious.map((message) =>\n\t\t\t\t\t\tmessage.id === streamingId\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t...message,\n\t\t\t\t\t\t\t\t\tcontent: message.content + chunk,\n\t\t\t\t\t\t\t\t\tmessageId: event.messageId ?? message.messageId,\n\t\t\t\t\t\t\t }\n\t\t\t\t\t\t\t: message,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (event.type === 'TEXT') {\n\t\t\t\t// TEXT carries the whole message, so it replaces the accumulated\n\t\t\t\t// chunks rather than appending — that keeps the bubble correct even if\n\t\t\t\t// a STREAM frame was dropped or duplicated. `responseId` rides inside\n\t\t\t\t// the content payload (as `lastResponseId`).\n\t\t\t\tconst {\n\t\t\t\t\ttext: fullText,\n\t\t\t\t\tfollowUps,\n\t\t\t\t\ttoolResults,\n\t\t\t\t\tresponseId,\n\t\t\t\t} = readCopilotEventContent(event);\n\t\t\t\tfinishStreamingBubble((message) => ({\n\t\t\t\t\t...message,\n\t\t\t\t\tcontent: fullText,\n\t\t\t\t\tmessageId: event.messageId ?? message.messageId,\n\t\t\t\t\t...(followUps ? { followUps } : {}),\n\t\t\t\t\t// Kept on the message, not just handled in passing, so the\n\t\t\t\t\t// action button is still there after a reload.\n\t\t\t\t\t...(toolResults ? { toolResults } : {}),\n\t\t\t\t}));\n\t\t\t\tsetThreadResponseId(responseId);\n\t\t\t\tsave();\n\t\t\t\tonResponseComplete?.({\n\t\t\t\t\ttext: fullText,\n\t\t\t\t\tresponseId,\n\t\t\t\t\tfollowUps,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (event.type === 'ERROR') {\n\t\t\t\tsetError(readCopilotEventContent(event).text || GENERIC_ERROR);\n\t\t\t\tfinishStreamingBubble();\n\t\t\t\tsave();\n\t\t\t}\n\n\t\t\t// Unknown types (TOOL_STATUS is declared server side but not emitted)\n\t\t\t// are ignored so a new event type cannot break an existing client.\n\t\t},\n\t\t[\n\t\t\tapplyMessages,\n\t\t\tfinishStreamingBubble,\n\t\t\tonResponseComplete,\n\t\t\tsave,\n\t\t\tsetThreadResponseId,\n\t\t],\n\t);\n\n\tconst sendMessage = useCallback(\n\t\t(message: string) => {\n\t\t\tconst userPrompt = message.trim();\n\t\t\tconst id = sessionIdRef.current;\n\t\t\tif (!userPrompt || !id || isStreaming) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tsetError(undefined);\n\t\t\tconst now = Date.now();\n\t\t\tconst assistantBubbleId = `copilot-a-${now}`;\n\t\t\tstreamingIdRef.current = assistantBubbleId;\n\n\t\t\tapplyMessages((previous) => [\n\t\t\t\t...previous,\n\t\t\t\t{\n\t\t\t\t\tid: `copilot-u-${now}`,\n\t\t\t\t\trole: 'user',\n\t\t\t\t\tcontent: userPrompt,\n\t\t\t\t\ttimestamp: now,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid: assistantBubbleId,\n\t\t\t\t\trole: 'assistant',\n\t\t\t\t\tcontent: '',\n\t\t\t\t\ttimestamp: now + 1,\n\t\t\t\t\tisStreaming: true,\n\t\t\t\t},\n\t\t\t]);\n\t\t\tsetIsStreaming(true);\n\t\t\tsave();\n\n\t\t\tabortRef.current = streamCopilotChat(\n\t\t\t\t{\n\t\t\t\t\tbaseUrl,\n\t\t\t\t\tendpointPath,\n\t\t\t\t\theaders,\n\t\t\t\t\tauthToken,\n\t\t\t\t\tgetAuthToken,\n\t\t\t\t\tfetchImpl,\n\t\t\t\t\tstoreId,\n\t\t\t\t\tagent,\n\t\t\t\t\troute,\n\t\t\t\t\tuserPrompt,\n\t\t\t\t\tsessionId: id,\n\t\t\t\t\tpreviousResponseId: previousResponseIdRef.current,\n\t\t\t\t\tsystemPrompt,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tonEvent: handleEvent,\n\t\t\t\t\tonComplete: () => {\n\t\t\t\t\t\t// STOP closes the stream without a frame, so this is where a turn\n\t\t\t\t\t\t// ends. TEXT has usually already settled the bubble.\n\t\t\t\t\t\tfinishStreamingBubble();\n\t\t\t\t\t\tsetIsStreaming(false);\n\t\t\t\t\t\tabortRef.current = null;\n\t\t\t\t\t\tsave();\n\t\t\t\t\t},\n\t\t\t\t\tonError: (streamError) => {\n\t\t\t\t\t\tsetError(streamError.message || GENERIC_ERROR);\n\t\t\t\t\t\tfinishStreamingBubble();\n\t\t\t\t\t\tsetIsStreaming(false);\n\t\t\t\t\t\tabortRef.current = null;\n\t\t\t\t\t\tsave();\n\t\t\t\t\t\tonError?.(streamError);\n\t\t\t\t\t},\n\t\t\t\t\tonAbort: () => {\n\t\t\t\t\t\tfinishStreamingBubble();\n\t\t\t\t\t\tsetIsStreaming(false);\n\t\t\t\t\t\tabortRef.current = null;\n\t\t\t\t\t\tsave();\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\t[\n\t\t\tapplyMessages,\n\t\t\tauthToken,\n\t\t\tbaseUrl,\n\t\t\tendpointPath,\n\t\t\tfetchImpl,\n\t\t\tagent,\n\t\t\tfinishStreamingBubble,\n\t\t\tgetAuthToken,\n\t\t\thandleEvent,\n\t\t\theaders,\n\t\t\tisStreaming,\n\t\t\tonError,\n\t\t\troute,\n\t\t\tsave,\n\t\t\tstoreId,\n\t\t\tsystemPrompt,\n\t\t],\n\t);\n\n\tconst regenerate = useCallback(() => {\n\t\tconst hasReply = messagesRef.current.some(\n\t\t\t(message) => message.role === 'assistant' && message.content.length > 0,\n\t\t);\n\t\tif (!hasReply) {\n\t\t\treturn;\n\t\t}\n\t\tsendMessage(COPILOT_REGENERATE_PROMPT);\n\t}, [sendMessage]);\n\n\t/**\n\t * Records a thumbs-up or thumbs-down on one reply.\n\t *\n\t * Fire-and-forget: a rating is a side note to the conversation, so a failed\n\t * post is reported through `onError` and otherwise ignored -- the thumb stays\n\t * lit either way rather than flickering back on a network hiccup.\n\t *\n\t * The reply's own text and the route ride along, so whoever reads the feedback\n\t * sees what was rated without looking the message up.\n\t */\n\tconst submitFeedback = useCallback(\n\t\t(\n\t\t\tmessageId: string,\n\t\t\tfeedback: CopilotFeedbackVote,\n\t\t\tdetail?: CopilotFeedbackDetail,\n\t\t) => {\n\t\t\tconst transport = transportRef.current;\n\t\t\tconst id = sessionIdRef.current;\n\t\t\tif (!id) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// A live turn's bubble carries only a local id until the final event\n\t\t\t// lands, so match on either and let the backend see whichever it gets.\n\t\t\tconst rated = messagesRef.current.find(\n\t\t\t\t(message) => (message.messageId ?? message.id) === messageId,\n\t\t\t);\n\t\t\tvoid sendCopilotFeedback({\n\t\t\t\tbaseUrl: transport.baseUrl,\n\t\t\t\tendpointPath: transport.feedbackPath,\n\t\t\t\theaders: transport.headers,\n\t\t\t\tauthToken: transport.authToken,\n\t\t\t\tgetAuthToken: transport.getAuthToken,\n\t\t\t\tfetchImpl: transport.fetchImpl,\n\t\t\t\tstoreId: transport.storeId,\n\t\t\t\tsessionId: id,\n\t\t\t\tmessageId,\n\t\t\t\tfeedback,\n\t\t\t\troute: routeRef.current,\n\t\t\t\treply: rated?.content,\n\t\t\t\tuser: transport.user,\n\t\t\t\treasons: detail?.reasons,\n\t\t\t\tcomment: detail?.comment,\n\t\t\t}).catch((feedbackError) => {\n\t\t\t\tonError?.(\n\t\t\t\t\tfeedbackError instanceof Error\n\t\t\t\t\t\t? feedbackError\n\t\t\t\t\t\t: new Error(String(feedbackError)),\n\t\t\t\t);\n\t\t\t});\n\t\t},\n\t\t[onError],\n\t);\n\n\tconst stopStreaming = useCallback(() => {\n\t\tif (!abortRef.current) {\n\t\t\treturn;\n\t\t}\n\t\t// Flagged here rather than in `onAbort`, because an abort that lands between\n\t\t// frames ends the stream through `onComplete` instead. Whichever fires, the\n\t\t// reader stopped this turn, and whatever text had arrived stays.\n\t\tconst streamingId = streamingIdRef.current;\n\t\tif (streamingId) {\n\t\t\tapplyMessages((previous) =>\n\t\t\t\tprevious.map((message) =>\n\t\t\t\t\tmessage.id === streamingId\n\t\t\t\t\t\t? { ...message, wasInterrupted: true }\n\t\t\t\t\t\t: message,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tabortRef.current();\n\t\tabortRef.current = null;\n\t}, [applyMessages]);\n\n\tconst startNewConversation = useCallback(() => {\n\t\tabortRef.current?.();\n\t\tabortRef.current = null;\n\t\tstreamingIdRef.current = null;\n\t\tsetIsStreaming(false);\n\t\tsetError(undefined);\n\t\t// A new conversation has no history behind it; the old session's pages\n\t\t// belong to the old session id.\n\t\tnextHistoryPageRef.current = 1;\n\t\tisLoadingHistoryRef.current = false;\n\t\tsetIsLoadingHistory(false);\n\t\tsetHasMoreHistory(false);\n\n\t\tconst activeRoute = routeRef.current;\n\t\t// The conversation being cleared, so its stored transcript can go with it.\n\t\tconst clearedId = sessionIdRef.current;\n\t\t// A host-pinned id is the host's to change, so only clear its transcript.\n\t\tconst nextId =\n\t\t\tpinnedSessionId ??\n\t\t\tresetCopilotSessionForRoute({ route: activeRoute, storeId });\n\t\tsessionIdRef.current = nextId;\n\t\tsetSessionId(nextId);\n\t\tmessagesRef.current = [];\n\t\tsetMessages([]);\n\t\tsetThreadResponseId(undefined);\n\n\t\t// \"Refresh chat\" wipes the record, not just the screen: the cleared\n\t\t// conversation is deleted from IndexedDB so reopening the tab starts fresh\n\t\t// instead of resuming it. Nothing is written for the new session here — its\n\t\t// first record is stamped when the first turn is sent, which is also what\n\t\t// keeps an untouched refresh from leaving an empty record behind.\n\t\tif (persist && clearedId) {\n\t\t\tvoid deleteCopilotSession(activeRoute, clearedId);\n\t\t}\n\t\tonSessionIdChange?.(nextId);\n\t}, [\n\t\tonSessionIdChange,\n\t\tpersist,\n\t\tpinnedSessionId,\n\t\tsetThreadResponseId,\n\t\tstoreId,\n\t]);\n\n\t// The thinking bubble is only for the wait before the first token; once text\n\t// starts arriving the bubble itself shows progress.\n\tconst isThinking = useMemo(\n\t\t() =>\n\t\t\tisStreaming &&\n\t\t\t!messages.some((message) => message.isStreaming && message.content),\n\t\t[isStreaming, messages],\n\t);\n\n\treturn {\n\t\tmessages,\n\t\tsessionId,\n\t\tpreviousResponseId,\n\t\tisStreaming,\n\t\tisThinking,\n\t\tisRestoring,\n\t\tisLoadingHistory,\n\t\thasMoreHistory,\n\t\terror,\n\t\tsendMessage,\n\t\tregenerate,\n\t\tsubmitFeedback,\n\t\tloadOlderMessages,\n\t\tstopStreaming,\n\t\tstartNewConversation,\n\t};\n};\n"],"names":["GENERIC_ERROR","asRestored","messages","message","useCopilotStreamChat","storeId","agent","DEFAULT_COPILOT_AGENT","route","DEFAULT_COPILOT_ROUTE","messagesPath","feedbackPath","user","loadHistory","systemPrompt","pinnedSessionId","persist","onError","onResponseComplete","onSessionIdChange","baseUrl","endpointPath","headers","authToken","getAuthToken","fetchImpl","setMessages","useState","sessionId","setSessionId","isStreaming","setIsStreaming","isRestoring","setIsRestoring","error","setError","previousResponseId","setPreviousResponseId","isLoadingHistory","setIsLoadingHistory","hasMoreHistory","setHasMoreHistory","messagesRef","useRef","previousResponseIdRef","streamingIdRef","abortRef","sessionIdRef","routeRef","nextHistoryPageRef","isLoadingHistoryRef","applyMessages","useCallback","updater","next","setThreadResponseId","responseId","save","id","persistCopilotTurn","transportRef","readHistoryPage","page","transport","historyPage","fetchCopilotMessages","COPILOT_HISTORY_PAGE_SIZE","useEffect","cancelled","resolveCopilotSessionIdForRoute","getCopilotSessionIdForRoute","record","loadCopilotSession","firstPage","loadOlderMessages","older","previous","known","fresh","historyError","pruneCopilotSessions","finishStreamingBubble","mutate","streamingId","settled","handleEvent","event","chunk","readCopilotEventContent","fullText","followUps","toolResults","sendMessage","userPrompt","now","assistantBubbleId","streamCopilotChat","streamError","regenerate","COPILOT_REGENERATE_PROMPT","submitFeedback","messageId","feedback","detail","rated","sendCopilotFeedback","feedbackError","stopStreaming","startNewConversation","activeRoute","clearedId","nextId","resetCopilotSessionForRoute","deleteCopilotSession","isThinking","useMemo"],"mappings":"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"}
1
+ {"version":3,"file":"useCopilotStreamChat.js","sources":["../../../../src/components/bik-copilot/useCopilotStreamChat.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { COPILOT_REGENERATE_PROMPT } from './copilotRegenerate';\nimport {\n\tCopilotFeedbackDetail,\n\tCopilotFeedbackVote,\n\tCopilotMessage,\n\tCopilotStreamEvent,\n\tUseCopilotStreamChatOptions,\n\tUseCopilotStreamChatResult,\n} from './model';\nimport {\n\tdeleteCopilotSession,\n\tloadCopilotSession,\n\tpersistCopilotTurn,\n\tpruneCopilotSessions,\n} from './services/copilotDb';\nimport { sendCopilotFeedback } from './services/copilotFeedbackClient';\nimport {\n\tCOPILOT_HISTORY_PAGE_SIZE,\n\tfetchCopilotMessages,\n} from './services/copilotMessagesClient';\nimport {\n\tgetCopilotSessionId,\n\tresetCopilotSession,\n\tresolveCopilotSessionId,\n} from './services/copilotPageSession';\nimport {\n\treadCopilotEventContent,\n\tstreamCopilotChat,\n} from './services/copilotStreamClient';\nimport {\n\tDEFAULT_COPILOT_AGENT,\n\tDEFAULT_COPILOT_ROUTE,\n} from './useCopilotIcebreakers';\n\nconst GENERIC_ERROR = 'Something went wrong. Please try again.';\n\n/**\n * Runs a merchant copilot conversation against the streaming\n * `/bik-ai/copilot-chatbot` endpoint.\n *\n * Owns three things the endpoint's contract requires:\n *\n * 1. **The session id.** One per store, not per route: the merchant moving\n * between dashboard pages stays in the same conversation, and so does a\n * refresh — the ids live in memory but are recovered from the stored records,\n * which is what keeps them lined up with the history the backend keys by them\n * (see `copilotPageSession`). The route travels with each turn instead, so the\n * backend still loads the POD, Area and page knowledge of wherever the\n * merchant is now.\n * 2. **`previousResponseId`.** Absent on the first turn; from then on it is the\n * `responseId` of the last `TEXT` event, which is what makes OpenAI replay the\n * earlier turns server side. A turn that breaks halfway returns no\n * `responseId`, and the backend means that — the next turn starts fresh.\n * 3. **The transcript**, stored in IndexedDB under the session id and written\n * every turn, so leaving the page mid-conversation loses nothing.\n */\nconst asRestored = (messages: CopilotMessage[]): CopilotMessage[] =>\n\tmessages.map((message) =>\n\t\tmessage.role === 'assistant' ? { ...message, isRestored: true } : message,\n\t);\n\nexport const useCopilotStreamChat = ({\n\tstoreId,\n\tagent = DEFAULT_COPILOT_AGENT,\n\troute = DEFAULT_COPILOT_ROUTE,\n\tmessagesPath,\n\tfeedbackPath,\n\tuser,\n\tloadHistory = true,\n\tsystemPrompt,\n\tsessionId: pinnedSessionId,\n\tpersist = true,\n\tonError,\n\tonResponseComplete,\n\tonSessionIdChange,\n\tbaseUrl,\n\tendpointPath,\n\theaders,\n\tauthToken,\n\tgetAuthToken,\n\tfetchImpl,\n}: UseCopilotStreamChatOptions): UseCopilotStreamChatResult => {\n\tconst [messages, setMessages] = useState<CopilotMessage[]>([]);\n\tconst [sessionId, setSessionId] = useState<string | null>(\n\t\tpinnedSessionId ?? null,\n\t);\n\tconst [isStreaming, setIsStreaming] = useState(false);\n\tconst [isRestoring, setIsRestoring] = useState(true);\n\tconst [error, setError] = useState<string | undefined>();\n\tconst [previousResponseId, setPreviousResponseId] = useState<\n\t\tstring | undefined\n\t>();\n\tconst [isLoadingHistory, setIsLoadingHistory] = useState(false);\n\tconst [hasMoreHistory, setHasMoreHistory] = useState(false);\n\n\t// Async stream handlers run long after the render that created them, so the\n\t// live values live in refs and state is only the render mirror.\n\tconst messagesRef = useRef<CopilotMessage[]>([]);\n\tconst previousResponseIdRef = useRef<string | undefined>(undefined);\n\tconst streamingIdRef = useRef<string | null>(null);\n\tconst abortRef = useRef<(() => void) | null>(null);\n\tconst sessionIdRef = useRef<string | null>(pinnedSessionId ?? null);\n\t// The active route, for the async handlers that write the transcript.\n\tconst routeRef = useRef<string>(route);\n\trouteRef.current = route;\n\t// Next page of stored history to ask for. Starts at 2 when the local cache\n\t// already holds page 1's worth, which is the steady state.\n\tconst nextHistoryPageRef = useRef(1);\n\tconst isLoadingHistoryRef = useRef(false);\n\n\tconst applyMessages = useCallback(\n\t\t(updater: (previous: CopilotMessage[]) => CopilotMessage[]) => {\n\t\t\tconst next = updater(messagesRef.current);\n\t\t\tmessagesRef.current = next;\n\t\t\tsetMessages(next);\n\t\t},\n\t\t[],\n\t);\n\n\tconst setThreadResponseId = useCallback((responseId: string | undefined) => {\n\t\tpreviousResponseIdRef.current = responseId;\n\t\tsetPreviousResponseId(responseId);\n\t}, []);\n\n\tconst save = useCallback(() => {\n\t\tconst id = sessionIdRef.current;\n\t\tif (!persist || !id) {\n\t\t\treturn;\n\t\t}\n\t\tvoid persistCopilotTurn({\n\t\t\troute: routeRef.current,\n\t\t\tsessionId: id,\n\t\t\tmessages: messagesRef.current,\n\t\t\tpreviousResponseId: previousResponseIdRef.current,\n\t\t\tstoreId,\n\t\t\tagent,\n\t\t});\n\t}, [agent, persist, storeId]);\n\n\t// Transport config for the async readers below. Held in a ref rather than in\n\t// the effect's deps: a host that passes an inline `headers` object would\n\t// otherwise re-run restore on every render and wipe the transcript.\n\tconst transportRef = useRef({\n\t\tbaseUrl,\n\t\tmessagesPath,\n\t\tfeedbackPath,\n\t\theaders,\n\t\tauthToken,\n\t\tgetAuthToken,\n\t\tfetchImpl,\n\t\tstoreId,\n\t\tloadHistory,\n\t\tuser,\n\t});\n\ttransportRef.current = {\n\t\tbaseUrl,\n\t\tmessagesPath,\n\t\tfeedbackPath,\n\t\theaders,\n\t\tauthToken,\n\t\tgetAuthToken,\n\t\tfetchImpl,\n\t\tstoreId,\n\t\tloadHistory,\n\t\tuser,\n\t};\n\n\tconst readHistoryPage = useCallback(async (page: number) => {\n\t\tconst transport = transportRef.current;\n\t\tconst id = sessionIdRef.current;\n\t\tif (!transport.loadHistory || !id) {\n\t\t\treturn null;\n\t\t}\n\t\tconst historyPage = await fetchCopilotMessages({\n\t\t\tbaseUrl: transport.baseUrl,\n\t\t\tendpointPath: transport.messagesPath,\n\t\t\theaders: transport.headers,\n\t\t\tauthToken: transport.authToken,\n\t\t\tgetAuthToken: transport.getAuthToken,\n\t\t\tfetchImpl: transport.fetchImpl,\n\t\t\tstoreId: transport.storeId,\n\t\t\tsessionId: id,\n\t\t\tpage,\n\t\t\tpageSize: COPILOT_HISTORY_PAGE_SIZE,\n\t\t});\n\t\treturn { ...historyPage, messages: asRestored(historyPage.messages) };\n\t}, []);\n\n\t// Resolve the store's conversation and read it back from IndexedDB.\n\t//\n\t// Deliberately not keyed on `route`: navigating is not a new chat, so a route\n\t// change must not restore, reset the transcript or abort a turn that is still\n\t// streaming. `routeRef` carries the current page into the next turn instead.\n\tuseEffect(() => {\n\t\tlet cancelled = false;\n\n\t\tconst restore = async () => {\n\t\t\tabortRef.current?.();\n\t\t\tabortRef.current = null;\n\t\t\tstreamingIdRef.current = null;\n\t\t\tsetIsStreaming(false);\n\t\t\tsetError(undefined);\n\t\t\tsetIsRestoring(true);\n\t\t\tsetHasMoreHistory(false);\n\t\t\tisLoadingHistoryRef.current = false;\n\t\t\tsetIsLoadingHistory(false);\n\n\t\t\tconst id =\n\t\t\t\tpinnedSessionId ??\n\t\t\t\t(persist\n\t\t\t\t\t? await resolveCopilotSessionId({ storeId })\n\t\t\t\t\t: getCopilotSessionId({ storeId }));\n\t\t\tconst record = persist ? await loadCopilotSession(id) : null;\n\t\t\tif (cancelled) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tsessionIdRef.current = id;\n\t\t\tsetSessionId(id);\n\t\t\tmessagesRef.current = asRestored(record?.messages ?? []);\n\t\t\tsetMessages(messagesRef.current);\n\t\t\tsetThreadResponseId(record?.previousResponseId);\n\t\t\tsetIsRestoring(false);\n\t\t\tonSessionIdChange?.(id);\n\n\t\t\t// The cache holds at most one page, so what is on screen already *is*\n\t\t\t// page 1 and the reader's first scroll-up wants page 2.\n\t\t\tif (messagesRef.current.length > 0) {\n\t\t\t\tnextHistoryPageRef.current = 2;\n\t\t\t\tsetHasMoreHistory(transportRef.current.loadHistory);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Nothing cached and nothing to scroll, so page 1 has to be fetched for\n\t\t\t// the history to be reachable at all — but only for an id the host\n\t\t\t// pinned. An id this library resolved came out of the local records, so\n\t\t\t// an empty cache for it means an empty conversation, and a freshly\n\t\t\t// minted one has nothing behind it by definition. Asking anyway would\n\t\t\t// put a pointless request on the start of every new conversation.\n\t\t\tif (!pinnedSessionId) {\n\t\t\t\tnextHistoryPageRef.current = 1;\n\t\t\t\tsetHasMoreHistory(false);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tnextHistoryPageRef.current = 1;\n\t\t\ttry {\n\t\t\t\tconst firstPage = await readHistoryPage(1);\n\t\t\t\tif (cancelled || !firstPage) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tnextHistoryPageRef.current = 2;\n\t\t\t\tapplyMessages(() => firstPage.messages);\n\t\t\t\tsetHasMoreHistory(firstPage.hasMore);\n\t\t\t} catch {\n\t\t\t\t// A session the backend has never heard of is the normal case for a\n\t\t\t\t// host-pinned id on its first use; the empty state covers it.\n\t\t\t\tsetHasMoreHistory(false);\n\t\t\t}\n\t\t};\n\n\t\tvoid restore();\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t};\n\t\t// onSessionIdChange is event-like: re-running restore because the host\n\t\t// passed a new inline callback would wipe the transcript.\n\t\t// eslint-disable-next-line react-hooks/exhaustive-deps\n\t}, [\n\t\tstoreId,\n\t\tpinnedSessionId,\n\t\tpersist,\n\t\tsetThreadResponseId,\n\t\tapplyMessages,\n\t\treadHistoryPage,\n\t]);\n\n\t/**\n\t * Prepends the page before the oldest message on screen.\n\t *\n\t * Guarded by a ref rather than the state flag because a scroll handler can\n\t * fire many times before React re-renders, and each of those would otherwise\n\t * start its own request for the same page.\n\t */\n\tconst loadOlderMessages = useCallback(() => {\n\t\tif (isLoadingHistoryRef.current || !hasMoreHistory) {\n\t\t\treturn;\n\t\t}\n\t\tisLoadingHistoryRef.current = true;\n\t\tsetIsLoadingHistory(true);\n\n\t\tconst page = nextHistoryPageRef.current;\n\t\tvoid (async () => {\n\t\t\ttry {\n\t\t\t\tconst older = await readHistoryPage(page);\n\t\t\t\tif (!older) {\n\t\t\t\t\tsetHasMoreHistory(false);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tnextHistoryPageRef.current = page + 1;\n\t\t\t\tsetHasMoreHistory(older.hasMore);\n\t\t\t\tapplyMessages((previous) => {\n\t\t\t\t\tconst known = new Set(\n\t\t\t\t\t\tprevious.flatMap((message) =>\n\t\t\t\t\t\t\tmessage.messageId\n\t\t\t\t\t\t\t\t? [message.id, message.messageId]\n\t\t\t\t\t\t\t\t: [message.id],\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tconst fresh = older.messages.filter(\n\t\t\t\t\t\t(message) => !known.has(message.id),\n\t\t\t\t\t);\n\t\t\t\t\treturn fresh.length > 0 ? [...fresh, ...previous] : previous;\n\t\t\t\t});\n\t\t\t} catch (historyError) {\n\t\t\t\t// Left silent in the transcript on purpose: failing to reach further\n\t\t\t\t// back is not a failure of the conversation on screen. Another\n\t\t\t\t// scroll-up retries the same page.\n\t\t\t\tonError?.(\n\t\t\t\t\thistoryError instanceof Error\n\t\t\t\t\t\t? historyError\n\t\t\t\t\t\t: new Error(String(historyError)),\n\t\t\t\t);\n\t\t\t} finally {\n\t\t\t\tisLoadingHistoryRef.current = false;\n\t\t\t\tsetIsLoadingHistory(false);\n\t\t\t}\n\t\t})();\n\t}, [applyMessages, hasMoreHistory, onError, readHistoryPage]);\n\n\t// Records pile up one per route per page load otherwise, since every refresh\n\t// writes under a new key.\n\tuseEffect(() => {\n\t\tif (persist) {\n\t\t\tvoid pruneCopilotSessions();\n\t\t}\n\t}, [persist]);\n\n\t// Abort an in-flight turn if the copilot unmounts.\n\tuseEffect(() => () => abortRef.current?.(), []);\n\n\tconst finishStreamingBubble = useCallback(\n\t\t(mutate?: (message: CopilotMessage) => CopilotMessage) => {\n\t\t\tconst streamingId = streamingIdRef.current;\n\t\t\tstreamingIdRef.current = null;\n\t\t\tif (!streamingId) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tapplyMessages((previous) =>\n\t\t\t\tprevious\n\t\t\t\t\t.map((message) => {\n\t\t\t\t\t\tif (message.id !== streamingId) {\n\t\t\t\t\t\t\treturn message;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst settled: CopilotMessage = {\n\t\t\t\t\t\t\t...message,\n\t\t\t\t\t\t\tisStreaming: false,\n\t\t\t\t\t\t};\n\t\t\t\t\t\treturn mutate ? mutate(settled) : settled;\n\t\t\t\t\t})\n\t\t\t\t\t// An assistant bubble that never received text would render as an\n\t\t\t\t\t// empty balloon; the error banner carries the information instead.\n\t\t\t\t\t// A stopped turn keeps its bubble either way — the interrupted note\n\t\t\t\t\t// is the only trace a turn the reader cut short ever happened.\n\t\t\t\t\t.filter(\n\t\t\t\t\t\t(message) =>\n\t\t\t\t\t\t\tmessage.id !== streamingId ||\n\t\t\t\t\t\t\tmessage.content.length > 0 ||\n\t\t\t\t\t\t\tmessage.wasInterrupted === true,\n\t\t\t\t\t),\n\t\t\t);\n\t\t},\n\t\t[applyMessages],\n\t);\n\n\tconst handleEvent = useCallback(\n\t\t(event: CopilotStreamEvent) => {\n\t\t\tconst streamingId = streamingIdRef.current;\n\n\t\t\tif (event.type === 'STREAM') {\n\t\t\t\tconst chunk = readCopilotEventContent(event).text;\n\t\t\t\tif (!streamingId || !chunk) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tapplyMessages((previous) =>\n\t\t\t\t\tprevious.map((message) =>\n\t\t\t\t\t\tmessage.id === streamingId\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t...message,\n\t\t\t\t\t\t\t\t\tcontent: message.content + chunk,\n\t\t\t\t\t\t\t\t\tmessageId: event.messageId ?? message.messageId,\n\t\t\t\t\t\t\t }\n\t\t\t\t\t\t\t: message,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (event.type === 'TEXT') {\n\t\t\t\t// TEXT carries the whole message, so it replaces the accumulated\n\t\t\t\t// chunks rather than appending — that keeps the bubble correct even if\n\t\t\t\t// a STREAM frame was dropped or duplicated. `responseId` rides inside\n\t\t\t\t// the content payload (as `lastResponseId`).\n\t\t\t\tconst {\n\t\t\t\t\ttext: fullText,\n\t\t\t\t\tfollowUps,\n\t\t\t\t\ttoolResults,\n\t\t\t\t\tresponseId,\n\t\t\t\t} = readCopilotEventContent(event);\n\t\t\t\tfinishStreamingBubble((message) => ({\n\t\t\t\t\t...message,\n\t\t\t\t\tcontent: fullText,\n\t\t\t\t\tmessageId: event.messageId ?? message.messageId,\n\t\t\t\t\t...(followUps ? { followUps } : {}),\n\t\t\t\t\t// Kept on the message, not just handled in passing, so the\n\t\t\t\t\t// action button is still there after a reload.\n\t\t\t\t\t...(toolResults ? { toolResults } : {}),\n\t\t\t\t}));\n\t\t\t\tsetThreadResponseId(responseId);\n\t\t\t\tsave();\n\t\t\t\tonResponseComplete?.({\n\t\t\t\t\ttext: fullText,\n\t\t\t\t\tresponseId,\n\t\t\t\t\tfollowUps,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (event.type === 'ERROR') {\n\t\t\t\tsetError(readCopilotEventContent(event).text || GENERIC_ERROR);\n\t\t\t\tfinishStreamingBubble();\n\t\t\t\tsave();\n\t\t\t}\n\n\t\t\t// Unknown types (TOOL_STATUS is declared server side but not emitted)\n\t\t\t// are ignored so a new event type cannot break an existing client.\n\t\t},\n\t\t[\n\t\t\tapplyMessages,\n\t\t\tfinishStreamingBubble,\n\t\t\tonResponseComplete,\n\t\t\tsave,\n\t\t\tsetThreadResponseId,\n\t\t],\n\t);\n\n\tconst sendMessage = useCallback(\n\t\t(message: string) => {\n\t\t\tconst userPrompt = message.trim();\n\t\t\tconst id = sessionIdRef.current;\n\t\t\tif (!userPrompt || !id || isStreaming) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tsetError(undefined);\n\t\t\tconst now = Date.now();\n\t\t\tconst assistantBubbleId = `copilot-a-${now}`;\n\t\t\tstreamingIdRef.current = assistantBubbleId;\n\n\t\t\tapplyMessages((previous) => [\n\t\t\t\t...previous,\n\t\t\t\t{\n\t\t\t\t\tid: `copilot-u-${now}`,\n\t\t\t\t\trole: 'user',\n\t\t\t\t\tcontent: userPrompt,\n\t\t\t\t\ttimestamp: now,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid: assistantBubbleId,\n\t\t\t\t\trole: 'assistant',\n\t\t\t\t\tcontent: '',\n\t\t\t\t\ttimestamp: now + 1,\n\t\t\t\t\tisStreaming: true,\n\t\t\t\t},\n\t\t\t]);\n\t\t\tsetIsStreaming(true);\n\t\t\tsave();\n\n\t\t\tabortRef.current = streamCopilotChat(\n\t\t\t\t{\n\t\t\t\t\tbaseUrl,\n\t\t\t\t\tendpointPath,\n\t\t\t\t\theaders,\n\t\t\t\t\tauthToken,\n\t\t\t\t\tgetAuthToken,\n\t\t\t\t\tfetchImpl,\n\t\t\t\t\tstoreId,\n\t\t\t\t\tagent,\n\t\t\t\t\troute,\n\t\t\t\t\tuserPrompt,\n\t\t\t\t\tsessionId: id,\n\t\t\t\t\tpreviousResponseId: previousResponseIdRef.current,\n\t\t\t\t\tsystemPrompt,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tonEvent: handleEvent,\n\t\t\t\t\tonComplete: () => {\n\t\t\t\t\t\t// STOP closes the stream without a frame, so this is where a turn\n\t\t\t\t\t\t// ends. TEXT has usually already settled the bubble.\n\t\t\t\t\t\tfinishStreamingBubble();\n\t\t\t\t\t\tsetIsStreaming(false);\n\t\t\t\t\t\tabortRef.current = null;\n\t\t\t\t\t\tsave();\n\t\t\t\t\t},\n\t\t\t\t\tonError: (streamError) => {\n\t\t\t\t\t\tsetError(streamError.message || GENERIC_ERROR);\n\t\t\t\t\t\tfinishStreamingBubble();\n\t\t\t\t\t\tsetIsStreaming(false);\n\t\t\t\t\t\tabortRef.current = null;\n\t\t\t\t\t\tsave();\n\t\t\t\t\t\tonError?.(streamError);\n\t\t\t\t\t},\n\t\t\t\t\tonAbort: () => {\n\t\t\t\t\t\tfinishStreamingBubble();\n\t\t\t\t\t\tsetIsStreaming(false);\n\t\t\t\t\t\tabortRef.current = null;\n\t\t\t\t\t\tsave();\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\t[\n\t\t\tapplyMessages,\n\t\t\tauthToken,\n\t\t\tbaseUrl,\n\t\t\tendpointPath,\n\t\t\tfetchImpl,\n\t\t\tagent,\n\t\t\tfinishStreamingBubble,\n\t\t\tgetAuthToken,\n\t\t\thandleEvent,\n\t\t\theaders,\n\t\t\tisStreaming,\n\t\t\tonError,\n\t\t\troute,\n\t\t\tsave,\n\t\t\tstoreId,\n\t\t\tsystemPrompt,\n\t\t],\n\t);\n\n\tconst regenerate = useCallback(() => {\n\t\tconst hasReply = messagesRef.current.some(\n\t\t\t(message) => message.role === 'assistant' && message.content.length > 0,\n\t\t);\n\t\tif (!hasReply) {\n\t\t\treturn;\n\t\t}\n\t\tsendMessage(COPILOT_REGENERATE_PROMPT);\n\t}, [sendMessage]);\n\n\t/**\n\t * Records a thumbs-up or thumbs-down on one reply.\n\t *\n\t * Fire-and-forget: a rating is a side note to the conversation, so a failed\n\t * post is reported through `onError` and otherwise ignored -- the thumb stays\n\t * lit either way rather than flickering back on a network hiccup.\n\t *\n\t * The reply's own text and the route ride along, so whoever reads the feedback\n\t * sees what was rated without looking the message up.\n\t */\n\tconst submitFeedback = useCallback(\n\t\t(\n\t\t\tmessageId: string,\n\t\t\tfeedback: CopilotFeedbackVote,\n\t\t\tdetail?: CopilotFeedbackDetail,\n\t\t) => {\n\t\t\tconst transport = transportRef.current;\n\t\t\tconst id = sessionIdRef.current;\n\t\t\tif (!id) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// A live turn's bubble carries only a local id until the final event\n\t\t\t// lands, so match on either and let the backend see whichever it gets.\n\t\t\tconst rated = messagesRef.current.find(\n\t\t\t\t(message) => (message.messageId ?? message.id) === messageId,\n\t\t\t);\n\t\t\tvoid sendCopilotFeedback({\n\t\t\t\tbaseUrl: transport.baseUrl,\n\t\t\t\tendpointPath: transport.feedbackPath,\n\t\t\t\theaders: transport.headers,\n\t\t\t\tauthToken: transport.authToken,\n\t\t\t\tgetAuthToken: transport.getAuthToken,\n\t\t\t\tfetchImpl: transport.fetchImpl,\n\t\t\t\tstoreId: transport.storeId,\n\t\t\t\tsessionId: id,\n\t\t\t\tmessageId,\n\t\t\t\tfeedback,\n\t\t\t\troute: routeRef.current,\n\t\t\t\treply: rated?.content,\n\t\t\t\tuser: transport.user,\n\t\t\t\treasons: detail?.reasons,\n\t\t\t\tcomment: detail?.comment,\n\t\t\t}).catch((feedbackError) => {\n\t\t\t\tonError?.(\n\t\t\t\t\tfeedbackError instanceof Error\n\t\t\t\t\t\t? feedbackError\n\t\t\t\t\t\t: new Error(String(feedbackError)),\n\t\t\t\t);\n\t\t\t});\n\t\t},\n\t\t[onError],\n\t);\n\n\tconst stopStreaming = useCallback(() => {\n\t\tif (!abortRef.current) {\n\t\t\treturn;\n\t\t}\n\t\t// Flagged here rather than in `onAbort`, because an abort that lands between\n\t\t// frames ends the stream through `onComplete` instead. Whichever fires, the\n\t\t// reader stopped this turn, and whatever text had arrived stays.\n\t\tconst streamingId = streamingIdRef.current;\n\t\tif (streamingId) {\n\t\t\tapplyMessages((previous) =>\n\t\t\t\tprevious.map((message) =>\n\t\t\t\t\tmessage.id === streamingId\n\t\t\t\t\t\t? { ...message, wasInterrupted: true }\n\t\t\t\t\t\t: message,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tabortRef.current();\n\t\tabortRef.current = null;\n\t}, [applyMessages]);\n\n\tconst startNewConversation = useCallback(() => {\n\t\tabortRef.current?.();\n\t\tabortRef.current = null;\n\t\tstreamingIdRef.current = null;\n\t\tsetIsStreaming(false);\n\t\tsetError(undefined);\n\t\t// A new conversation has no history behind it; the old session's pages\n\t\t// belong to the old session id.\n\t\tnextHistoryPageRef.current = 1;\n\t\tisLoadingHistoryRef.current = false;\n\t\tsetIsLoadingHistory(false);\n\t\tsetHasMoreHistory(false);\n\n\t\t// The conversation being cleared, so its stored transcript can go with it.\n\t\tconst clearedId = sessionIdRef.current;\n\t\t// A host-pinned id is the host's to change, so only clear its transcript.\n\t\tconst nextId = pinnedSessionId ?? resetCopilotSession({ storeId });\n\t\tsessionIdRef.current = nextId;\n\t\tsetSessionId(nextId);\n\t\tmessagesRef.current = [];\n\t\tsetMessages([]);\n\t\tsetThreadResponseId(undefined);\n\n\t\t// \"Refresh chat\" wipes the record, not just the screen: the cleared\n\t\t// conversation is deleted from IndexedDB so reopening the tab starts fresh\n\t\t// instead of resuming it. Nothing is written for the new session here — its\n\t\t// first record is stamped when the first turn is sent, which is also what\n\t\t// keeps an untouched refresh from leaving an empty record behind.\n\t\tif (persist && clearedId) {\n\t\t\tvoid deleteCopilotSession(clearedId);\n\t\t}\n\t\tonSessionIdChange?.(nextId);\n\t}, [\n\t\tonSessionIdChange,\n\t\tpersist,\n\t\tpinnedSessionId,\n\t\tsetThreadResponseId,\n\t\tstoreId,\n\t]);\n\n\t// The thinking bubble is only for the wait before the first token; once text\n\t// starts arriving the bubble itself shows progress.\n\tconst isThinking = useMemo(\n\t\t() =>\n\t\t\tisStreaming &&\n\t\t\t!messages.some((message) => message.isStreaming && message.content),\n\t\t[isStreaming, messages],\n\t);\n\n\treturn {\n\t\tmessages,\n\t\tsessionId,\n\t\tpreviousResponseId,\n\t\tisStreaming,\n\t\tisThinking,\n\t\tisRestoring,\n\t\tisLoadingHistory,\n\t\thasMoreHistory,\n\t\terror,\n\t\tsendMessage,\n\t\tregenerate,\n\t\tsubmitFeedback,\n\t\tloadOlderMessages,\n\t\tstopStreaming,\n\t\tstartNewConversation,\n\t};\n};\n"],"names":["GENERIC_ERROR","asRestored","messages","message","useCopilotStreamChat","storeId","agent","DEFAULT_COPILOT_AGENT","route","DEFAULT_COPILOT_ROUTE","messagesPath","feedbackPath","user","loadHistory","systemPrompt","pinnedSessionId","persist","onError","onResponseComplete","onSessionIdChange","baseUrl","endpointPath","headers","authToken","getAuthToken","fetchImpl","setMessages","useState","sessionId","setSessionId","isStreaming","setIsStreaming","isRestoring","setIsRestoring","error","setError","previousResponseId","setPreviousResponseId","isLoadingHistory","setIsLoadingHistory","hasMoreHistory","setHasMoreHistory","messagesRef","useRef","previousResponseIdRef","streamingIdRef","abortRef","sessionIdRef","routeRef","nextHistoryPageRef","isLoadingHistoryRef","applyMessages","useCallback","updater","next","setThreadResponseId","responseId","save","id","persistCopilotTurn","transportRef","readHistoryPage","page","transport","historyPage","fetchCopilotMessages","COPILOT_HISTORY_PAGE_SIZE","useEffect","cancelled","resolveCopilotSessionId","getCopilotSessionId","record","loadCopilotSession","firstPage","loadOlderMessages","older","previous","known","fresh","historyError","pruneCopilotSessions","finishStreamingBubble","mutate","streamingId","settled","handleEvent","event","chunk","readCopilotEventContent","fullText","followUps","toolResults","sendMessage","userPrompt","now","assistantBubbleId","streamCopilotChat","streamError","regenerate","COPILOT_REGENERATE_PROMPT","submitFeedback","messageId","feedback","detail","rated","sendCopilotFeedback","feedbackError","stopStreaming","startNewConversation","clearedId","nextId","resetCopilotSession","deleteCopilotSession","isThinking","useMemo"],"mappings":"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"}