@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/README.md ADDED
@@ -0,0 +1,331 @@
1
+ # `@usepanacea/react`
2
+
3
+ > React hooks and components for embedding Panacea support chat in any React or Next.js app.
4
+
5
+ Two integration styles, same package:
6
+
7
+ - **Headless** — use hooks (`useChat`, `useLiveSession`, `useWidget`) with your own UI
8
+ - **Drop-in** — use pre-built Tailwind v4 + shadcn-pattern components (`PanaceaChat`, `PanaceaMessages`, `PanaceaInput`, `PanaceaFAB`)
9
+
10
+ ---
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @usepanacea/react react react-dom
16
+ # or
17
+ pnpm add @usepanacea/react react react-dom
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Quick Start (Drop-in)
23
+
24
+ ### With Tailwind v4 (recommended)
25
+
26
+ Add the package source to your Tailwind input so its utility classes are included:
27
+
28
+ ```css
29
+ /* app/globals.css */
30
+ @import "tailwindcss";
31
+ @source "../../node_modules/@usepanacea/react/dist";
32
+ ```
33
+
34
+ Then wrap your layout:
35
+
36
+ ```tsx
37
+ // app/layout.tsx
38
+ import { PanaceaProvider, PanaceaChat } from "@usepanacea/react";
39
+
40
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
41
+ return (
42
+ <html>
43
+ <body>
44
+ <PanaceaProvider tenantId="t_your_tenant_id">
45
+ {children}
46
+ <PanaceaChat title="Support" placeholder="Ask anything…" />
47
+ </PanaceaProvider>
48
+ </body>
49
+ </html>
50
+ );
51
+ }
52
+ ```
53
+
54
+ ### Without Tailwind (pre-built CSS)
55
+
56
+ ```tsx
57
+ // app/layout.tsx
58
+ import { PanaceaProvider, PanaceaChat } from "@usepanacea/react";
59
+ import "@usepanacea/react/styles.css";
60
+
61
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
62
+ return (
63
+ <html>
64
+ <body>
65
+ <PanaceaProvider tenantId="t_your_tenant_id">
66
+ {children}
67
+ <PanaceaChat />
68
+ </PanaceaProvider>
69
+ </body>
70
+ </html>
71
+ );
72
+ }
73
+ ```
74
+
75
+ ---
76
+
77
+ ## `PanaceaProvider` Props
78
+
79
+ | Prop | Type | Default | Description |
80
+ | -------------------- | ---------- | ------------ | --------------------------------------------------- |
81
+ | `tenantId` | `string` | **required** | Your Panacea tenant ID |
82
+ | `apiBase` | `string` | `""` | Base URL of your Panacea API (omit for same-origin) |
83
+ | `customerToken` | `string` | — | Pre-issued signed identity JWT (optional) |
84
+ | `escalationKeywords` | `string[]` | — | Words that trigger the "speak to a human?" offer |
85
+ | `persona` | `object` | — | `{ name, avatar, greeting, tone }` overrides |
86
+
87
+ ---
88
+
89
+ ## Headless Hooks
90
+
91
+ ### `useChat()`
92
+
93
+ Core AI chat + escalation state.
94
+
95
+ ```tsx
96
+ import { useChat } from "@usepanacea/react";
97
+
98
+ function MyChat() {
99
+ const { turns, loading, streaming, isEscalated, send, escalate, react, reset } = useChat();
100
+
101
+ return (
102
+ <div>
103
+ {turns.map((t, i) => (
104
+ <div key={i} style={{ textAlign: t.role === "user" ? "right" : "left" }}>
105
+ {t.content}
106
+ </div>
107
+ ))}
108
+ {loading && <p>Thinking…</p>}
109
+ <button onClick={() => send("Hello!")}>Send</button>
110
+ {!isEscalated && <button onClick={() => escalate()}>Talk to a human</button>}
111
+ </div>
112
+ );
113
+ }
114
+ ```
115
+
116
+ **Returns:**
117
+
118
+ | Value | Type | Description |
119
+ | ------------- | ------------------------------------------------------------------------ | -------------------------------------------------- |
120
+ | `turns` | `Turn[]` | All conversation turns |
121
+ | `loading` | `boolean` | True while AI response is in flight |
122
+ | `streaming` | `string` | Partial token stream during SSE |
123
+ | `sessionId` | `string \| null` | Current session ID (null until first message) |
124
+ | `isEscalated` | `boolean` | True once handed over to a human agent |
125
+ | `send` | `(message: string) => Promise<void>` | Send a message — auto-routes to live agent if live |
126
+ | `escalate` | `(reason?: string) => Promise<void>` | Manually trigger human handover |
127
+ | `react` | `(turnIndex: number, reaction: "helpful"\|"unhelpful") => Promise<void>` | Submit a thumbs reaction |
128
+ | `reset` | `() => void` | Clear all turns and start fresh |
129
+
130
+ ---
131
+
132
+ ### `useLiveSession()`
133
+
134
+ Live agent message sync (polls every 3 s when escalated).
135
+
136
+ ```tsx
137
+ import { useLiveSession } from "@usepanacea/react";
138
+
139
+ function LiveIndicator() {
140
+ const { isLive, agentMessages, sendMessage } = useLiveSession();
141
+
142
+ if (!isLive) return null;
143
+
144
+ return (
145
+ <div>
146
+ <p>Connected to a live agent</p>
147
+ {agentMessages.map((m) => (
148
+ <p key={m.id}>{m.content}</p>
149
+ ))}
150
+ <button onClick={() => sendMessage("Can you help?")}>Reply</button>
151
+ </div>
152
+ );
153
+ }
154
+ ```
155
+
156
+ **Returns:**
157
+
158
+ | Value | Type | Description |
159
+ | --------------- | ------------------------------------ | --------------------------------------------- |
160
+ | `isLive` | `boolean` | True when session is under live agent control |
161
+ | `escalationId` | `string \| null` | Active escalation ID |
162
+ | `liveMessages` | `LiveMessage[]` | All live messages (both directions) |
163
+ | `agentMessages` | `LiveMessage[]` | Filtered to agent-sent messages only |
164
+ | `sendMessage` | `(content: string) => Promise<void>` | Send a customer message to the live agent |
165
+
166
+ ---
167
+
168
+ ### `useWidget()`
169
+
170
+ Open/close state for the chat panel.
171
+
172
+ ```tsx
173
+ import { useWidget } from "@usepanacea/react";
174
+
175
+ function MyFAB() {
176
+ const { isOpen, toggle } = useWidget();
177
+ return <button onClick={toggle}>{isOpen ? "Close" : "Chat"}</button>;
178
+ }
179
+ ```
180
+
181
+ **Returns:** `{ isOpen, open, close, toggle }`
182
+
183
+ ---
184
+
185
+ ## Pre-built Components
186
+
187
+ All pre-built components accept a `className` prop for Tailwind overrides.
188
+
189
+ ### `<PanaceaChat />`
190
+
191
+ Full panel — FAB + collapsible chat panel. Renders `PanaceaFAB`, `PanaceaMessages`, and `PanaceaInput` together.
192
+
193
+ ```tsx
194
+ <PanaceaChat
195
+ title="Support" // panel header text (default: "Support")
196
+ placeholder="Ask…" // input placeholder
197
+ className="" // extra classes on the panel wrapper
198
+ fabClassName="" // extra classes on the FAB button
199
+ />
200
+ ```
201
+
202
+ ### `<PanaceaMessages />`
203
+
204
+ Scrollable message list. Merges AI turns with live agent turns, shows a typing indicator while streaming.
205
+
206
+ ```tsx
207
+ <PanaceaMessages className="h-96" />
208
+ ```
209
+
210
+ ### `<PanaceaInput />`
211
+
212
+ Rounded-pill text input + Send button. Shows "Reply to agent…" placeholder when session is escalated.
213
+
214
+ ```tsx
215
+ <PanaceaInput placeholder="Type a message…" className="" />
216
+ ```
217
+
218
+ ### `<PanaceaFAB />`
219
+
220
+ Fixed floating action button, bottom-right.
221
+
222
+ ```tsx
223
+ <PanaceaFAB className="bottom-8 right-8" />
224
+ ```
225
+
226
+ ---
227
+
228
+ ## Custom UI with Headless Hooks
229
+
230
+ ```tsx
231
+ import { PanaceaProvider, useChat, useLiveSession, useWidget } from "@usepanacea/react";
232
+
233
+ function MyCustomChat() {
234
+ const { turns, send, isEscalated, loading } = useChat();
235
+ const { isLive, agentMessages } = useLiveSession();
236
+ const { isOpen, toggle } = useWidget();
237
+
238
+ return (
239
+ <div>
240
+ <button onClick={toggle}>💬</button>
241
+ {isOpen && (
242
+ <div className="my-chat-panel">
243
+ {turns.map((t, i) => (
244
+ <div key={i} className={t.role === "user" ? "user-bubble" : "ai-bubble"}>
245
+ {t.content}
246
+ </div>
247
+ ))}
248
+ {isLive &&
249
+ agentMessages.map((m) => (
250
+ <div key={m.id} className="agent-bubble">
251
+ {m.content}
252
+ </div>
253
+ ))}
254
+ {loading && <span>…</span>}
255
+ <input
256
+ onKeyDown={(e) => e.key === "Enter" && send(e.currentTarget.value)}
257
+ placeholder={isEscalated ? "Reply to agent…" : "Ask anything…"}
258
+ />
259
+ </div>
260
+ )}
261
+ </div>
262
+ );
263
+ }
264
+
265
+ export default function App() {
266
+ return (
267
+ <PanaceaProvider tenantId="t_xxx" apiBase="https://your-panacea.example.com">
268
+ <MyCustomChat />
269
+ </PanaceaProvider>
270
+ );
271
+ }
272
+ ```
273
+
274
+ ---
275
+
276
+ ## Styling
277
+
278
+ The package ships with design tokens as CSS custom properties so you can theme the pre-built components without forking them:
279
+
280
+ ```css
281
+ /* Override in your global CSS */
282
+ :root {
283
+ --panacea-primary: #6366f1; /* FAB + user bubble + send button */
284
+ --panacea-primary-foreground: #fff;
285
+ --panacea-background: #fff;
286
+ --panacea-foreground: #0f172a;
287
+ --panacea-border: #e2e8f0;
288
+ --panacea-muted: #f1f5f9;
289
+ --panacea-muted-foreground: #64748b;
290
+ --panacea-radius: 0.75rem;
291
+ }
292
+ ```
293
+
294
+ ---
295
+
296
+ ## Exports
297
+
298
+ ```ts
299
+ // Provider + context
300
+ export { PanaceaProvider } from "./provider";
301
+
302
+ // Hooks
303
+ export { useChat } from "./hooks/useChat";
304
+ export { useLiveSession } from "./hooks/useLiveSession";
305
+ export { useWidget } from "./hooks/useWidget";
306
+
307
+ // Pre-built components
308
+ export { PanaceaChat, PanaceaMessages, PanaceaInput, PanaceaFAB } from "./components";
309
+
310
+ // Types
311
+ export type { PanaceaConfig, Turn, LiveMessage, Source, PanaceaContextValue } from "./types";
312
+ ```
313
+
314
+ CSS: `import "@usepanacea/react/styles.css"` (only needed without Tailwind v4 in the host app)
315
+
316
+ ---
317
+
318
+ ## Build
319
+
320
+ ```bash
321
+ # JS + types
322
+ pnpm build:js
323
+
324
+ # Pre-built CSS
325
+ pnpm build:css
326
+
327
+ # Both
328
+ pnpm build
329
+ ```
330
+
331
+ Output in `dist/`: `index.js` (ESM), `index.cjs` (CJS), `index.d.ts`, `styles.css`.