@helpway/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 +130 -0
- package/dist/index.d.ts +257 -0
- package/dist/index.js +4164 -0
- package/dist/index.js.map +1 -0
- package/package.json +72 -0
- package/src/themed/styles.css +3475 -0
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# @helpway/react
|
|
2
|
+
|
|
3
|
+
Official React SDK for [Helpway](https://helpway.ai) — AI-powered customer support in a single component.
|
|
4
|
+
|
|
5
|
+
Two integration levels in one package:
|
|
6
|
+
|
|
7
|
+
- **Drop-in**: mount `<HelpwayWidget apiKey="..." />` anywhere in your tree and you're done.
|
|
8
|
+
- **Headless**: use `SupportProvider` + hooks (`useWidget`, `useConversations`, `useTimeline`, `useComposer`) to build your own chat UI against the same realtime backend.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @helpway/react
|
|
14
|
+
# or
|
|
15
|
+
bun add @helpway/react
|
|
16
|
+
# or
|
|
17
|
+
pnpm add @helpway/react
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Peer dependencies: `react` ≥ 18 and `react-dom` ≥ 18.
|
|
21
|
+
|
|
22
|
+
## Drop-in
|
|
23
|
+
|
|
24
|
+
Grab your public API key from the Helpway dashboard → Settings → API keys (format: `pk_live_…`).
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
// app/layout.tsx (Next.js App Router)
|
|
28
|
+
import { HelpwayWidget } from "@helpway/react";
|
|
29
|
+
|
|
30
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
31
|
+
return (
|
|
32
|
+
<html>
|
|
33
|
+
<body>
|
|
34
|
+
{children}
|
|
35
|
+
<HelpwayWidget apiKey="pk_live_XXXXXXXX" />
|
|
36
|
+
</body>
|
|
37
|
+
</html>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
// main.tsx (Vite / CRA)
|
|
44
|
+
import { HelpwayWidget } from "@helpway/react";
|
|
45
|
+
import { StrictMode } from "react";
|
|
46
|
+
import { createRoot } from "react-dom/client";
|
|
47
|
+
import App from "./App";
|
|
48
|
+
|
|
49
|
+
createRoot(document.getElementById("root")!).render(
|
|
50
|
+
<StrictMode>
|
|
51
|
+
<App />
|
|
52
|
+
<HelpwayWidget apiKey="pk_live_XXXXXXXX" />
|
|
53
|
+
</StrictMode>,
|
|
54
|
+
);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The component injects the Helpway widget loader script into `document.body`, handles StrictMode double-mounts, and removes the script on unmount.
|
|
58
|
+
|
|
59
|
+
### Props
|
|
60
|
+
|
|
61
|
+
| Prop | Type | Default | Description |
|
|
62
|
+
| -------- | -------- | ------------------------------------ | --------------------------------------------- |
|
|
63
|
+
| `apiKey` | `string` | — | Public key from the dashboard. Required. |
|
|
64
|
+
| `src` | `string` | `https://api.helpway.ai/widget.js` | Override for staging / self-hosted setups. |
|
|
65
|
+
|
|
66
|
+
### Identifying logged-in users
|
|
67
|
+
|
|
68
|
+
After your user signs in, call the global `Helpway` identify function so the dashboard sees their real name and email instead of `Anonymous`:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
window.Helpway("identify", {
|
|
72
|
+
uniqueId: user.id,
|
|
73
|
+
name: user.name,
|
|
74
|
+
email: user.email,
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Headless
|
|
79
|
+
|
|
80
|
+
If you want to render your own chat UI (custom bubbles, different layout, embedded in your app instead of a floating button), use the provider + hooks:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import { SupportProvider, useConversations, useTimeline, useComposer } from "@helpway/react";
|
|
84
|
+
|
|
85
|
+
function ChatSurface() {
|
|
86
|
+
const { conversations, activeConversationId, setActive } = useConversations();
|
|
87
|
+
const { items } = useTimeline();
|
|
88
|
+
const { draft, setDraft, send } = useComposer();
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div>
|
|
92
|
+
{/* render conversations, messages, composer — full control */}
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export default function App() {
|
|
98
|
+
return (
|
|
99
|
+
<SupportProvider apiKey="pk_live_XXXXXXXX">
|
|
100
|
+
<ChatSurface />
|
|
101
|
+
</SupportProvider>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Exported hooks:
|
|
107
|
+
|
|
108
|
+
- `useWidget()` — global state, openers/closers
|
|
109
|
+
- `useConversations()` — list + active conversation
|
|
110
|
+
- `useTimeline()` — messages + events for the active conversation
|
|
111
|
+
- `useComposer()` — draft state + send/attach
|
|
112
|
+
- `usePresence()` — online team members
|
|
113
|
+
- `useUnread()` — unread counts
|
|
114
|
+
- `useRealtime()` — raw WebSocket event stream
|
|
115
|
+
|
|
116
|
+
Full API docs: [helpway.ai/docs/react](https://helpway.ai/docs/react)
|
|
117
|
+
|
|
118
|
+
## Styling
|
|
119
|
+
|
|
120
|
+
The default themed widget ships opinionated styles. If you use `DefaultWidget`, pull in the stylesheet once:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import "@helpway/react/styles.css";
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The headless hooks have no styles — bring your own.
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
MIT © Helpway
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as _helpway_widget_core from '@helpway/widget-core';
|
|
3
|
+
import { WidgetStore } from '@helpway/widget-core';
|
|
4
|
+
export { ConversationSummary, RealtimeEvent, TimelineItem, WidgetState, WidgetStore, clearVisitorId, formatRelativeTime, formatTime, getInitials, getOrCreateVisitorId } from '@helpway/widget-core';
|
|
5
|
+
import * as react from 'react';
|
|
6
|
+
import react__default, { ReactNode } from 'react';
|
|
7
|
+
import * as _helpway_types from '@helpway/types';
|
|
8
|
+
import { TimelineItem, RealtimeEvent, TimelineItemPart } from '@helpway/types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `<HelpwayWidget />` — one-line drop-in React component that loads
|
|
12
|
+
* the Helpway widget script and tears it down on unmount.
|
|
13
|
+
*
|
|
14
|
+
* This is the lowest-friction integration path for React / Next.js /
|
|
15
|
+
* Vite apps that don't need to render custom chat UI themselves. Under
|
|
16
|
+
* the hood it just injects `https://api.helpway.ai/widget.js` with the
|
|
17
|
+
* provided public key, exactly like the plain `<script>` snippet — but
|
|
18
|
+
* wrapped in a React lifecycle so it participates in StrictMode double
|
|
19
|
+
* mounts, SSR boundaries, and component cleanup without footguns.
|
|
20
|
+
*
|
|
21
|
+
* Power-user alternative: import `{ SupportProvider, useWidget, … }`
|
|
22
|
+
* from this same package for full programmatic control.
|
|
23
|
+
*/
|
|
24
|
+
interface HelpwayWidgetProps {
|
|
25
|
+
/**
|
|
26
|
+
* Public API key from the Helpway dashboard. Required.
|
|
27
|
+
* Format: `pk_live_…` or `pk_test_…`.
|
|
28
|
+
*/
|
|
29
|
+
apiKey: string;
|
|
30
|
+
/**
|
|
31
|
+
* Override the default widget loader URL. Almost no one needs this —
|
|
32
|
+
* exposed for staging / self-hosted setups.
|
|
33
|
+
* @default "https://api.helpway.ai/widget.js"
|
|
34
|
+
*/
|
|
35
|
+
src?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Render this once at the root of your app (e.g. in `_app.tsx`,
|
|
39
|
+
* `layout.tsx`, or `main.tsx`). Unmounting removes the script and
|
|
40
|
+
* collapses the widget iframe — useful for route-scoped mounts.
|
|
41
|
+
*/
|
|
42
|
+
declare function HelpwayWidget({ apiKey, src, }: HelpwayWidgetProps): null;
|
|
43
|
+
|
|
44
|
+
interface IdentifyData {
|
|
45
|
+
externalId: string;
|
|
46
|
+
email?: string;
|
|
47
|
+
name?: string;
|
|
48
|
+
image?: string;
|
|
49
|
+
metadata?: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
/** Use a slice of the widget store with auto re-render */
|
|
52
|
+
declare function useWidgetStore<T>(selector: (state: WidgetStore) => T): T;
|
|
53
|
+
/** Hook to identify the current visitor (link to customer contact) */
|
|
54
|
+
declare function useIdentify(): (data: IdentifyData) => Promise<void>;
|
|
55
|
+
interface SupportProviderProps {
|
|
56
|
+
/** Public API key (pk_xxx) */
|
|
57
|
+
apiKey: string;
|
|
58
|
+
/** API base URL (default: auto-detect from script tag or window) */
|
|
59
|
+
apiUrl?: string;
|
|
60
|
+
/** Widget locale (e.g., "fr", "de"). Falls back to navigator.language → "en" */
|
|
61
|
+
locale?: string;
|
|
62
|
+
/** Deep link: auto-open this conversation after bootstrap (from ?hw_conv= URL param) */
|
|
63
|
+
initialConversationId?: string;
|
|
64
|
+
/** Callback to expose widget API to parent (for embed loader) */
|
|
65
|
+
onReady?: (api: {
|
|
66
|
+
identify: (data: IdentifyData) => Promise<void>;
|
|
67
|
+
openConversation: (id: string) => void;
|
|
68
|
+
}) => void;
|
|
69
|
+
children: ReactNode;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Root provider for the Helpway widget.
|
|
73
|
+
* Creates tRPC client, WebSocket connection, and state store.
|
|
74
|
+
*
|
|
75
|
+
* Visitor identity is managed via server-side cookies (hw_visitor).
|
|
76
|
+
* No localStorage or client-side ID management needed.
|
|
77
|
+
*/
|
|
78
|
+
declare function SupportProvider({ apiKey, apiUrl, locale, initialConversationId, onReady, children, }: SupportProviderProps): react_jsx_runtime.JSX.Element;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Core widget state — connection status, visitor identity, website info.
|
|
82
|
+
*
|
|
83
|
+
* Usage:
|
|
84
|
+
* ```tsx
|
|
85
|
+
* const { isReady, visitor, website, identify } = useWidget()
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
declare function useWidget(): {
|
|
89
|
+
isReady: boolean;
|
|
90
|
+
isConnecting: boolean;
|
|
91
|
+
visitor: _helpway_types.PublicVisitor | null;
|
|
92
|
+
website: {
|
|
93
|
+
id: string;
|
|
94
|
+
name: string;
|
|
95
|
+
domain: string | null;
|
|
96
|
+
logoUrl: string | null;
|
|
97
|
+
} | null;
|
|
98
|
+
agentConfig: _helpway_widget_core.AgentConfig | null;
|
|
99
|
+
identify: (data: {
|
|
100
|
+
externalId: string;
|
|
101
|
+
email?: string;
|
|
102
|
+
name?: string;
|
|
103
|
+
image?: string;
|
|
104
|
+
metadata?: Record<string, unknown>;
|
|
105
|
+
}) => Promise<any>;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Conversation list + create new conversation.
|
|
110
|
+
*/
|
|
111
|
+
declare function useConversations(): {
|
|
112
|
+
conversations: _helpway_widget_core.ConversationSummary[];
|
|
113
|
+
create: (text: string) => Promise<any>;
|
|
114
|
+
isCreating: boolean;
|
|
115
|
+
setActiveConversation: (id: string | null) => void;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Timeline items for a conversation + real-time updates.
|
|
120
|
+
*/
|
|
121
|
+
declare function useTimeline(conversationId: string | null): {
|
|
122
|
+
items: TimelineItem[];
|
|
123
|
+
isLoading: boolean;
|
|
124
|
+
hasMore: boolean;
|
|
125
|
+
loadMore: () => Promise<void>;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
interface PendingAttachment {
|
|
129
|
+
id: string;
|
|
130
|
+
file: File;
|
|
131
|
+
previewUrl: string | null;
|
|
132
|
+
isUploading: boolean;
|
|
133
|
+
error: string | null;
|
|
134
|
+
/** Resolved after upload — the part to include in sendMessage */
|
|
135
|
+
part: {
|
|
136
|
+
type: "image";
|
|
137
|
+
url: string;
|
|
138
|
+
mimeType: string;
|
|
139
|
+
} | {
|
|
140
|
+
type: "file";
|
|
141
|
+
url: string;
|
|
142
|
+
fileName: string;
|
|
143
|
+
fileSize: number;
|
|
144
|
+
mimeType: string;
|
|
145
|
+
} | null;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Composer state — text input + attachments + submit for a conversation.
|
|
149
|
+
*/
|
|
150
|
+
declare function useComposer(conversationId: string | null): {
|
|
151
|
+
text: string;
|
|
152
|
+
setText: react.Dispatch<react.SetStateAction<string>>;
|
|
153
|
+
submit: () => Promise<void>;
|
|
154
|
+
isSubmitting: boolean;
|
|
155
|
+
attachments: PendingAttachment[];
|
|
156
|
+
addFiles: (files: File[]) => Promise<void>;
|
|
157
|
+
removeAttachment: (id: string) => void;
|
|
158
|
+
sendError: string | null;
|
|
159
|
+
clearSendError: () => void;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Typing presence — show "is typing" + send own typing status.
|
|
164
|
+
*
|
|
165
|
+
* Debounced: only sends WS event when typing state *changes*.
|
|
166
|
+
* While typing, auto-stops after 3 seconds of inactivity.
|
|
167
|
+
*
|
|
168
|
+
* Usage:
|
|
169
|
+
* ```tsx
|
|
170
|
+
* const { isTyping, setTyping } = usePresence(conversationId)
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
declare function usePresence(conversationId: string | null): {
|
|
174
|
+
isTyping: boolean;
|
|
175
|
+
isAiProcessing: boolean;
|
|
176
|
+
setTyping: (typing: boolean) => void;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Total unread count across all conversations.
|
|
181
|
+
*
|
|
182
|
+
* Usage:
|
|
183
|
+
* ```tsx
|
|
184
|
+
* const { totalUnread } = useUnread()
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
declare function useUnread(): {
|
|
188
|
+
totalUnread: number;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Subscribe to raw WebSocket events.
|
|
193
|
+
* For advanced usage — most apps should use useTimeline/usePresence instead.
|
|
194
|
+
*
|
|
195
|
+
* Usage:
|
|
196
|
+
* ```tsx
|
|
197
|
+
* useRealtime((event) => {
|
|
198
|
+
* if (event.type === 'timelineItemCreated') { ... }
|
|
199
|
+
* })
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
declare function useRealtime(handler: (event: RealtimeEvent) => void): void;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Unified support widget — Intercom-style with tab navigation.
|
|
206
|
+
* Two tabs: Home + Messages. Sub-views: conversation + new conversation.
|
|
207
|
+
*/
|
|
208
|
+
declare function DefaultWidget({ initialOpen }?: {
|
|
209
|
+
initialOpen?: boolean;
|
|
210
|
+
}): react_jsx_runtime.JSX.Element;
|
|
211
|
+
|
|
212
|
+
interface MessageBubbleProps {
|
|
213
|
+
text: string;
|
|
214
|
+
sender: "visitor" | "team" | "ai";
|
|
215
|
+
senderName?: string;
|
|
216
|
+
time: string;
|
|
217
|
+
parts?: TimelineItemPart[] | null;
|
|
218
|
+
}
|
|
219
|
+
/** Single message bubble — Intercom-style, renders text + image/file parts */
|
|
220
|
+
declare function MessageBubble({ text, sender, senderName, time, parts }: MessageBubbleProps): react_jsx_runtime.JSX.Element;
|
|
221
|
+
|
|
222
|
+
interface MessageListProps {
|
|
223
|
+
items: TimelineItem[];
|
|
224
|
+
visitorName?: string;
|
|
225
|
+
isTyping?: boolean;
|
|
226
|
+
isAiProcessing?: boolean;
|
|
227
|
+
hasMore?: boolean;
|
|
228
|
+
isLoadingMore?: boolean;
|
|
229
|
+
onLoadMore?: () => void;
|
|
230
|
+
/** Slot rendered after all messages but before typing indicators (e.g. email prompt) */
|
|
231
|
+
renderAfterMessages?: react__default.ReactNode;
|
|
232
|
+
}
|
|
233
|
+
/** Timeline with date separators, typing indicator, infinite scroll, and AI processing state */
|
|
234
|
+
declare function MessageList({ items, visitorName, isTyping, isAiProcessing, hasMore, isLoadingMore, onLoadMore, renderAfterMessages, }: MessageListProps): react_jsx_runtime.JSX.Element;
|
|
235
|
+
|
|
236
|
+
interface ComposerProps {
|
|
237
|
+
text: string;
|
|
238
|
+
onTextChange: (text: string) => void;
|
|
239
|
+
onSubmit: () => void;
|
|
240
|
+
onTyping?: (isTyping: boolean) => void;
|
|
241
|
+
isSubmitting?: boolean;
|
|
242
|
+
placeholder?: string;
|
|
243
|
+
/** Compact mode — single-line input without footer row */
|
|
244
|
+
compact?: boolean;
|
|
245
|
+
/** Pending attachments to preview */
|
|
246
|
+
attachments?: PendingAttachment[];
|
|
247
|
+
/** Called when files are added (paste, drop, or file picker) */
|
|
248
|
+
onAddFiles?: (files: File[]) => void;
|
|
249
|
+
/** Called to remove a pending attachment */
|
|
250
|
+
onRemoveAttachment?: (id: string) => void;
|
|
251
|
+
/** Disable entire composer (e.g. email required before sending) */
|
|
252
|
+
disabled?: boolean;
|
|
253
|
+
}
|
|
254
|
+
/** Widget composer — auto-resize, Enter-to-send, paste/drop attachments */
|
|
255
|
+
declare function Composer({ text, onTextChange, onSubmit, onTyping, isSubmitting, placeholder, compact, attachments, onAddFiles, onRemoveAttachment, disabled, }: ComposerProps): react_jsx_runtime.JSX.Element;
|
|
256
|
+
|
|
257
|
+
export { Composer, DefaultWidget, HelpwayWidget, type HelpwayWidgetProps, type IdentifyData, MessageBubble, MessageList, SupportProvider, useComposer, useConversations, useIdentify, usePresence, useRealtime, useTimeline, useUnread, useWidget, useWidgetStore };
|