@usepanacea/react 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,550 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var classVarianceAuthority = require('class-variance-authority');
6
+ var clsx = require('clsx');
7
+ var tailwindMerge = require('tailwind-merge');
8
+
9
+ // src/provider.tsx
10
+
11
+ // src/token.ts
12
+ var TokenManager = class {
13
+ constructor(config, apiBase) {
14
+ this.config = config;
15
+ this.apiBase = apiBase;
16
+ }
17
+ config;
18
+ apiBase;
19
+ state = null;
20
+ refreshPromise = null;
21
+ async getToken() {
22
+ if (!this.state || Date.now() >= this.state.expiresAt - 6e4) {
23
+ if (!this.refreshPromise) {
24
+ this.refreshPromise = this.refresh().finally(() => {
25
+ this.refreshPromise = null;
26
+ });
27
+ }
28
+ return this.refreshPromise;
29
+ }
30
+ return this.state.token;
31
+ }
32
+ async refresh() {
33
+ const res = await fetch(`${this.apiBase}/api/v1/auth/widget-token`, {
34
+ method: "POST",
35
+ headers: { "Content-Type": "application/json" },
36
+ body: JSON.stringify({ tenantId: this.config.tenantId })
37
+ });
38
+ if (!res.ok) {
39
+ throw new Error(`Panacea: token refresh failed (${res.status})`);
40
+ }
41
+ const data = await res.json();
42
+ this.state = {
43
+ token: data.token,
44
+ expiresAt: new Date(data.expiresAt).getTime()
45
+ };
46
+ return this.state.token;
47
+ }
48
+ };
49
+ var PanaceaContext = react.createContext(null);
50
+ function usePanaceaContext() {
51
+ const ctx = react.useContext(PanaceaContext);
52
+ if (!ctx) {
53
+ throw new Error(
54
+ 'usePanaceaContext must be used inside <PanaceaProvider>. Wrap your component tree with <PanaceaProvider tenantId="...">.'
55
+ );
56
+ }
57
+ return ctx;
58
+ }
59
+ function PanaceaProvider({
60
+ children,
61
+ tenantId,
62
+ apiBase: apiBaseProp,
63
+ customerToken,
64
+ escalationKeywords,
65
+ persona
66
+ }) {
67
+ const apiBase = apiBaseProp ?? (typeof window !== "undefined" ? window.location.origin : "");
68
+ const config = react.useMemo(
69
+ () => ({ tenantId, apiBase, customerToken, escalationKeywords, persona }),
70
+ // eslint-disable-next-line react-hooks/exhaustive-deps
71
+ [tenantId, apiBase, customerToken]
72
+ );
73
+ const tokenMgr = react.useMemo(
74
+ () => new TokenManager(config, apiBase),
75
+ // eslint-disable-next-line react-hooks/exhaustive-deps
76
+ [tenantId, apiBase]
77
+ );
78
+ const [turns, setTurns] = react.useState([]);
79
+ const [loading, setLoading] = react.useState(false);
80
+ const [streaming, setStreaming] = react.useState("");
81
+ const sessionId = react.useRef(null);
82
+ const [liveEscalationId, setLiveEscalationId] = react.useState(null);
83
+ const [liveMessages, setLiveMessages] = react.useState([]);
84
+ const [isOpen, setIsOpen] = react.useState(false);
85
+ const value = react.useMemo(
86
+ () => ({
87
+ config,
88
+ apiBase,
89
+ getToken: () => tokenMgr.getToken(),
90
+ turns,
91
+ setTurns,
92
+ loading,
93
+ setLoading,
94
+ streaming,
95
+ setStreaming,
96
+ sessionId,
97
+ liveEscalationId,
98
+ setLiveEscalationId,
99
+ liveMessages,
100
+ setLiveMessages,
101
+ isOpen,
102
+ setIsOpen
103
+ }),
104
+ [
105
+ config,
106
+ apiBase,
107
+ tokenMgr,
108
+ turns,
109
+ loading,
110
+ streaming,
111
+ liveEscalationId,
112
+ liveMessages,
113
+ isOpen
114
+ ]
115
+ );
116
+ return /* @__PURE__ */ jsxRuntime.jsx(PanaceaContext.Provider, { value, children });
117
+ }
118
+ function useChat() {
119
+ const {
120
+ config,
121
+ apiBase,
122
+ getToken,
123
+ turns,
124
+ setTurns,
125
+ loading,
126
+ setLoading,
127
+ streaming,
128
+ setStreaming,
129
+ sessionId,
130
+ liveEscalationId,
131
+ setLiveEscalationId
132
+ } = usePanaceaContext();
133
+ const send = react.useCallback(
134
+ async (message) => {
135
+ if (liveEscalationId) {
136
+ setTurns((prev) => [...prev, { role: "user", content: message }]);
137
+ try {
138
+ const token = await getToken();
139
+ await fetch(
140
+ `${apiBase}/api/v1/inbox/${liveEscalationId}/customer-message`,
141
+ {
142
+ method: "POST",
143
+ headers: {
144
+ "Content-Type": "application/json",
145
+ Authorization: `Bearer ${token}`
146
+ },
147
+ body: JSON.stringify({ content: message })
148
+ }
149
+ );
150
+ } catch {
151
+ }
152
+ return;
153
+ }
154
+ setLoading(true);
155
+ setStreaming("");
156
+ setTurns((prev) => [...prev, { role: "user", content: message }]);
157
+ try {
158
+ const token = await getToken();
159
+ const res = await fetch(`${apiBase}/api/v1/query`, {
160
+ method: "POST",
161
+ headers: {
162
+ "Content-Type": "application/json",
163
+ Authorization: `Bearer ${token}`,
164
+ Accept: "text/event-stream"
165
+ },
166
+ body: JSON.stringify({
167
+ question: message,
168
+ sessionId: sessionId.current
169
+ })
170
+ });
171
+ if (!res.ok || !res.body) {
172
+ throw new Error(`Query failed: ${res.status}`);
173
+ }
174
+ const reader = res.body.getReader();
175
+ const decoder = new TextDecoder();
176
+ let buffer = "";
177
+ let finalResponse = null;
178
+ while (true) {
179
+ const { done, value } = await reader.read();
180
+ if (done) break;
181
+ buffer += decoder.decode(value, { stream: true });
182
+ const lines = buffer.split("\n");
183
+ buffer = lines.pop() ?? "";
184
+ for (const line of lines) {
185
+ if (!line.startsWith("data: ")) continue;
186
+ const raw = line.slice(6).trim();
187
+ if (!raw || raw === "[DONE]") continue;
188
+ try {
189
+ const frame = JSON.parse(raw);
190
+ if (frame.type === "token" && frame.delta) {
191
+ setStreaming((s) => s + frame.delta);
192
+ } else if (frame.type === "done") {
193
+ finalResponse = {
194
+ answer: frame.answer ?? "",
195
+ sources: frame.sources ?? [],
196
+ confidence: frame.confidence ?? 0,
197
+ flaggedForReview: frame.flaggedForReview ?? false,
198
+ sessionId: frame.sessionId ?? "",
199
+ escalated: frame.escalated
200
+ };
201
+ } else if (frame.type === "error") {
202
+ throw new Error(frame.message ?? "Stream error");
203
+ }
204
+ } catch {
205
+ }
206
+ }
207
+ }
208
+ if (!finalResponse) throw new Error("Stream ended without done frame");
209
+ sessionId.current = finalResponse.sessionId;
210
+ setTurns((prev) => [
211
+ ...prev,
212
+ {
213
+ role: "assistant",
214
+ content: finalResponse.answer,
215
+ confidence: finalResponse.confidence,
216
+ sources: finalResponse.sources,
217
+ flaggedForReview: finalResponse.flaggedForReview,
218
+ reaction: null,
219
+ escalated: finalResponse.escalated
220
+ }
221
+ ]);
222
+ } catch (err) {
223
+ setTurns((prev) => [
224
+ ...prev,
225
+ {
226
+ role: "assistant",
227
+ content: err instanceof Error ? `Something went wrong: ${err.message}` : "Something went wrong. Please try again."
228
+ }
229
+ ]);
230
+ } finally {
231
+ setLoading(false);
232
+ setStreaming("");
233
+ }
234
+ },
235
+ [
236
+ apiBase,
237
+ getToken,
238
+ liveEscalationId,
239
+ sessionId,
240
+ setLoading,
241
+ setStreaming,
242
+ setTurns
243
+ ]
244
+ );
245
+ const escalate = react.useCallback(
246
+ async (reason = "Customer requested human support") => {
247
+ if (!sessionId.current) return;
248
+ try {
249
+ const token = await getToken();
250
+ const res = await fetch(
251
+ `${apiBase}/api/v1/sessions/${sessionId.current}/escalate`,
252
+ {
253
+ method: "POST",
254
+ headers: {
255
+ "Content-Type": "application/json",
256
+ Authorization: `Bearer ${token}`
257
+ },
258
+ body: JSON.stringify({ reason })
259
+ }
260
+ );
261
+ if (res.ok) {
262
+ const data = await res.json();
263
+ const id = data?.data?.escalationId ?? data?.escalationId;
264
+ if (id) setLiveEscalationId(id);
265
+ }
266
+ } catch {
267
+ }
268
+ },
269
+ [apiBase, getToken, sessionId, setLiveEscalationId]
270
+ );
271
+ const react$1 = react.useCallback(
272
+ async (turnIndex, reaction) => {
273
+ if (!sessionId.current) return;
274
+ setTurns(
275
+ (prev) => prev.map((t, i) => i === turnIndex ? { ...t, reaction } : t)
276
+ );
277
+ try {
278
+ const token = await getToken();
279
+ await fetch(
280
+ `${apiBase}/api/v1/sessions/${sessionId.current}/reaction`,
281
+ {
282
+ method: "POST",
283
+ headers: {
284
+ "Content-Type": "application/json",
285
+ Authorization: `Bearer ${token}`
286
+ },
287
+ body: JSON.stringify({ reaction, turnIndex })
288
+ }
289
+ );
290
+ } catch {
291
+ }
292
+ },
293
+ [apiBase, getToken, sessionId, setTurns]
294
+ );
295
+ const reset = react.useCallback(() => {
296
+ setTurns([]);
297
+ setStreaming("");
298
+ sessionId.current = null;
299
+ }, [sessionId, setStreaming, setTurns]);
300
+ return {
301
+ turns,
302
+ loading,
303
+ streaming,
304
+ sessionId: sessionId.current,
305
+ isEscalated: !!liveEscalationId,
306
+ send,
307
+ escalate,
308
+ react: react$1,
309
+ reset
310
+ };
311
+ }
312
+ function useLiveSession() {
313
+ const {
314
+ apiBase,
315
+ getToken,
316
+ liveEscalationId,
317
+ liveMessages,
318
+ setLiveMessages
319
+ } = usePanaceaContext();
320
+ const seenIds = react.useRef(/* @__PURE__ */ new Set());
321
+ react.useEffect(() => {
322
+ if (!liveEscalationId) return;
323
+ const poll = async () => {
324
+ try {
325
+ const token = await getToken();
326
+ const res = await fetch(
327
+ `${apiBase}/api/v1/inbox/${liveEscalationId}/messages`,
328
+ { headers: { Authorization: `Bearer ${token}` } }
329
+ );
330
+ if (!res.ok) return;
331
+ const data = await res.json();
332
+ const all = data?.data?.messages ?? data?.messages ?? [];
333
+ const newMsgs = all.filter((m) => !seenIds.current.has(m.id));
334
+ if (newMsgs.length > 0) {
335
+ newMsgs.forEach((m) => seenIds.current.add(m.id));
336
+ setLiveMessages((prev) => [...prev, ...newMsgs]);
337
+ }
338
+ } catch {
339
+ }
340
+ };
341
+ poll();
342
+ const id = setInterval(poll, 3e3);
343
+ return () => clearInterval(id);
344
+ }, [liveEscalationId, apiBase, getToken, setLiveMessages]);
345
+ const sendMessage = react.useCallback(
346
+ async (content) => {
347
+ if (!liveEscalationId) return;
348
+ try {
349
+ const token = await getToken();
350
+ await fetch(
351
+ `${apiBase}/api/v1/inbox/${liveEscalationId}/customer-message`,
352
+ {
353
+ method: "POST",
354
+ headers: {
355
+ "Content-Type": "application/json",
356
+ Authorization: `Bearer ${token}`
357
+ },
358
+ body: JSON.stringify({ content })
359
+ }
360
+ );
361
+ } catch {
362
+ }
363
+ },
364
+ [liveEscalationId, apiBase, getToken]
365
+ );
366
+ return {
367
+ isLive: !!liveEscalationId,
368
+ escalationId: liveEscalationId,
369
+ liveMessages,
370
+ agentMessages: liveMessages.filter((m) => m.senderRole === "agent"),
371
+ sendMessage
372
+ };
373
+ }
374
+ function useWidget() {
375
+ const { isOpen, setIsOpen } = usePanaceaContext();
376
+ const open = react.useCallback(() => setIsOpen(true), [setIsOpen]);
377
+ const close = react.useCallback(() => setIsOpen(false), [setIsOpen]);
378
+ const toggle = react.useCallback(() => setIsOpen((v) => !v), [setIsOpen]);
379
+ return { isOpen, open, close, toggle };
380
+ }
381
+ function cn(...inputs) {
382
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
383
+ }
384
+ var buttonVariants = classVarianceAuthority.cva(
385
+ "inline-flex items-center justify-center gap-1.5 rounded-lg text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50",
386
+ {
387
+ variants: {
388
+ variant: {
389
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
390
+ outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
391
+ ghost: "hover:bg-accent hover:text-accent-foreground"
392
+ },
393
+ size: {
394
+ default: "h-9 px-4 py-2",
395
+ sm: "h-8 px-3 text-xs",
396
+ icon: "size-9"
397
+ }
398
+ },
399
+ defaultVariants: { variant: "default", size: "default" }
400
+ }
401
+ );
402
+ function Button({ className, variant, size, ...props }) {
403
+ return /* @__PURE__ */ jsxRuntime.jsx("button", { className: cn(buttonVariants({ variant, size }), className), ...props });
404
+ }
405
+ function PanaceaFAB({ className }) {
406
+ const { toggle } = useWidget();
407
+ return /* @__PURE__ */ jsxRuntime.jsx(
408
+ "button",
409
+ {
410
+ "aria-label": "Open support chat",
411
+ onClick: toggle,
412
+ className: cn(
413
+ "fixed bottom-6 right-6 z-[2147483647] flex size-14 items-center justify-center rounded-full bg-primary text-2xl text-primary-foreground shadow-lg transition-transform hover:scale-105",
414
+ className
415
+ ),
416
+ children: "\u{1F4AC}"
417
+ }
418
+ );
419
+ }
420
+ function Bubble({ turn }) {
421
+ const isUser = turn.role === "user";
422
+ const isAgent = turn.isLiveAgent;
423
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("mb-3 flex flex-col", isUser ? "items-end" : "items-start"), children: [
424
+ isAgent && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-1 text-[10px] font-bold uppercase tracking-wide text-blue-600", children: "Agent" }),
425
+ /* @__PURE__ */ jsxRuntime.jsx(
426
+ "div",
427
+ {
428
+ className: cn(
429
+ "max-w-[82%] rounded-2xl px-3.5 py-2.5 text-sm leading-relaxed",
430
+ isUser ? "rounded-tr-sm bg-primary text-primary-foreground" : isAgent ? "rounded-tl-sm bg-blue-100 text-blue-900" : "rounded-tl-sm bg-muted text-foreground"
431
+ ),
432
+ children: turn.content
433
+ }
434
+ )
435
+ ] });
436
+ }
437
+ function TypingDots() {
438
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-3 flex items-start", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-1 rounded-2xl rounded-tl-sm bg-muted px-3.5 py-3", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxRuntime.jsx(
439
+ "span",
440
+ {
441
+ className: "size-1.5 animate-bounce rounded-full bg-muted-foreground/50",
442
+ style: { animationDelay: `${i * 0.15}s` }
443
+ },
444
+ i
445
+ )) }) });
446
+ }
447
+ function PanaceaMessages({ className }) {
448
+ const { turns, loading, streaming } = useChat();
449
+ const { agentMessages } = useLiveSession();
450
+ const bottomRef = react.useRef(null);
451
+ const agentTurns = agentMessages.map((m) => ({
452
+ role: "assistant",
453
+ content: m.content,
454
+ isLiveAgent: true
455
+ }));
456
+ const allTurns = [...turns, ...agentTurns];
457
+ react.useEffect(() => {
458
+ bottomRef.current?.scrollIntoView({ behavior: "smooth" });
459
+ }, [allTurns.length]);
460
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-1 flex-col overflow-y-auto p-4", className), children: [
461
+ allTurns.map((turn, i) => /* @__PURE__ */ jsxRuntime.jsx(Bubble, { turn }, i)),
462
+ loading && !streaming && /* @__PURE__ */ jsxRuntime.jsx(TypingDots, {}),
463
+ streaming && /* @__PURE__ */ jsxRuntime.jsx(Bubble, { turn: { role: "assistant", content: streaming } }),
464
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: bottomRef })
465
+ ] });
466
+ }
467
+ function PanaceaInput({
468
+ placeholder,
469
+ className
470
+ }) {
471
+ const { send, loading, isEscalated } = useChat();
472
+ const inputRef = react.useRef(null);
473
+ const handleSend = () => {
474
+ const val = inputRef.current?.value.trim();
475
+ if (!val || loading) return;
476
+ void send(val);
477
+ if (inputRef.current) inputRef.current.value = "";
478
+ };
479
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex gap-2 border-t border-border bg-background p-3", className), children: [
480
+ /* @__PURE__ */ jsxRuntime.jsx(
481
+ "input",
482
+ {
483
+ ref: inputRef,
484
+ placeholder: isEscalated ? "Reply to agent\u2026" : placeholder ?? "Ask a question\u2026",
485
+ disabled: loading,
486
+ onKeyDown: (e) => e.key === "Enter" && handleSend(),
487
+ className: "flex-1 rounded-full border border-input bg-muted/40 px-4 py-2 text-sm outline-none placeholder:text-muted-foreground focus:border-primary focus:bg-background disabled:opacity-50"
488
+ }
489
+ ),
490
+ /* @__PURE__ */ jsxRuntime.jsx(
491
+ Button,
492
+ {
493
+ onClick: handleSend,
494
+ disabled: loading,
495
+ size: "sm",
496
+ className: "shrink-0 rounded-full px-4",
497
+ children: "Send"
498
+ }
499
+ )
500
+ ] });
501
+ }
502
+ function PanaceaChat({
503
+ title,
504
+ placeholder,
505
+ className
506
+ }) {
507
+ const { config } = usePanaceaContext();
508
+ const { isOpen, close } = useWidget();
509
+ const panelTitle = title ?? config.persona?.name ?? "Support";
510
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
511
+ /* @__PURE__ */ jsxRuntime.jsx(PanaceaFAB, {}),
512
+ /* @__PURE__ */ jsxRuntime.jsxs(
513
+ "div",
514
+ {
515
+ className: cn(
516
+ "fixed bottom-24 right-6 z-[2147483646] flex w-[360px] flex-col overflow-hidden rounded-2xl border border-border bg-background shadow-2xl transition-all duration-200",
517
+ isOpen ? "pointer-events-auto h-[520px] opacity-100" : "pointer-events-none h-0 opacity-0",
518
+ className
519
+ ),
520
+ children: [
521
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center justify-between bg-primary px-4 py-3.5", children: [
522
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold text-primary-foreground", children: panelTitle }),
523
+ /* @__PURE__ */ jsxRuntime.jsx(
524
+ "button",
525
+ {
526
+ onClick: close,
527
+ "aria-label": "Close",
528
+ className: "text-primary-foreground/70 transition-colors hover:text-primary-foreground",
529
+ children: "\u2715"
530
+ }
531
+ )
532
+ ] }),
533
+ /* @__PURE__ */ jsxRuntime.jsx(PanaceaMessages, {}),
534
+ /* @__PURE__ */ jsxRuntime.jsx(PanaceaInput, { placeholder })
535
+ ]
536
+ }
537
+ )
538
+ ] });
539
+ }
540
+
541
+ exports.PanaceaChat = PanaceaChat;
542
+ exports.PanaceaFAB = PanaceaFAB;
543
+ exports.PanaceaInput = PanaceaInput;
544
+ exports.PanaceaMessages = PanaceaMessages;
545
+ exports.PanaceaProvider = PanaceaProvider;
546
+ exports.useChat = useChat;
547
+ exports.useLiveSession = useLiveSession;
548
+ exports.useWidget = useWidget;
549
+ //# sourceMappingURL=index.cjs.map
550
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/token.ts","../src/provider.tsx","../src/hooks/useChat.ts","../src/hooks/useLiveSession.ts","../src/hooks/useWidget.ts","../src/lib/utils.ts","../src/components/index.tsx"],"names":["createContext","useContext","useMemo","useState","useRef","jsx","useCallback","react","useEffect","twMerge","clsx","cva","jsxs","Fragment"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { PanaceaConfig } from \"./types\";\n\ninterface TokenState {\n token: string;\n /** unix ms */\n expiresAt: number;\n}\n\n/**\n * Manages the short-lived widget JWT (15 min TTL).\n * Auto-refreshes 60s before expiry. Concurrent callers share one refresh promise.\n */\nexport class TokenManager {\n private state: TokenState | null = null;\n private refreshPromise: Promise<string> | null = null;\n\n constructor(\n private readonly config: PanaceaConfig,\n private readonly apiBase: string\n ) {}\n\n async getToken(): Promise<string> {\n if (!this.state || Date.now() >= this.state.expiresAt - 60_000) {\n if (!this.refreshPromise) {\n this.refreshPromise = this.refresh().finally(() => {\n this.refreshPromise = null;\n });\n }\n return this.refreshPromise;\n }\n return this.state.token;\n }\n\n private async refresh(): Promise<string> {\n const res = await fetch(`${this.apiBase}/api/v1/auth/widget-token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ tenantId: this.config.tenantId }),\n });\n\n if (!res.ok) {\n throw new Error(`Panacea: token refresh failed (${res.status})`);\n }\n\n const data = (await res.json()) as { token: string; expiresAt: string };\n this.state = {\n token: data.token,\n expiresAt: new Date(data.expiresAt).getTime(),\n };\n return this.state.token;\n }\n}\n","\"use client\";\n\nimport React, {\n createContext,\n useContext,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport type {\n LiveMessage,\n PanaceaConfig,\n PanaceaContextValue,\n Turn,\n} from \"./types\";\nimport { TokenManager } from \"./token\";\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst PanaceaContext = createContext<PanaceaContextValue | null>(null);\n\nexport function usePanaceaContext(): PanaceaContextValue {\n const ctx = useContext(PanaceaContext);\n if (!ctx) {\n throw new Error(\n \"usePanaceaContext must be used inside <PanaceaProvider>. \" +\n \"Wrap your component tree with <PanaceaProvider tenantId=\\\"...\\\">.\"\n );\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\ninterface PanaceaProviderProps {\n children: React.ReactNode;\n tenantId: string;\n apiBase?: string;\n customerToken?: string;\n escalationKeywords?: string[];\n persona?: PanaceaConfig[\"persona\"];\n}\n\nexport function PanaceaProvider({\n children,\n tenantId,\n apiBase: apiBaseProp,\n customerToken,\n escalationKeywords,\n persona,\n}: PanaceaProviderProps) {\n const apiBase =\n apiBaseProp ??\n (typeof window !== \"undefined\" ? window.location.origin : \"\");\n\n const config: PanaceaConfig = useMemo(\n () => ({ tenantId, apiBase, customerToken, escalationKeywords, persona }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [tenantId, apiBase, customerToken]\n );\n\n // Stable token manager instance across re-renders\n const tokenMgr = useMemo(\n () => new TokenManager(config, apiBase),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [tenantId, apiBase]\n );\n\n // AI conversation\n const [turns, setTurns] = useState<Turn[]>([]);\n const [loading, setLoading] = useState(false);\n const [streaming, setStreaming] = useState(\"\");\n const sessionId = useRef<string | null>(null);\n\n // Live handover\n const [liveEscalationId, setLiveEscalationId] = useState<string | null>(null);\n const [liveMessages, setLiveMessages] = useState<LiveMessage[]>([]);\n\n // Panel\n const [isOpen, setIsOpen] = useState(false);\n\n const value: PanaceaContextValue = useMemo(\n () => ({\n config,\n apiBase,\n getToken: () => tokenMgr.getToken(),\n turns,\n setTurns,\n loading,\n setLoading,\n streaming,\n setStreaming,\n sessionId,\n liveEscalationId,\n setLiveEscalationId,\n liveMessages,\n setLiveMessages,\n isOpen,\n setIsOpen,\n }),\n [\n config,\n apiBase,\n tokenMgr,\n turns,\n loading,\n streaming,\n liveEscalationId,\n liveMessages,\n isOpen,\n ]\n );\n\n return (\n <PanaceaContext.Provider value={value}>{children}</PanaceaContext.Provider>\n );\n}\n","\"use client\";\n\nimport { useCallback } from \"react\";\nimport { usePanaceaContext } from \"../provider\";\nimport type { Source, Turn } from \"../types\";\n\ninterface QueryResponse {\n answer: string;\n sources: Source[];\n confidence: number;\n flaggedForReview: boolean;\n sessionId: string;\n escalated?: boolean;\n}\n\ninterface UseChatReturn {\n /** All conversation turns so far */\n turns: Turn[];\n /** True while an AI response is in flight */\n loading: boolean;\n /** Partial token stream content during SSE streaming */\n streaming: string;\n /** Current session ID (null until first message) */\n sessionId: string | null;\n /** True once the session has been escalated to a human */\n isEscalated: boolean;\n /** Send a message. Routes to live agent if session is escalated. */\n send: (message: string) => Promise<void>;\n /** Manually trigger escalation to a human agent */\n escalate: (reason?: string) => Promise<void>;\n /** Post a thumbs-up/down reaction for a turn by index */\n react: (turnIndex: number, reaction: \"helpful\" | \"unhelpful\") => Promise<void>;\n /** Clear all turns and start fresh */\n reset: () => void;\n}\n\nexport function useChat(): UseChatReturn {\n const {\n config,\n apiBase,\n getToken,\n turns,\n setTurns,\n loading,\n setLoading,\n streaming,\n setStreaming,\n sessionId,\n liveEscalationId,\n setLiveEscalationId,\n } = usePanaceaContext();\n\n const send = useCallback(\n async (message: string) => {\n // While escalated, route to the live customer-message endpoint\n if (liveEscalationId) {\n setTurns((prev) => [...prev, { role: \"user\", content: message }]);\n try {\n const token = await getToken();\n await fetch(\n `${apiBase}/api/v1/inbox/${liveEscalationId}/customer-message`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({ content: message }),\n }\n );\n } catch {\n // Message shown optimistically — non-critical\n }\n return;\n }\n\n setLoading(true);\n setStreaming(\"\");\n setTurns((prev) => [...prev, { role: \"user\", content: message }]);\n\n try {\n const token = await getToken();\n const res = await fetch(`${apiBase}/api/v1/query`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n Accept: \"text/event-stream\",\n },\n body: JSON.stringify({\n question: message,\n sessionId: sessionId.current,\n }),\n });\n\n if (!res.ok || !res.body) {\n throw new Error(`Query failed: ${res.status}`);\n }\n\n // SSE streaming\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let finalResponse: QueryResponse | null = null;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (!line.startsWith(\"data: \")) continue;\n const raw = line.slice(6).trim();\n if (!raw || raw === \"[DONE]\") continue;\n try {\n const frame = JSON.parse(raw) as {\n type: \"token\" | \"done\" | \"error\";\n delta?: string;\n answer?: string;\n sources?: Source[];\n confidence?: number;\n sessionId?: string;\n flaggedForReview?: boolean;\n escalated?: boolean;\n message?: string;\n };\n if (frame.type === \"token\" && frame.delta) {\n setStreaming((s) => s + frame.delta);\n } else if (frame.type === \"done\") {\n finalResponse = {\n answer: frame.answer ?? \"\",\n sources: frame.sources ?? [],\n confidence: frame.confidence ?? 0,\n flaggedForReview: frame.flaggedForReview ?? false,\n sessionId: frame.sessionId ?? \"\",\n escalated: frame.escalated,\n };\n } else if (frame.type === \"error\") {\n throw new Error(frame.message ?? \"Stream error\");\n }\n } catch {\n // Ignore malformed frames\n }\n }\n }\n\n if (!finalResponse) throw new Error(\"Stream ended without done frame\");\n\n sessionId.current = finalResponse.sessionId;\n\n setTurns((prev) => [\n ...prev,\n {\n role: \"assistant\",\n content: finalResponse!.answer,\n confidence: finalResponse!.confidence,\n sources: finalResponse!.sources,\n flaggedForReview: finalResponse!.flaggedForReview,\n reaction: null,\n escalated: finalResponse!.escalated,\n },\n ]);\n } catch (err) {\n setTurns((prev) => [\n ...prev,\n {\n role: \"assistant\",\n content:\n err instanceof Error\n ? `Something went wrong: ${err.message}`\n : \"Something went wrong. Please try again.\",\n },\n ]);\n } finally {\n setLoading(false);\n setStreaming(\"\");\n }\n },\n [\n apiBase,\n getToken,\n liveEscalationId,\n sessionId,\n setLoading,\n setStreaming,\n setTurns,\n ]\n );\n\n const escalate = useCallback(\n async (reason = \"Customer requested human support\") => {\n if (!sessionId.current) return;\n try {\n const token = await getToken();\n const res = await fetch(\n `${apiBase}/api/v1/sessions/${sessionId.current}/escalate`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({ reason }),\n }\n );\n if (res.ok) {\n const data = (await res.json()) as {\n data?: { escalationId?: string };\n escalationId?: string;\n };\n const id = data?.data?.escalationId ?? data?.escalationId;\n if (id) setLiveEscalationId(id);\n }\n } catch {\n // Non-critical\n }\n },\n [apiBase, getToken, sessionId, setLiveEscalationId]\n );\n\n const react = useCallback(\n async (turnIndex: number, reaction: \"helpful\" | \"unhelpful\") => {\n if (!sessionId.current) return;\n setTurns((prev) =>\n prev.map((t, i) => (i === turnIndex ? { ...t, reaction } : t))\n );\n try {\n const token = await getToken();\n await fetch(\n `${apiBase}/api/v1/sessions/${sessionId.current}/reaction`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({ reaction, turnIndex }),\n }\n );\n } catch {\n // Non-critical\n }\n },\n [apiBase, getToken, sessionId, setTurns]\n );\n\n const reset = useCallback(() => {\n setTurns([]);\n setStreaming(\"\");\n sessionId.current = null;\n }, [sessionId, setStreaming, setTurns]);\n\n return {\n turns,\n loading,\n streaming,\n sessionId: sessionId.current,\n isEscalated: !!liveEscalationId,\n send,\n escalate,\n react,\n reset,\n };\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useRef } from \"react\";\nimport { usePanaceaContext } from \"../provider\";\nimport type { LiveMessage } from \"../types\";\n\ninterface UseLiveSessionReturn {\n /** True once the session has been escalated */\n isLive: boolean;\n /** The active escalation ID, or null */\n escalationId: string | null;\n /** All messages exchanged since escalation (agent + customer) */\n liveMessages: LiveMessage[];\n /** Agent-only messages (for display in widget — customer messages shown optimistically) */\n agentMessages: LiveMessage[];\n /** Send a customer message to the live agent */\n sendMessage: (content: string) => Promise<void>;\n}\n\nexport function useLiveSession(): UseLiveSessionReturn {\n const {\n apiBase,\n getToken,\n liveEscalationId,\n liveMessages,\n setLiveMessages,\n } = usePanaceaContext();\n\n const seenIds = useRef<Set<string>>(new Set());\n\n // Poll for new messages every 3s while escalated\n useEffect(() => {\n if (!liveEscalationId) return;\n\n const poll = async () => {\n try {\n const token = await getToken();\n const res = await fetch(\n `${apiBase}/api/v1/inbox/${liveEscalationId}/messages`,\n { headers: { Authorization: `Bearer ${token}` } }\n );\n if (!res.ok) return;\n\n const data = (await res.json()) as {\n data?: { messages?: LiveMessage[] };\n messages?: LiveMessage[];\n };\n const all: LiveMessage[] =\n data?.data?.messages ?? data?.messages ?? [];\n\n // Only add messages we haven't seen yet\n const newMsgs = all.filter((m) => !seenIds.current.has(m.id));\n if (newMsgs.length > 0) {\n newMsgs.forEach((m) => seenIds.current.add(m.id));\n setLiveMessages((prev) => [...prev, ...newMsgs]);\n }\n } catch {\n // Non-critical\n }\n };\n\n poll();\n const id = setInterval(poll, 3_000);\n return () => clearInterval(id);\n }, [liveEscalationId, apiBase, getToken, setLiveMessages]);\n\n const sendMessage = useCallback(\n async (content: string) => {\n if (!liveEscalationId) return;\n try {\n const token = await getToken();\n await fetch(\n `${apiBase}/api/v1/inbox/${liveEscalationId}/customer-message`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({ content }),\n }\n );\n } catch {\n // Non-critical — message shown optimistically by the caller\n }\n },\n [liveEscalationId, apiBase, getToken]\n );\n\n return {\n isLive: !!liveEscalationId,\n escalationId: liveEscalationId,\n liveMessages,\n agentMessages: liveMessages.filter((m) => m.senderRole === \"agent\"),\n sendMessage,\n };\n}\n","\"use client\";\n\nimport { useCallback } from \"react\";\nimport { usePanaceaContext } from \"../provider\";\n\ninterface UseWidgetReturn {\n isOpen: boolean;\n open: () => void;\n close: () => void;\n toggle: () => void;\n}\n\nexport function useWidget(): UseWidgetReturn {\n const { isOpen, setIsOpen } = usePanaceaContext();\n\n const open = useCallback(() => setIsOpen(true), [setIsOpen]);\n const close = useCallback(() => setIsOpen(false), [setIsOpen]);\n const toggle = useCallback(() => setIsOpen((v) => !v), [setIsOpen]);\n\n return { isOpen, open, close, toggle };\n}\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport React, { useEffect, useRef } from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"../lib/utils\";\nimport { useChat } from \"../hooks/useChat\";\nimport { useLiveSession } from \"../hooks/useLiveSession\";\nimport { useWidget } from \"../hooks/useWidget\";\nimport { usePanaceaContext } from \"../provider\";\nimport type { Turn } from \"../types\";\n\n// ---------------------------------------------------------------------------\n// Button (shadcn pattern)\n// ---------------------------------------------------------------------------\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-1.5 rounded-lg text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n outline:\n \"border border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n ghost: \"hover:bg-accent hover:text-accent-foreground\",\n },\n size: {\n default: \"h-9 px-4 py-2\",\n sm: \"h-8 px-3 text-xs\",\n icon: \"size-9\",\n },\n },\n defaultVariants: { variant: \"default\", size: \"default\" },\n }\n);\n\ninterface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {}\n\nfunction Button({ className, variant, size, ...props }: ButtonProps) {\n return (\n <button className={cn(buttonVariants({ variant, size }), className)} {...props} />\n );\n}\n\n// ---------------------------------------------------------------------------\n// FAB\n// ---------------------------------------------------------------------------\n\nexport function PanaceaFAB({ className }: { className?: string }) {\n const { toggle } = useWidget();\n return (\n <button\n aria-label=\"Open support chat\"\n onClick={toggle}\n className={cn(\n \"fixed bottom-6 right-6 z-[2147483647] flex size-14 items-center justify-center rounded-full bg-primary text-2xl text-primary-foreground shadow-lg transition-transform hover:scale-105\",\n className\n )}\n >\n 💬\n </button>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Message bubble\n// ---------------------------------------------------------------------------\n\nfunction Bubble({ turn }: { turn: Turn }) {\n const isUser = turn.role === \"user\";\n const isAgent = turn.isLiveAgent;\n return (\n <div className={cn(\"mb-3 flex flex-col\", isUser ? \"items-end\" : \"items-start\")}>\n {isAgent && (\n <span className=\"mb-1 text-[10px] font-bold uppercase tracking-wide text-blue-600\">\n Agent\n </span>\n )}\n <div\n className={cn(\n \"max-w-[82%] rounded-2xl px-3.5 py-2.5 text-sm leading-relaxed\",\n isUser\n ? \"rounded-tr-sm bg-primary text-primary-foreground\"\n : isAgent\n ? \"rounded-tl-sm bg-blue-100 text-blue-900\"\n : \"rounded-tl-sm bg-muted text-foreground\"\n )}\n >\n {turn.content}\n </div>\n </div>\n );\n}\n\nfunction TypingDots() {\n return (\n <div className=\"mb-3 flex items-start\">\n <div className=\"flex gap-1 rounded-2xl rounded-tl-sm bg-muted px-3.5 py-3\">\n {[0, 1, 2].map((i) => (\n <span\n key={i}\n className=\"size-1.5 animate-bounce rounded-full bg-muted-foreground/50\"\n style={{ animationDelay: `${i * 0.15}s` }}\n />\n ))}\n </div>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// PanaceaMessages\n// ---------------------------------------------------------------------------\n\nexport function PanaceaMessages({ className }: { className?: string }) {\n const { turns, loading, streaming } = useChat();\n const { agentMessages } = useLiveSession();\n const bottomRef = useRef<HTMLDivElement>(null);\n\n const agentTurns: Turn[] = agentMessages.map((m) => ({\n role: \"assistant\",\n content: m.content,\n isLiveAgent: true,\n }));\n const allTurns = [...turns, ...agentTurns];\n\n useEffect(() => {\n bottomRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, [allTurns.length]);\n\n return (\n <div className={cn(\"flex flex-1 flex-col overflow-y-auto p-4\", className)}>\n {allTurns.map((turn, i) => (\n <Bubble key={i} turn={turn} />\n ))}\n {loading && !streaming && <TypingDots />}\n {streaming && <Bubble turn={{ role: \"assistant\", content: streaming }} />}\n <div ref={bottomRef} />\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// PanaceaInput\n// ---------------------------------------------------------------------------\n\nexport function PanaceaInput({\n placeholder,\n className,\n}: {\n placeholder?: string;\n className?: string;\n}) {\n const { send, loading, isEscalated } = useChat();\n const inputRef = useRef<HTMLInputElement>(null);\n\n const handleSend = () => {\n const val = inputRef.current?.value.trim();\n if (!val || loading) return;\n void send(val);\n if (inputRef.current) inputRef.current.value = \"\";\n };\n\n return (\n <div className={cn(\"flex gap-2 border-t border-border bg-background p-3\", className)}>\n <input\n ref={inputRef}\n placeholder={isEscalated ? \"Reply to agent…\" : (placeholder ?? \"Ask a question…\")}\n disabled={loading}\n onKeyDown={(e) => e.key === \"Enter\" && handleSend()}\n className=\"flex-1 rounded-full border border-input bg-muted/40 px-4 py-2 text-sm outline-none placeholder:text-muted-foreground focus:border-primary focus:bg-background disabled:opacity-50\"\n />\n <Button\n onClick={handleSend}\n disabled={loading}\n size=\"sm\"\n className=\"shrink-0 rounded-full px-4\"\n >\n Send\n </Button>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// PanaceaChat — full pre-built floating panel\n// ---------------------------------------------------------------------------\n\nexport function PanaceaChat({\n title,\n placeholder,\n className,\n}: {\n title?: string;\n placeholder?: string;\n className?: string;\n}) {\n const { config } = usePanaceaContext();\n const { isOpen, close } = useWidget();\n const panelTitle = title ?? config.persona?.name ?? \"Support\";\n\n return (\n <>\n <PanaceaFAB />\n <div\n className={cn(\n \"fixed bottom-24 right-6 z-[2147483646] flex w-[360px] flex-col overflow-hidden rounded-2xl border border-border bg-background shadow-2xl transition-all duration-200\",\n isOpen\n ? \"pointer-events-auto h-[520px] opacity-100\"\n : \"pointer-events-none h-0 opacity-0\",\n className\n )}\n >\n {/* Header */}\n <div className=\"flex shrink-0 items-center justify-between bg-primary px-4 py-3.5\">\n <span className=\"text-sm font-semibold text-primary-foreground\">\n {panelTitle}\n </span>\n <button\n onClick={close}\n aria-label=\"Close\"\n className=\"text-primary-foreground/70 transition-colors hover:text-primary-foreground\"\n >\n ✕\n </button>\n </div>\n\n <PanaceaMessages />\n <PanaceaInput placeholder={placeholder} />\n </div>\n </>\n );\n}\n"]}