@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.
- package/README.md +25 -4
- package/dist/cjs/components/bik-copilot/BIKCopilot.js +2 -0
- package/dist/cjs/components/bik-copilot/BIKCopilot.js.map +1 -0
- package/dist/cjs/components/bik-copilot/ControlledCopilot.js +2 -0
- package/dist/cjs/components/bik-copilot/ControlledCopilot.js.map +1 -0
- package/dist/cjs/components/bik-copilot/ControlledCopilot.styled.js +27 -0
- package/dist/cjs/components/bik-copilot/ControlledCopilot.styled.js.map +1 -0
- package/dist/cjs/components/bik-copilot/CopilotBall.js +2 -0
- package/dist/cjs/components/bik-copilot/CopilotBall.js.map +1 -0
- package/dist/cjs/components/bik-copilot/CopilotBall.styled.js +42 -0
- package/dist/cjs/components/bik-copilot/CopilotBall.styled.js.map +1 -0
- package/dist/cjs/components/bik-copilot/CopilotPanel.js +2 -0
- package/dist/cjs/components/bik-copilot/CopilotPanel.js.map +1 -0
- package/dist/cjs/components/bik-copilot/CopilotPanel.styled.js +419 -0
- package/dist/cjs/components/bik-copilot/CopilotPanel.styled.js.map +1 -0
- package/dist/cjs/components/bik-copilot/copilotPlacement.js +2 -0
- package/dist/cjs/components/bik-copilot/copilotPlacement.js.map +1 -0
- package/dist/cjs/components/bik-copilot/services/copilotDb.js +2 -0
- package/dist/cjs/components/bik-copilot/services/copilotDb.js.map +1 -0
- package/dist/cjs/components/bik-copilot/services/copilotIcebreakersClient.js +2 -0
- package/dist/cjs/components/bik-copilot/services/copilotIcebreakersClient.js.map +1 -0
- package/dist/cjs/components/bik-copilot/services/copilotMessagesClient.js +2 -0
- package/dist/cjs/components/bik-copilot/services/copilotMessagesClient.js.map +1 -0
- package/dist/cjs/components/bik-copilot/services/copilotPageSession.js +2 -0
- package/dist/cjs/components/bik-copilot/services/copilotPageSession.js.map +1 -0
- package/dist/cjs/components/bik-copilot/services/copilotSseParser.js +7 -0
- package/dist/cjs/components/bik-copilot/services/copilotSseParser.js.map +1 -0
- package/dist/cjs/components/bik-copilot/services/copilotStreamClient.js +2 -0
- package/dist/cjs/components/bik-copilot/services/copilotStreamClient.js.map +1 -0
- package/dist/cjs/components/bik-copilot/useCopilotIcebreakers.js +2 -0
- package/dist/cjs/components/bik-copilot/useCopilotIcebreakers.js.map +1 -0
- package/dist/cjs/components/bik-copilot/useCopilotStreamChat.js +2 -0
- package/dist/cjs/components/bik-copilot/useCopilotStreamChat.js.map +1 -0
- package/dist/cjs/components/bik-copilot/useDraggableFloater.js +2 -0
- package/dist/cjs/components/bik-copilot/useDraggableFloater.js.map +1 -0
- package/dist/cjs/index.js +1 -1
- package/dist/esm/components/bik-copilot/BIKCopilot.d.ts +32 -0
- package/dist/esm/components/bik-copilot/BIKCopilot.js +114 -0
- package/dist/esm/components/bik-copilot/BIKCopilot.js.map +1 -0
- package/dist/esm/components/bik-copilot/ControlledCopilot.d.ts +14 -0
- package/dist/esm/components/bik-copilot/ControlledCopilot.js +170 -0
- package/dist/esm/components/bik-copilot/ControlledCopilot.js.map +1 -0
- package/dist/esm/components/bik-copilot/ControlledCopilot.styled.d.ts +7 -0
- package/dist/esm/components/bik-copilot/ControlledCopilot.styled.js +31 -0
- package/dist/esm/components/bik-copilot/ControlledCopilot.styled.js.map +1 -0
- package/dist/esm/components/bik-copilot/CopilotBall.d.ts +13 -0
- package/dist/esm/components/bik-copilot/CopilotBall.js +50 -0
- package/dist/esm/components/bik-copilot/CopilotBall.js.map +1 -0
- package/dist/esm/components/bik-copilot/CopilotBall.styled.d.ts +9 -0
- package/dist/esm/components/bik-copilot/CopilotBall.styled.js +46 -0
- package/dist/esm/components/bik-copilot/CopilotBall.styled.js.map +1 -0
- package/dist/esm/components/bik-copilot/CopilotPanel.d.ts +9 -0
- package/dist/esm/components/bik-copilot/CopilotPanel.js +309 -0
- package/dist/esm/components/bik-copilot/CopilotPanel.js.map +1 -0
- package/dist/esm/components/bik-copilot/CopilotPanel.styled.d.ts +58 -0
- package/dist/esm/components/bik-copilot/CopilotPanel.styled.js +453 -0
- package/dist/esm/components/bik-copilot/CopilotPanel.styled.js.map +1 -0
- package/dist/esm/components/bik-copilot/copilotPlacement.d.ts +24 -0
- package/dist/esm/components/bik-copilot/copilotPlacement.js +38 -0
- package/dist/esm/components/bik-copilot/copilotPlacement.js.map +1 -0
- package/dist/esm/components/bik-copilot/index.d.ts +16 -0
- package/dist/esm/components/bik-copilot/model.d.ts +674 -0
- package/dist/esm/components/bik-copilot/services/copilotDb.d.ts +90 -0
- package/dist/esm/components/bik-copilot/services/copilotDb.js +96 -0
- package/dist/esm/components/bik-copilot/services/copilotDb.js.map +1 -0
- package/dist/esm/components/bik-copilot/services/copilotIcebreakersClient.d.ts +11 -0
- package/dist/esm/components/bik-copilot/services/copilotIcebreakersClient.js +54 -0
- package/dist/esm/components/bik-copilot/services/copilotIcebreakersClient.js.map +1 -0
- package/dist/esm/components/bik-copilot/services/copilotMessagesClient.d.ts +23 -0
- package/dist/esm/components/bik-copilot/services/copilotMessagesClient.js +78 -0
- package/dist/esm/components/bik-copilot/services/copilotMessagesClient.js.map +1 -0
- package/dist/esm/components/bik-copilot/services/copilotPageSession.d.ts +37 -0
- package/dist/esm/components/bik-copilot/services/copilotPageSession.js +35 -0
- package/dist/esm/components/bik-copilot/services/copilotPageSession.js.map +1 -0
- package/dist/esm/components/bik-copilot/services/copilotSseParser.d.ts +22 -0
- package/dist/esm/components/bik-copilot/services/copilotSseParser.js +52 -0
- package/dist/esm/components/bik-copilot/services/copilotSseParser.js.map +1 -0
- package/dist/esm/components/bik-copilot/services/copilotStreamClient.d.ts +31 -0
- package/dist/esm/components/bik-copilot/services/copilotStreamClient.js +103 -0
- package/dist/esm/components/bik-copilot/services/copilotStreamClient.js.map +1 -0
- package/dist/esm/components/bik-copilot/useCopilotIcebreakers.d.ts +17 -0
- package/dist/esm/components/bik-copilot/useCopilotIcebreakers.js +60 -0
- package/dist/esm/components/bik-copilot/useCopilotIcebreakers.js.map +1 -0
- package/dist/esm/components/bik-copilot/useCopilotStreamChat.d.ts +20 -0
- package/dist/esm/components/bik-copilot/useCopilotStreamChat.js +333 -0
- package/dist/esm/components/bik-copilot/useCopilotStreamChat.js.map +1 -0
- package/dist/esm/components/bik-copilot/useDraggableFloater.d.ts +9 -0
- package/dist/esm/components/bik-copilot/useDraggableFloater.js +118 -0
- package/dist/esm/components/bik-copilot/useDraggableFloater.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +793 -743
- package/dist/esm/index.js.map +1 -1
- 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
|
+
}
|