@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 +331 -0
- package/dist/index.cjs +550 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +112 -0
- package/dist/index.d.ts +112 -0
- package/dist/index.js +541 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +2 -0
- package/package.json +61 -0
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`.
|