@bikdotai/bik-component-library 0.0.856 → 0.0.857-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.
Files changed (93) hide show
  1. package/README.md +25 -4
  2. package/dist/cjs/components/bik-copilot/BIKCopilot.js +2 -0
  3. package/dist/cjs/components/bik-copilot/BIKCopilot.js.map +1 -0
  4. package/dist/cjs/components/bik-copilot/ControlledCopilot.js +2 -0
  5. package/dist/cjs/components/bik-copilot/ControlledCopilot.js.map +1 -0
  6. package/dist/cjs/components/bik-copilot/ControlledCopilot.styled.js +27 -0
  7. package/dist/cjs/components/bik-copilot/ControlledCopilot.styled.js.map +1 -0
  8. package/dist/cjs/components/bik-copilot/CopilotBall.js +2 -0
  9. package/dist/cjs/components/bik-copilot/CopilotBall.js.map +1 -0
  10. package/dist/cjs/components/bik-copilot/CopilotBall.styled.js +42 -0
  11. package/dist/cjs/components/bik-copilot/CopilotBall.styled.js.map +1 -0
  12. package/dist/cjs/components/bik-copilot/CopilotPanel.js +2 -0
  13. package/dist/cjs/components/bik-copilot/CopilotPanel.js.map +1 -0
  14. package/dist/cjs/components/bik-copilot/CopilotPanel.styled.js +419 -0
  15. package/dist/cjs/components/bik-copilot/CopilotPanel.styled.js.map +1 -0
  16. package/dist/cjs/components/bik-copilot/copilotPlacement.js +2 -0
  17. package/dist/cjs/components/bik-copilot/copilotPlacement.js.map +1 -0
  18. package/dist/cjs/components/bik-copilot/services/copilotDb.js +2 -0
  19. package/dist/cjs/components/bik-copilot/services/copilotDb.js.map +1 -0
  20. package/dist/cjs/components/bik-copilot/services/copilotIcebreakersClient.js +2 -0
  21. package/dist/cjs/components/bik-copilot/services/copilotIcebreakersClient.js.map +1 -0
  22. package/dist/cjs/components/bik-copilot/services/copilotMessagesClient.js +2 -0
  23. package/dist/cjs/components/bik-copilot/services/copilotMessagesClient.js.map +1 -0
  24. package/dist/cjs/components/bik-copilot/services/copilotPageSession.js +2 -0
  25. package/dist/cjs/components/bik-copilot/services/copilotPageSession.js.map +1 -0
  26. package/dist/cjs/components/bik-copilot/services/copilotSseParser.js +7 -0
  27. package/dist/cjs/components/bik-copilot/services/copilotSseParser.js.map +1 -0
  28. package/dist/cjs/components/bik-copilot/services/copilotStreamClient.js +2 -0
  29. package/dist/cjs/components/bik-copilot/services/copilotStreamClient.js.map +1 -0
  30. package/dist/cjs/components/bik-copilot/useCopilotIcebreakers.js +2 -0
  31. package/dist/cjs/components/bik-copilot/useCopilotIcebreakers.js.map +1 -0
  32. package/dist/cjs/components/bik-copilot/useCopilotStreamChat.js +2 -0
  33. package/dist/cjs/components/bik-copilot/useCopilotStreamChat.js.map +1 -0
  34. package/dist/cjs/components/bik-copilot/useDraggableFloater.js +2 -0
  35. package/dist/cjs/components/bik-copilot/useDraggableFloater.js.map +1 -0
  36. package/dist/cjs/index.js +1 -1
  37. package/dist/esm/components/bik-copilot/BIKCopilot.d.ts +32 -0
  38. package/dist/esm/components/bik-copilot/BIKCopilot.js +114 -0
  39. package/dist/esm/components/bik-copilot/BIKCopilot.js.map +1 -0
  40. package/dist/esm/components/bik-copilot/ControlledCopilot.d.ts +14 -0
  41. package/dist/esm/components/bik-copilot/ControlledCopilot.js +170 -0
  42. package/dist/esm/components/bik-copilot/ControlledCopilot.js.map +1 -0
  43. package/dist/esm/components/bik-copilot/ControlledCopilot.styled.d.ts +7 -0
  44. package/dist/esm/components/bik-copilot/ControlledCopilot.styled.js +31 -0
  45. package/dist/esm/components/bik-copilot/ControlledCopilot.styled.js.map +1 -0
  46. package/dist/esm/components/bik-copilot/CopilotBall.d.ts +13 -0
  47. package/dist/esm/components/bik-copilot/CopilotBall.js +50 -0
  48. package/dist/esm/components/bik-copilot/CopilotBall.js.map +1 -0
  49. package/dist/esm/components/bik-copilot/CopilotBall.styled.d.ts +9 -0
  50. package/dist/esm/components/bik-copilot/CopilotBall.styled.js +46 -0
  51. package/dist/esm/components/bik-copilot/CopilotBall.styled.js.map +1 -0
  52. package/dist/esm/components/bik-copilot/CopilotPanel.d.ts +9 -0
  53. package/dist/esm/components/bik-copilot/CopilotPanel.js +309 -0
  54. package/dist/esm/components/bik-copilot/CopilotPanel.js.map +1 -0
  55. package/dist/esm/components/bik-copilot/CopilotPanel.styled.d.ts +58 -0
  56. package/dist/esm/components/bik-copilot/CopilotPanel.styled.js +453 -0
  57. package/dist/esm/components/bik-copilot/CopilotPanel.styled.js.map +1 -0
  58. package/dist/esm/components/bik-copilot/copilotPlacement.d.ts +24 -0
  59. package/dist/esm/components/bik-copilot/copilotPlacement.js +38 -0
  60. package/dist/esm/components/bik-copilot/copilotPlacement.js.map +1 -0
  61. package/dist/esm/components/bik-copilot/index.d.ts +16 -0
  62. package/dist/esm/components/bik-copilot/model.d.ts +674 -0
  63. package/dist/esm/components/bik-copilot/services/copilotDb.d.ts +90 -0
  64. package/dist/esm/components/bik-copilot/services/copilotDb.js +96 -0
  65. package/dist/esm/components/bik-copilot/services/copilotDb.js.map +1 -0
  66. package/dist/esm/components/bik-copilot/services/copilotIcebreakersClient.d.ts +11 -0
  67. package/dist/esm/components/bik-copilot/services/copilotIcebreakersClient.js +54 -0
  68. package/dist/esm/components/bik-copilot/services/copilotIcebreakersClient.js.map +1 -0
  69. package/dist/esm/components/bik-copilot/services/copilotMessagesClient.d.ts +23 -0
  70. package/dist/esm/components/bik-copilot/services/copilotMessagesClient.js +78 -0
  71. package/dist/esm/components/bik-copilot/services/copilotMessagesClient.js.map +1 -0
  72. package/dist/esm/components/bik-copilot/services/copilotPageSession.d.ts +37 -0
  73. package/dist/esm/components/bik-copilot/services/copilotPageSession.js +35 -0
  74. package/dist/esm/components/bik-copilot/services/copilotPageSession.js.map +1 -0
  75. package/dist/esm/components/bik-copilot/services/copilotSseParser.d.ts +22 -0
  76. package/dist/esm/components/bik-copilot/services/copilotSseParser.js +52 -0
  77. package/dist/esm/components/bik-copilot/services/copilotSseParser.js.map +1 -0
  78. package/dist/esm/components/bik-copilot/services/copilotStreamClient.d.ts +31 -0
  79. package/dist/esm/components/bik-copilot/services/copilotStreamClient.js +103 -0
  80. package/dist/esm/components/bik-copilot/services/copilotStreamClient.js.map +1 -0
  81. package/dist/esm/components/bik-copilot/useCopilotIcebreakers.d.ts +17 -0
  82. package/dist/esm/components/bik-copilot/useCopilotIcebreakers.js +60 -0
  83. package/dist/esm/components/bik-copilot/useCopilotIcebreakers.js.map +1 -0
  84. package/dist/esm/components/bik-copilot/useCopilotStreamChat.d.ts +20 -0
  85. package/dist/esm/components/bik-copilot/useCopilotStreamChat.js +333 -0
  86. package/dist/esm/components/bik-copilot/useCopilotStreamChat.js.map +1 -0
  87. package/dist/esm/components/bik-copilot/useDraggableFloater.d.ts +9 -0
  88. package/dist/esm/components/bik-copilot/useDraggableFloater.js +118 -0
  89. package/dist/esm/components/bik-copilot/useDraggableFloater.js.map +1 -0
  90. package/dist/esm/index.d.ts +1 -0
  91. package/dist/esm/index.js +793 -743
  92. package/dist/esm/index.js.map +1 -1
  93. package/package.json +1 -1
@@ -0,0 +1,674 @@
1
+ import { CSSProperties, ReactNode, PointerEvent as ReactPointerEvent } from 'react';
2
+ import { ChatMessage, IceBreaker } from '../bik-chatbot/types';
3
+ /**
4
+ * Corner of the viewport the ball is parked in before the user drags it.
5
+ */
6
+ export type CopilotCorner = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
7
+ /**
8
+ * Absolute viewport coordinates (px) of the ball's top-left corner.
9
+ */
10
+ export interface CopilotBallPosition {
11
+ x: number;
12
+ y: number;
13
+ }
14
+ /**
15
+ * Viewport size the ball and panel are positioned against.
16
+ */
17
+ export interface CopilotViewport {
18
+ width: number;
19
+ height: number;
20
+ }
21
+ /**
22
+ * Presentation and behaviour props shared by every copilot entry point.
23
+ */
24
+ export interface CopilotShellProps {
25
+ /**
26
+ * Diameter of the draggable ball in px.
27
+ * @default 56
28
+ */
29
+ ballSize?: number;
30
+ /**
31
+ * Viewport corner the ball starts in. Ignored when `initialPosition` is set.
32
+ * @default 'bottom-right'
33
+ */
34
+ corner?: CopilotCorner;
35
+ /**
36
+ * Distance in px kept between the ball and the viewport edges it is parked
37
+ * against.
38
+ * @default 24
39
+ */
40
+ offset?: number;
41
+ /**
42
+ * Exact starting coordinates of the ball's top-left corner. Takes precedence
43
+ * over `corner` and is clamped into the viewport.
44
+ */
45
+ initialPosition?: CopilotBallPosition;
46
+ /**
47
+ * Allows the user to drag the ball anywhere in the viewport. When `false` the
48
+ * ball stays parked and only acts as a button.
49
+ * @default true
50
+ */
51
+ draggable?: boolean;
52
+ /**
53
+ * Animates the ball back to the nearest left/right edge after a drag ends,
54
+ * keeping the vertical position the user chose.
55
+ * @default true
56
+ */
57
+ snapToEdge?: boolean;
58
+ /** Fired after a drag gesture settles, with the ball's final position. */
59
+ onPositionChange?: (position: CopilotBallPosition) => void;
60
+ /** Icon rendered inside the ball while the panel is closed. */
61
+ ballIcon?: ReactNode;
62
+ /** Icon rendered inside the ball while the panel is open. */
63
+ ballOpenIcon?: ReactNode;
64
+ /**
65
+ * Background of the ball. Accepts any CSS background value, so gradients work.
66
+ * @default COLORS.background.brand
67
+ */
68
+ ballColor?: string;
69
+ /**
70
+ * Stacking order for both the ball and the panel.
71
+ * @default 1200
72
+ */
73
+ zIndex?: number;
74
+ /** Accessible label announced for the ball. */
75
+ ballAriaLabel?: string;
76
+ /**
77
+ * Panel width in px. Shrinks automatically on narrow viewports.
78
+ * @default 380
79
+ */
80
+ panelWidth?: number;
81
+ /**
82
+ * Panel height in px. Shrinks automatically on short viewports.
83
+ * @default 540
84
+ */
85
+ panelHeight?: number;
86
+ /**
87
+ * Gap in px between the ball and the panel.
88
+ * @default 12
89
+ */
90
+ panelGap?: number;
91
+ /**
92
+ * Title shown in the panel header.
93
+ * @default 'Copilot'
94
+ */
95
+ title?: string;
96
+ /** Optional pill rendered next to the title, e.g. `"New"`. */
97
+ badge?: string;
98
+ /** Secondary line rendered under the title. */
99
+ subtitle?: string;
100
+ /** Custom node rendered as the header avatar. */
101
+ avatar?: ReactNode;
102
+ /**
103
+ * Placeholder for the message input.
104
+ * @default 'Write your message...'
105
+ */
106
+ placeholder?: string;
107
+ /**
108
+ * Fine print rendered under the input. Pass an empty string to hide it.
109
+ * @default 'AI can make mistakes, Verify the responses.'
110
+ */
111
+ disclaimer?: string;
112
+ /**
113
+ * Headline of the empty state shown before the first message.
114
+ * @default the `title` value
115
+ */
116
+ emptyStateTitle?: string;
117
+ /**
118
+ * Sub-headline of the empty state shown before the first message.
119
+ * @default 'Here are some things you can try'
120
+ */
121
+ emptyStateSubtitle?: string;
122
+ /**
123
+ * Quick-start prompts shown in the empty state. Clicking one sends it as a
124
+ * user message.
125
+ */
126
+ suggestions?: IceBreaker[];
127
+ /** Fired when a suggestion is clicked, before the message is sent. */
128
+ onSuggestionClick?: (suggestion: IceBreaker) => void;
129
+ /**
130
+ * Shows the reset button in the header. Only rendered when `onRefresh` is
131
+ * also provided.
132
+ * @default true
133
+ */
134
+ showRefresh?: boolean;
135
+ /** Fired when the header reset button is clicked. */
136
+ onRefresh?: () => void;
137
+ /**
138
+ * Disables the input and suggestions without hiding them.
139
+ * @default false
140
+ */
141
+ inputDisabled?: boolean;
142
+ /**
143
+ * Label rendered beside the animated dots while a reply is pending. Pass an
144
+ * empty string for dots only.
145
+ * @default 'Thinking'
146
+ */
147
+ thinkingLabel?: string;
148
+ /**
149
+ * Note shown in place of — or under — a reply the reader stopped. Pass an
150
+ * empty string to hide it.
151
+ * @default 'This message was interrupted'
152
+ */
153
+ interruptedLabel?: string;
154
+ /**
155
+ * Caption above the follow-up chips the backend suggests. Pass an empty
156
+ * string to show the chips with no caption.
157
+ * @default 'Follow ups'
158
+ */
159
+ followUpsLabel?: string;
160
+ /**
161
+ * Caption shown above the transcript while an older page is being fetched.
162
+ * @default 'Loading earlier messages'
163
+ */
164
+ historyLoadingLabel?: string;
165
+ /**
166
+ * Controls the panel from the outside. When set, the component stops managing
167
+ * its own open state and `onOpenChange` must apply the change.
168
+ */
169
+ open?: boolean;
170
+ /**
171
+ * Whether the panel starts open. Ignored when `open` is provided.
172
+ * @default false
173
+ */
174
+ defaultOpen?: boolean;
175
+ /** Fired whenever the panel is opened or closed. */
176
+ onOpenChange?: (open: boolean) => void;
177
+ /**
178
+ * Closes the panel when the user clicks anywhere outside it.
179
+ * @default false
180
+ */
181
+ closeOnClickOutside?: boolean;
182
+ /** Class applied to the panel. */
183
+ className?: string;
184
+ /** Inline styles merged onto the panel. */
185
+ style?: CSSProperties;
186
+ }
187
+ /**
188
+ * Props for the fully controlled copilot: the host owns the transcript and the
189
+ * send handler, so it works with any backend.
190
+ */
191
+ export interface ControlledCopilotProps extends CopilotShellProps {
192
+ /** Conversation rendered in the panel, oldest message first. */
193
+ messages: CopilotMessage[];
194
+ /** Called with the trimmed message text when the user sends or picks a suggestion. */
195
+ onSendMessage: (message: string) => void;
196
+ /**
197
+ * Renders the thinking indicator and blocks sending while a reply is pending.
198
+ * @default false
199
+ */
200
+ isThinking?: boolean;
201
+ /**
202
+ * True for the whole turn, from send until the answer is complete. Turns the
203
+ * send button into a stop button while `onStop` is provided.
204
+ * @default false
205
+ */
206
+ isStreaming?: boolean;
207
+ /**
208
+ * Called when the reader presses stop. Without it the button stays a send
209
+ * button, since there would be nothing to stop.
210
+ */
211
+ onStop?: () => void;
212
+ /**
213
+ * Offers older messages when the reader scrolls to the top. Without
214
+ * `onLoadOlderMessages` nothing is requested, however this is set.
215
+ * @default false
216
+ */
217
+ hasMoreHistory?: boolean;
218
+ /** Shows the loading row above the transcript. @default false */
219
+ isLoadingHistory?: boolean;
220
+ /**
221
+ * Called when the reader scrolls to the top and there is more history. Fires
222
+ * on scroll, so the handler must tolerate being called again while a page is
223
+ * already on its way.
224
+ */
225
+ onLoadOlderMessages?: () => void;
226
+ /** Error banner rendered above the input. */
227
+ errorMessage?: string;
228
+ }
229
+ /**
230
+ * Agents the merchant agent factory can resolve. The backend validates this
231
+ * against its own enum and rejects anything else, so a new agent has to be added
232
+ * here and there.
233
+ */
234
+ export type CopilotAgent = 'BIK_ASSISTANT_AGENT';
235
+ /**
236
+ * One SSE payload from `/bik-ai/copilot-chatbot`.
237
+ *
238
+ * The endpoint sends `data: {json}` frames with no `event:` name — `type` is the
239
+ * discriminator. `STOP` is never a frame: the server ends the HTTP stream
240
+ * instead, so completion is "the stream closed".
241
+ */
242
+ /**
243
+ * Structured `content` of a `TEXT` event. `STREAM` and `ERROR` still carry a
244
+ * bare string, and so did `TEXT` before the backend wrapped it, so both shapes
245
+ * have to be read — see `readCopilotEventContent`.
246
+ */
247
+ export interface CopilotAnswerPayload {
248
+ /** `'ANSWER'` today. Unknown kinds are read for their `message` all the same. */
249
+ type?: string;
250
+ /** The reply itself, as markdown. */
251
+ message?: string;
252
+ /** Suggested next prompts, rendered as chips under the reply. */
253
+ followUps?: string[];
254
+ }
255
+ export interface CopilotStreamEvent {
256
+ /**
257
+ * `STREAM` for an incremental chunk, `TEXT` for the final full message,
258
+ * `ERROR` when the turn failed. `TOOL_STATUS` is reserved by the backend but
259
+ * not emitted yet.
260
+ */
261
+ type: 'STREAM' | 'TEXT' | 'ERROR' | 'TOOL_STATUS' | string;
262
+ /**
263
+ * Chunk text on `STREAM`, the complete answer on `TEXT`, the reason on
264
+ * `ERROR`. `TEXT` wraps it in a `CopilotAnswerPayload`.
265
+ */
266
+ content?: string | CopilotAnswerPayload;
267
+ /** Server-side id of the message being streamed; stable across its chunks. */
268
+ messageId?: string;
269
+ /**
270
+ * OpenAI's response id, present on `TEXT` only. Echo it back as
271
+ * `previousResponseId` next turn to keep the thread's context. Absent when
272
+ * the turn broke halfway — the backend drops the thread on purpose then.
273
+ */
274
+ responseId?: string;
275
+ storeId?: string;
276
+ sessionId?: string;
277
+ }
278
+ /** Body of one streaming copilot turn. */
279
+ export interface CopilotStreamRequest {
280
+ storeId: string;
281
+ agent: CopilotAgent;
282
+ /** Dashboard route the merchant is on, e.g. `'campaigns/overview'`. */
283
+ route: string;
284
+ userPrompt: string;
285
+ sessionId: string;
286
+ /** Signed-in merchant user, sent as `email`. Omitted when the host has none. */
287
+ email?: string;
288
+ /** Omitted on the first turn, then the previous turn's `responseId`. */
289
+ previousResponseId?: string;
290
+ /** Overrides the agent's server-side system prompt. */
291
+ systemPrompt?: string;
292
+ model?: string;
293
+ maxOutputTokens?: number;
294
+ }
295
+ /** How to reach the endpoint. */
296
+ export interface CopilotTransportOptions {
297
+ /** Origin of the AI backend, e.g. `'http://localhost:5001'`. */
298
+ baseUrl: string;
299
+ /**
300
+ * Path appended to `baseUrl`.
301
+ * @default '/bik-ai/copilot-chatbot'
302
+ */
303
+ endpointPath?: string;
304
+ /**
305
+ * Extra headers merged into the request — this is where `bik-referer` goes.
306
+ */
307
+ headers?: Record<string, string>;
308
+ /** Value of the `authorization` header. */
309
+ authToken?: string;
310
+ /**
311
+ * Resolves the `authorization` header per request. Prefer this over
312
+ * `authToken` when the token can be refreshed, since it is read on every turn.
313
+ */
314
+ getAuthToken?: () => string | Promise<string>;
315
+ /** Injectable `fetch`, for tests and for hosts with their own instrumentation. */
316
+ fetchImpl?: typeof fetch;
317
+ }
318
+ export type StreamCopilotChatOptions = CopilotStreamRequest & CopilotTransportOptions;
319
+ /** Options for `fetchCopilotIcebreakers`. */
320
+ export interface FetchCopilotIcebreakersOptions extends Omit<CopilotTransportOptions, 'endpointPath'> {
321
+ /**
322
+ * Path appended to `baseUrl`.
323
+ * @default '/bik-ai/copilot-icebreakers'
324
+ */
325
+ endpointPath?: string;
326
+ agent: CopilotAgent;
327
+ route: string;
328
+ /** Sent as `store_id`. Not used to resolve prompts today, but accepted. */
329
+ storeId?: string;
330
+ signal?: AbortSignal;
331
+ }
332
+ /** What the ice-breaker endpoint answers with. */
333
+ export interface CopilotIcebreakersResult {
334
+ agent: string;
335
+ /** The route the backend actually matched, normalized. */
336
+ route: string;
337
+ /** Human-readable page name behind the matched route, when it has one. */
338
+ pageName?: string;
339
+ /** Empty when no knowledge file covers the route — a valid answer. */
340
+ icebreakers: string[];
341
+ }
342
+ /** Options for `useCopilotIcebreakers`. */
343
+ export interface UseCopilotIcebreakersOptions extends Omit<CopilotTransportOptions, 'endpointPath'> {
344
+ /**
345
+ * Path appended to `baseUrl`.
346
+ * @default '/bik-ai/copilot-icebreakers'
347
+ */
348
+ icebreakersPath?: string;
349
+ /**
350
+ * @default 'BIK_ASSISTANT_AGENT'
351
+ */
352
+ agent?: CopilotAgent;
353
+ /**
354
+ * @default 'campaigns/overview'
355
+ */
356
+ route?: string;
357
+ storeId?: string;
358
+ /**
359
+ * Gates the request. The copilot passes `false` until the panel has been
360
+ * opened, so a merchant who never opens it costs no call.
361
+ * @default true
362
+ */
363
+ enabled?: boolean;
364
+ }
365
+ /** Return value of `useCopilotIcebreakers`. */
366
+ export interface UseCopilotIcebreakersResult {
367
+ /** Prompts as the panel wants them, ready for `suggestions`. */
368
+ icebreakers: IceBreaker[];
369
+ /** Page name behind the matched route, when the backend supplied one. */
370
+ pageName?: string;
371
+ /** The normalized route the backend matched. */
372
+ matchedRoute?: string;
373
+ isLoading: boolean;
374
+ /** Set when the request failed. An empty result is not an error. */
375
+ error?: string;
376
+ /** Refetches for the current agent + route. */
377
+ refresh: () => void;
378
+ }
379
+ /** Callbacks driven by `streamCopilotChat`. */
380
+ export interface CopilotStreamHandlers {
381
+ onEvent: (event: CopilotStreamEvent) => void;
382
+ /** The server closed the stream — the turn is done. */
383
+ onComplete?: () => void;
384
+ onError?: (error: Error) => void;
385
+ onAbort?: () => void;
386
+ }
387
+ /** A chat message, plus the streaming state the copilot needs. */
388
+ export interface CopilotMessage extends ChatMessage {
389
+ /** True while this assistant bubble is still being streamed into. */
390
+ isStreaming?: boolean;
391
+ /** `messageId` of the stream events that produced this bubble. */
392
+ messageId?: string;
393
+ /**
394
+ * True when the reader stopped this turn before the answer finished. Survives
395
+ * a reload, so the transcript still says the reply is incomplete.
396
+ */
397
+ wasInterrupted?: boolean;
398
+ /**
399
+ * Prompts the backend suggests as the next question. Only the newest reply's
400
+ * are offered — older ones stay in the transcript but are not rendered.
401
+ */
402
+ followUps?: string[];
403
+ }
404
+ /**
405
+ * One row of `/bik-ai/copilot-messages`, as the backend serializes it. Mapped
406
+ * onto `CopilotMessage` by `fetchCopilotMessages`.
407
+ */
408
+ export interface CopilotStoredMessageRow {
409
+ message_id?: string;
410
+ session_id?: string;
411
+ /** Page the merchant was on when they sent the prompt, normalized. */
412
+ route?: string | null;
413
+ agent?: string | null;
414
+ /** Who sent it. Read case-insensitively, so the enum can grow. */
415
+ owner?: string;
416
+ message?: string;
417
+ /**
418
+ * When the row was created. Declared as epoch millis, but real responses have
419
+ * also carried a numeric string or an ISO timestamp, so readers coerce it
420
+ * rather than trust the number — see `fetchCopilotMessages`.
421
+ */
422
+ created_at?: number | string;
423
+ /** Staff member the turn belongs to; null on older rows. */
424
+ email?: string | null;
425
+ }
426
+ /** One page of server-side history, newest page first. */
427
+ export interface CopilotMessagesPage {
428
+ /** Oldest first within the page, so it can be prepended as-is. */
429
+ messages: CopilotMessage[];
430
+ page: number;
431
+ pageSize: number;
432
+ /** False once there is nothing older left to fetch. */
433
+ hasMore: boolean;
434
+ }
435
+ /** Options for `fetchCopilotMessages`. */
436
+ export interface FetchCopilotMessagesOptions extends Omit<CopilotTransportOptions, 'endpointPath'> {
437
+ /**
438
+ * Path appended to `baseUrl`.
439
+ * @default '/bik-ai/copilot-messages'
440
+ */
441
+ endpointPath?: string;
442
+ storeId: string;
443
+ sessionId: string;
444
+ /** 1 is the newest page; 2 is the 20 before that. @default 1 */
445
+ page?: number;
446
+ /** @default 20, which is also the cap on what IndexedDB keeps. */
447
+ pageSize?: number;
448
+ signal?: AbortSignal;
449
+ }
450
+ /** What is persisted in IndexedDB for one conversation. */
451
+ export interface CopilotSessionRecord {
452
+ sessionId: string;
453
+ createdAt: number;
454
+ updatedAt: number;
455
+ /**
456
+ * When this record stops being readable: last write + 24h, restamped on every
457
+ * write. Absent on records written before the TTL existed, which are read as
458
+ * `updatedAt + 24h`.
459
+ */
460
+ expiresAt?: number;
461
+ messages: CopilotMessage[];
462
+ /** Last `responseId` seen, so a reload can continue the same OpenAI thread. */
463
+ previousResponseId?: string;
464
+ storeId?: string;
465
+ agent?: string;
466
+ route?: string;
467
+ }
468
+ /** Options for `useCopilotStreamChat`. */
469
+ export interface UseCopilotStreamChatOptions extends CopilotTransportOptions {
470
+ storeId: string;
471
+ /**
472
+ * @default 'BIK_ASSISTANT_AGENT'
473
+ */
474
+ agent?: CopilotAgent;
475
+ /**
476
+ * Route the merchant is on; sent every turn, so it can change
477
+ * mid-conversation. The backend resolves the turn's page knowledge from it, so
478
+ * the default is a placeholder to replace with the real route.
479
+ * @default 'campaigns/overview'
480
+ */
481
+ route?: string;
482
+ /**
483
+ * Signed-in merchant user, sent with every turn as `email`. Left out of the
484
+ * body entirely when the host has no email to give.
485
+ */
486
+ email?: string;
487
+ /**
488
+ * Path of the stored-history endpoint, appended to `baseUrl`.
489
+ * @default '/bik-ai/copilot-messages'
490
+ */
491
+ messagesPath?: string;
492
+ /**
493
+ * Reads older messages back from `/bik-ai/copilot-messages` — page 1 when
494
+ * nothing is cached locally, and the page before that each time the reader
495
+ * scrolls to the top. Turn it off to keep the copilot on local history alone.
496
+ * @default true
497
+ */
498
+ loadHistory?: boolean;
499
+ systemPrompt?: string;
500
+ model?: string;
501
+ maxOutputTokens?: number;
502
+ /**
503
+ * Pins the conversation to a session id. Omit it and the hook restores the
504
+ * last session from IndexedDB, or creates one on first use.
505
+ */
506
+ sessionId?: string;
507
+ /**
508
+ * Persists the transcript and the thread's `responseId` to IndexedDB.
509
+ * @default true
510
+ */
511
+ persist?: boolean;
512
+ /** Fired when a turn fails. The message is also surfaced in `error`. */
513
+ onError?: (error: Error) => void;
514
+ /** Fired once a turn's final `TEXT` event has arrived. */
515
+ onResponseComplete?: (payload: {
516
+ text: string;
517
+ responseId?: string;
518
+ followUps?: string[];
519
+ }) => void;
520
+ /** Fired when the active session id changes, including on restore. */
521
+ onSessionIdChange?: (sessionId: string) => void;
522
+ }
523
+ /** Return value of `useCopilotStreamChat`. */
524
+ export interface UseCopilotStreamChatResult {
525
+ messages: CopilotMessage[];
526
+ /** `null` until the session has been restored or created. */
527
+ sessionId: string | null;
528
+ /** Id threaded into the next turn to keep OpenAI's context. */
529
+ previousResponseId?: string;
530
+ /** True from send until the stream closes. */
531
+ isStreaming: boolean;
532
+ /** True while waiting for the first chunk — what drives the thinking bubble. */
533
+ isThinking: boolean;
534
+ /** True while the transcript is being read back from IndexedDB. */
535
+ isRestoring: boolean;
536
+ /** True while an older page of history is on its way. */
537
+ isLoadingHistory: boolean;
538
+ /** False once the endpoint reports there is nothing older left. */
539
+ hasMoreHistory: boolean;
540
+ error?: string;
541
+ sendMessage: (message: string) => void;
542
+ /**
543
+ * Fetches the page before the oldest message on screen and prepends it. Safe
544
+ * to call on every scroll event: it no-ops while a page is in flight and once
545
+ * the history runs out.
546
+ */
547
+ loadOlderMessages: () => void;
548
+ /** Aborts the in-flight turn, keeping whatever text already arrived. */
549
+ stopStreaming: () => void;
550
+ /** Starts a fresh session id, clearing the transcript and the thread. */
551
+ startNewConversation: () => void;
552
+ }
553
+ /**
554
+ * Props for the copilot wired to the streaming `/bik-ai/copilot-chatbot`
555
+ * endpoint, with IndexedDB-backed history and route-aware ice breakers.
556
+ */
557
+ export interface BIKCopilotProps extends CopilotShellProps, UseCopilotStreamChatOptions {
558
+ /**
559
+ * Path of the ice-breaker endpoint, appended to `baseUrl`.
560
+ * @default '/bik-ai/copilot-icebreakers'
561
+ */
562
+ icebreakersPath?: string;
563
+ /**
564
+ * Loads the route's suggested prompts from `/bik-ai/copilot-icebreakers` and
565
+ * shows them in the empty state. The request only fires once the panel has
566
+ * been opened. Passing `suggestions` explicitly wins over whatever is fetched.
567
+ * @default true
568
+ */
569
+ loadIcebreakers?: boolean;
570
+ }
571
+ /**
572
+ * Props of the presentational panel. Rendered by `ControlledCopilot`, exported so a
573
+ * host can build its own shell around it.
574
+ */
575
+ export interface CopilotPanelProps {
576
+ messages: CopilotMessage[];
577
+ onSend: (message: string) => void;
578
+ onClose: () => void;
579
+ title?: string;
580
+ badge?: string;
581
+ subtitle?: string;
582
+ avatar?: ReactNode;
583
+ placeholder?: string;
584
+ disclaimer?: string;
585
+ emptyStateTitle?: string;
586
+ emptyStateSubtitle?: string;
587
+ suggestions?: IceBreaker[];
588
+ onSuggestionClick?: (suggestion: IceBreaker) => void;
589
+ showRefresh?: boolean;
590
+ onRefresh?: () => void;
591
+ isThinking?: boolean;
592
+ thinkingLabel?: string;
593
+ isStreaming?: boolean;
594
+ onStop?: () => void;
595
+ interruptedLabel?: string;
596
+ followUpsLabel?: string;
597
+ hasMoreHistory?: boolean;
598
+ isLoadingHistory?: boolean;
599
+ onLoadOlderMessages?: () => void;
600
+ historyLoadingLabel?: string;
601
+ inputDisabled?: boolean;
602
+ errorMessage?: string;
603
+ width?: number;
604
+ height?: number;
605
+ className?: string;
606
+ style?: CSSProperties;
607
+ }
608
+ /**
609
+ * Props of the draggable ball. Rendered by `ControlledCopilot`, exported so a host can
610
+ * attach it to something other than the copilot panel.
611
+ */
612
+ export interface CopilotBallProps {
613
+ isOpen: boolean;
614
+ onToggle: () => void;
615
+ position: CopilotBallPosition;
616
+ size: number;
617
+ isDragging: boolean;
618
+ draggable: boolean;
619
+ color: string;
620
+ zIndex: number;
621
+ icon?: ReactNode;
622
+ openIcon?: ReactNode;
623
+ ariaLabel?: string;
624
+ onPointerDown: (event: ReactPointerEvent<HTMLElement>) => void;
625
+ onPointerMove: (event: ReactPointerEvent<HTMLElement>) => void;
626
+ onPointerUp: (event: ReactPointerEvent<HTMLElement>) => void;
627
+ onPointerCancel: (event: ReactPointerEvent<HTMLElement>) => void;
628
+ }
629
+ /**
630
+ * Pointer handlers produced by `useDraggableFloater`, spread onto the ball.
631
+ */
632
+ export interface CopilotDragHandlers {
633
+ onPointerDown: (event: ReactPointerEvent<HTMLElement>) => void;
634
+ onPointerMove: (event: ReactPointerEvent<HTMLElement>) => void;
635
+ onPointerUp: (event: ReactPointerEvent<HTMLElement>) => void;
636
+ onPointerCancel: (event: ReactPointerEvent<HTMLElement>) => void;
637
+ }
638
+ /**
639
+ * Options accepted by `useDraggableFloater`.
640
+ */
641
+ export interface UseDraggableFloaterOptions {
642
+ /** Diameter of the dragged element in px, used for viewport clamping. */
643
+ size: number;
644
+ /** Corner the element is parked in when no `initialPosition` is given. */
645
+ corner: CopilotCorner;
646
+ /** Distance kept from the viewport edges the element is parked against. */
647
+ offset: number;
648
+ /** Whether drag gestures are accepted at all. */
649
+ enabled: boolean;
650
+ /** Whether the element returns to the nearest horizontal edge on release. */
651
+ snapToEdge: boolean;
652
+ /** Exact starting coordinates, clamped into the viewport. */
653
+ initialPosition?: CopilotBallPosition;
654
+ /** Fired once a drag gesture settles. */
655
+ onPositionChange?: (position: CopilotBallPosition) => void;
656
+ }
657
+ /**
658
+ * Return value of `useDraggableFloater`.
659
+ */
660
+ export interface UseDraggableFloaterResult {
661
+ /** Current top-left coordinates of the element. */
662
+ position: CopilotBallPosition;
663
+ /** True while a gesture has moved past the drag threshold. */
664
+ isDragging: boolean;
665
+ /** Latest known viewport size, updated on resize. */
666
+ viewport: CopilotViewport;
667
+ /** Handlers to spread onto the dragged element. */
668
+ dragHandlers: CopilotDragHandlers;
669
+ /**
670
+ * Reads and clears the "the last gesture was a drag" flag. Call it at the top
671
+ * of the element's click handler so a drag does not also fire a click.
672
+ */
673
+ shouldIgnoreClick: () => boolean;
674
+ }