@bernierllc/onboarding-chat-ui 0.0.1 → 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/LICENSE +5 -0
- package/README.md +344 -28
- package/dist/components/AttachmentDropzone.d.ts +11 -0
- package/dist/components/AttachmentDropzone.d.ts.map +1 -0
- package/dist/components/AttachmentDropzone.js +128 -0
- package/dist/components/AttachmentDropzone.js.map +1 -0
- package/dist/components/ChatPane.d.ts +12 -0
- package/dist/components/ChatPane.d.ts.map +1 -0
- package/dist/components/ChatPane.js +54 -0
- package/dist/components/ChatPane.js.map +1 -0
- package/dist/components/CompletionCard.d.ts +9 -0
- package/dist/components/CompletionCard.d.ts.map +1 -0
- package/dist/components/CompletionCard.js +14 -0
- package/dist/components/CompletionCard.js.map +1 -0
- package/dist/components/OnboardingChat.d.ts +14 -0
- package/dist/components/OnboardingChat.d.ts.map +1 -0
- package/dist/components/OnboardingChat.js +72 -0
- package/dist/components/OnboardingChat.js.map +1 -0
- package/dist/components/ProgressChecklist.d.ts +9 -0
- package/dist/components/ProgressChecklist.d.ts.map +1 -0
- package/dist/components/ProgressChecklist.js +20 -0
- package/dist/components/ProgressChecklist.js.map +1 -0
- package/dist/components/RatingBar.d.ts +9 -0
- package/dist/components/RatingBar.d.ts.map +1 -0
- package/dist/components/RatingBar.js +38 -0
- package/dist/components/RatingBar.js.map +1 -0
- package/dist/components/SidePanel.d.ts +11 -0
- package/dist/components/SidePanel.d.ts.map +1 -0
- package/dist/components/SidePanel.js +13 -0
- package/dist/components/SidePanel.js.map +1 -0
- package/dist/context/OnboardingChatContext.d.ts +29 -0
- package/dist/context/OnboardingChatContext.d.ts.map +1 -0
- package/dist/context/OnboardingChatContext.js +30 -0
- package/dist/context/OnboardingChatContext.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +27 -0
- package/dist/errors.js.map +1 -0
- package/dist/hooks/useAutoGreeting.d.ts +9 -0
- package/dist/hooks/useAutoGreeting.d.ts.map +1 -0
- package/dist/hooks/useAutoGreeting.js +36 -0
- package/dist/hooks/useAutoGreeting.js.map +1 -0
- package/dist/hooks/useGoalProgress.d.ts +10 -0
- package/dist/hooks/useGoalProgress.d.ts.map +1 -0
- package/dist/hooks/useGoalProgress.js +48 -0
- package/dist/hooks/useGoalProgress.js.map +1 -0
- package/dist/hooks/useOnboardingChat.d.ts +16 -0
- package/dist/hooks/useOnboardingChat.d.ts.map +1 -0
- package/dist/hooks/useOnboardingChat.js +172 -0
- package/dist/hooks/useOnboardingChat.js.map +1 -0
- package/dist/hooks/usePanelRegistry.d.ts +11 -0
- package/dist/hooks/usePanelRegistry.d.ts.map +1 -0
- package/dist/hooks/usePanelRegistry.js +44 -0
- package/dist/hooks/usePanelRegistry.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/types/ui-types.d.ts +161 -0
- package/dist/types/ui-types.d.ts.map +1 -0
- package/dist/types/ui-types.js +9 -0
- package/dist/types/ui-types.js.map +1 -0
- package/package.json +70 -7
package/LICENSE
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Copyright (c) 2025 Bernier LLC
|
|
2
|
+
|
|
3
|
+
This file is licensed to the client under a limited-use license.
|
|
4
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
5
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
package/README.md
CHANGED
|
@@ -1,45 +1,361 @@
|
|
|
1
1
|
# @bernierllc/onboarding-chat-ui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React UI component library for onboarding chat experiences. Provides a full-featured streaming chat interface backed by Server-Sent Events (SSE), goal progress tracking, plugin-driven side panels, and completion handoffs — all wired to `@bernierllc/onboarding-config-core` configuration objects.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install @bernierllc/onboarding-chat-ui
|
|
9
|
+
```
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
Peer dependencies:
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
3. Establish provenance for packages published under this name
|
|
13
|
+
```bash
|
|
14
|
+
npm install react react-dom @bernierllc/onboarding-config-core @bernierllc/onboarding-feature-plugin
|
|
15
|
+
```
|
|
15
16
|
|
|
16
|
-
##
|
|
17
|
+
## Usage
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
```tsx
|
|
20
|
+
import { OnboardingChat } from '@bernierllc/onboarding-chat-ui';
|
|
21
|
+
import type { OnboardingConfig } from '@bernierllc/onboarding-config-core';
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
const config: OnboardingConfig = {
|
|
24
|
+
id: 'my-onboarding',
|
|
25
|
+
name: 'Getting Started',
|
|
26
|
+
persona: {
|
|
27
|
+
tone: 'friendly-expert',
|
|
28
|
+
greeting: "Hi! Let's get you set up.",
|
|
29
|
+
askOneAtATime: true,
|
|
30
|
+
},
|
|
31
|
+
phases: [{ id: 'setup', title: 'Setup', guidance: 'Configure your account.', order: 1 }],
|
|
32
|
+
goals: [
|
|
33
|
+
{
|
|
34
|
+
id: 'add-logo',
|
|
35
|
+
description: 'Add your logo',
|
|
36
|
+
required: true,
|
|
37
|
+
phase: 'setup',
|
|
38
|
+
completionCheck: { type: 'field-present', field: 'logoUrl' },
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
completionCriteria: { requireAllGoals: true },
|
|
42
|
+
handoffs: [{ id: 'dashboard', type: 'route', label: 'Go to Dashboard', order: 1 }],
|
|
43
|
+
features: { enabledPlugins: [], pluginConfig: {} },
|
|
44
|
+
};
|
|
21
45
|
|
|
22
|
-
|
|
46
|
+
export default function OnboardingPage() {
|
|
47
|
+
return (
|
|
48
|
+
<OnboardingChat
|
|
49
|
+
chatEndpoint="/api/onboarding/chat"
|
|
50
|
+
sessionId="sess-abc123"
|
|
51
|
+
userId="user-xyz"
|
|
52
|
+
config={config}
|
|
53
|
+
onComplete={(status) => console.log('Onboarding complete', status)}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
23
58
|
|
|
24
|
-
|
|
25
|
-
2. Configure the trusted publisher (e.g., GitHub Actions)
|
|
26
|
-
3. Specify the repository and workflow that should be allowed to publish
|
|
27
|
-
4. Use the configured workflow to publish your actual package
|
|
59
|
+
### Custom Hooks Usage
|
|
28
60
|
|
|
29
|
-
|
|
61
|
+
Use the low-level hooks directly to build a custom UI:
|
|
30
62
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
- Provides no functionality
|
|
34
|
-
- Should not be installed as a dependency
|
|
35
|
-
- Exists only for administrative purposes
|
|
63
|
+
```tsx
|
|
64
|
+
import { useOnboardingChat, useGoalProgress } from '@bernierllc/onboarding-chat-ui';
|
|
36
65
|
|
|
37
|
-
|
|
66
|
+
function MyCustomChat({ config }) {
|
|
67
|
+
const { messages, send, isStreaming, setupStatus, isComplete, error } = useOnboardingChat({
|
|
68
|
+
chatEndpoint: '/api/chat',
|
|
69
|
+
sessionId: 'sess-1',
|
|
70
|
+
userId: 'user-1',
|
|
71
|
+
});
|
|
38
72
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
73
|
+
const { goalItems, requiredMet, requiredTotal } = useGoalProgress({
|
|
74
|
+
goals: config.goals,
|
|
75
|
+
setupStatus,
|
|
76
|
+
});
|
|
42
77
|
|
|
43
|
-
|
|
78
|
+
return (
|
|
79
|
+
<div>
|
|
80
|
+
{error && <p>Error: {error.message}</p>}
|
|
81
|
+
<p>{requiredMet}/{requiredTotal} goals complete</p>
|
|
82
|
+
{messages.map(m => <div key={m.id}>{m.role}: {m.content}</div>)}
|
|
83
|
+
{isComplete && <p>All done!</p>}
|
|
84
|
+
<button disabled={isStreaming} onClick={() => send('Hello')}>Send</button>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
44
89
|
|
|
45
|
-
|
|
90
|
+
## API
|
|
91
|
+
|
|
92
|
+
### `OnboardingChat` Component
|
|
93
|
+
|
|
94
|
+
Root orchestrator component. Renders the three-column layout: chat pane, progress checklist, and side panel.
|
|
95
|
+
|
|
96
|
+
| Prop | Type | Required | Description |
|
|
97
|
+
|------|------|----------|-------------|
|
|
98
|
+
| `chatEndpoint` | `string` | Yes | POST endpoint for chat messages |
|
|
99
|
+
| `config` | `OnboardingConfig` | Yes | Onboarding configuration object |
|
|
100
|
+
| `sessionId` | `string` | Yes | Unique session identifier |
|
|
101
|
+
| `userId` | `string` | Yes | Current user identifier |
|
|
102
|
+
| `statusEndpoint` | `string` | No | Endpoint for goal status polling |
|
|
103
|
+
| `ratingEndpoint` | `string` | No | Endpoint for thumbs up/down ratings |
|
|
104
|
+
| `panelDescriptors` | `PluginPanelDescriptor[]` | No | Plugin panel slot declarations |
|
|
105
|
+
| `panelComponents` | `Record<string, React.ComponentType>` | No | React components keyed by panel slot |
|
|
106
|
+
| `onComplete` | `(status: SetupStatus \| null) => void` | No | Callback when onboarding completes |
|
|
107
|
+
| `fetchFn` | `typeof fetch` | No | Injectable fetch (useful for tests) |
|
|
108
|
+
| `className` | `string` | No | Additional CSS class |
|
|
109
|
+
|
|
110
|
+
### `ChatPane` Component
|
|
111
|
+
|
|
112
|
+
The message thread plus input area.
|
|
113
|
+
|
|
114
|
+
| Prop | Type | Required | Description |
|
|
115
|
+
|------|------|----------|-------------|
|
|
116
|
+
| `messages` | `OnboardingMessage[]` | Yes | Current message list |
|
|
117
|
+
| `isStreaming` | `boolean` | Yes | Whether an SSE stream is in progress |
|
|
118
|
+
| `onSend` | `(text: string, attachments?: Attachment[]) => void` | Yes | Send handler |
|
|
119
|
+
| `className` | `string` | No | Additional CSS class |
|
|
120
|
+
|
|
121
|
+
### `ProgressChecklist` Component
|
|
122
|
+
|
|
123
|
+
Renders goal items with check/incomplete badges, driven by `SetupStatus` from the SSE stream.
|
|
124
|
+
|
|
125
|
+
| Prop | Type | Required | Description |
|
|
126
|
+
|------|------|----------|-------------|
|
|
127
|
+
| `goals` | `OnboardingGoal[]` | Yes | Goals from `OnboardingConfig` |
|
|
128
|
+
| `status` | `SetupStatus \| null` | Yes | Latest status from SSE stream |
|
|
129
|
+
| `showRequired` | `boolean` | No | Filter to required goals only |
|
|
130
|
+
| `className` | `string` | No | Additional CSS class |
|
|
131
|
+
|
|
132
|
+
### `SidePanel` Component
|
|
133
|
+
|
|
134
|
+
Plugin slot container. Renders the component registered for the currently active panel slot.
|
|
135
|
+
|
|
136
|
+
| Prop | Type | Required | Description |
|
|
137
|
+
|------|------|----------|-------------|
|
|
138
|
+
| `activePanel` | `string \| null` | Yes | Currently active slot key |
|
|
139
|
+
| `panelComponents` | `Record<string, React.ComponentType>` | Yes | Component map by slot |
|
|
140
|
+
| `fallback` | `React.ReactNode` | No | Rendered when no active panel |
|
|
141
|
+
| `className` | `string` | No | Additional CSS class |
|
|
142
|
+
|
|
143
|
+
### `CompletionCard` Component
|
|
144
|
+
|
|
145
|
+
Shown when the onboarding is complete.
|
|
146
|
+
|
|
147
|
+
| Prop | Type | Required | Description |
|
|
148
|
+
|------|------|----------|-------------|
|
|
149
|
+
| `onboardingName` | `string` | Yes | Display name of the onboarding flow |
|
|
150
|
+
| `handoffs` | `HandoffTarget[]` | Yes | Available next steps from config |
|
|
151
|
+
| `onHandoffClick` | `(target: HandoffTarget) => void` | Yes | Called when user clicks a handoff |
|
|
152
|
+
| `children` | `React.ReactNode` | No | Optional extra content |
|
|
153
|
+
| `className` | `string` | No | Additional CSS class |
|
|
154
|
+
|
|
155
|
+
### `RatingBar` Component
|
|
156
|
+
|
|
157
|
+
Thumbs up / thumbs down rating widget.
|
|
158
|
+
|
|
159
|
+
| Prop | Type | Required | Description |
|
|
160
|
+
|------|------|----------|-------------|
|
|
161
|
+
| `onRate` | `(rating: 'up' \| 'down') => void` | Yes | Called with the selected rating |
|
|
162
|
+
| `className` | `string` | No | Additional CSS class |
|
|
163
|
+
|
|
164
|
+
### `AttachmentDropzone` Component
|
|
165
|
+
|
|
166
|
+
Drag-and-drop file picker. Converts files to base64 data URLs and calls `onAttach`.
|
|
167
|
+
|
|
168
|
+
| Prop | Type | Required | Description |
|
|
169
|
+
|------|------|----------|-------------|
|
|
170
|
+
| `onAttach` | `(attachments: Attachment[]) => void` | Yes | Called with converted attachments |
|
|
171
|
+
| `accept` | `string` | No | File types for the hidden input (e.g. `"image/*"`) |
|
|
172
|
+
| `maxSizeMB` | `number` | No | Max file size (default: 5 MB) |
|
|
173
|
+
| `className` | `string` | No | Additional CSS class |
|
|
174
|
+
|
|
175
|
+
### `useOnboardingChat` Hook
|
|
176
|
+
|
|
177
|
+
Core hook managing SSE streaming, message accumulation, and state transitions.
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
import { useOnboardingChat } from '@bernierllc/onboarding-chat-ui';
|
|
181
|
+
|
|
182
|
+
const {
|
|
183
|
+
messages, // OnboardingMessage[]
|
|
184
|
+
send, // (text: string, attachments?: Attachment[]) => void
|
|
185
|
+
isStreaming, // boolean
|
|
186
|
+
toolCallEvents, // ToolCallEvent[]
|
|
187
|
+
lastToolCall, // ToolCallEvent | null
|
|
188
|
+
setupStatus, // SetupStatus | null
|
|
189
|
+
isComplete, // boolean
|
|
190
|
+
error, // Error | null
|
|
191
|
+
} = useOnboardingChat({
|
|
192
|
+
chatEndpoint: '/api/chat',
|
|
193
|
+
sessionId: 'sess-1',
|
|
194
|
+
userId: 'user-1',
|
|
195
|
+
fetchFn: customFetch, // optional, defaults to window.fetch
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Options:**
|
|
200
|
+
|
|
201
|
+
| Option | Type | Required | Description |
|
|
202
|
+
|--------|------|----------|-------------|
|
|
203
|
+
| `chatEndpoint` | `string` | Yes | POST endpoint URL |
|
|
204
|
+
| `sessionId` | `string` | Yes | Session ID sent in request body |
|
|
205
|
+
| `userId` | `string` | Yes | User ID sent in request body |
|
|
206
|
+
| `fetchFn` | `typeof fetch` | No | Injectable fetch (testing/SSR) |
|
|
207
|
+
|
|
208
|
+
### `useGoalProgress` Hook
|
|
209
|
+
|
|
210
|
+
Derives display-ready goal items from raw goals and live `SetupStatus`.
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
import { useGoalProgress } from '@bernierllc/onboarding-chat-ui';
|
|
214
|
+
|
|
215
|
+
const { goalItems, requiredMet, requiredTotal, isComplete } = useGoalProgress({
|
|
216
|
+
goals: config.goals,
|
|
217
|
+
setupStatus,
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### `usePanelRegistry` Hook
|
|
222
|
+
|
|
223
|
+
Determines which plugin panel slot is active based on recent tool call events.
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
import { usePanelRegistry } from '@bernierllc/onboarding-chat-ui';
|
|
227
|
+
|
|
228
|
+
const { activePanel } = usePanelRegistry({
|
|
229
|
+
panelDescriptors: config.features.panelDescriptors ?? [],
|
|
230
|
+
toolCallEvents,
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### `useAutoGreeting` Hook
|
|
235
|
+
|
|
236
|
+
Fires an initial greeting message after a configurable delay when the message list is empty.
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
import { useAutoGreeting } from '@bernierllc/onboarding-chat-ui';
|
|
240
|
+
|
|
241
|
+
useAutoGreeting({
|
|
242
|
+
messages,
|
|
243
|
+
send,
|
|
244
|
+
greeting: config.persona.greeting,
|
|
245
|
+
delayMs: 500, // default
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### `OnboardingChatProvider` / `useOnboardingChatContext`
|
|
250
|
+
|
|
251
|
+
Share chat state across a component subtree without prop drilling.
|
|
252
|
+
|
|
253
|
+
```tsx
|
|
254
|
+
import { OnboardingChatProvider, useOnboardingChatContext } from '@bernierllc/onboarding-chat-ui';
|
|
255
|
+
|
|
256
|
+
function App() {
|
|
257
|
+
return (
|
|
258
|
+
<OnboardingChatProvider value={chatState}>
|
|
259
|
+
<MyPanel />
|
|
260
|
+
</OnboardingChatProvider>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function MyPanel() {
|
|
265
|
+
const { messages, isStreaming } = useOnboardingChatContext();
|
|
266
|
+
return <div>{isStreaming ? 'Thinking...' : `${messages.length} messages`}</div>;
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### `OnboardingUIError`
|
|
271
|
+
|
|
272
|
+
All errors thrown by this package are instances of `OnboardingUIError` with a `code` field and optional `cause` chain.
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
import { OnboardingUIError } from '@bernierllc/onboarding-chat-ui';
|
|
276
|
+
|
|
277
|
+
if (error instanceof OnboardingUIError) {
|
|
278
|
+
console.error(error.code, error.message, error.cause);
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
| Code | Cause |
|
|
283
|
+
|------|-------|
|
|
284
|
+
| `CHAT_REQUEST_FAILED` | Non-2xx HTTP response from chat endpoint |
|
|
285
|
+
| `NO_RESPONSE_BODY` | Successful HTTP response but `body` is null |
|
|
286
|
+
| `STREAM_ERROR` | Exception thrown during streaming (network failure, parse error) |
|
|
287
|
+
| `FILE_READ_ERROR` | `FileReader` error inside `AttachmentDropzone` |
|
|
288
|
+
|
|
289
|
+
## Integration Documentation
|
|
290
|
+
|
|
291
|
+
### Logger Integration
|
|
292
|
+
|
|
293
|
+
This package uses `@bernierllc/logger` for structured error logging. All caught errors are logged at the `error` level with full context before propagating to the caller. The logger auto-detects the service environment; no configuration is required.
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
// Internal usage — no setup needed by consumers
|
|
297
|
+
import { logger } from '@bernierllc/logger';
|
|
298
|
+
logger.error('OnboardingUIError during chat stream', error, { context });
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### NeverHub Integration
|
|
302
|
+
|
|
303
|
+
This package supports optional NeverHub integration for real-time event tracking and session telemetry. When `@bernierllc/neverhub-adapter` is available in the environment and `NeverHubAdapter.detect()` returns `true`, the `OnboardingChat` component registers itself and emits lifecycle events. Core functionality works regardless of NeverHub availability (graceful degradation).
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
// NeverHub auto-detection pattern (internal)
|
|
307
|
+
import { NeverHubAdapter } from '@bernierllc/neverhub-adapter';
|
|
308
|
+
|
|
309
|
+
if (await NeverHubAdapter.detect()) {
|
|
310
|
+
const adapter = new NeverHubAdapter();
|
|
311
|
+
await adapter.register({ type: 'onboarding-chat-ui', sessionId });
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
To enable NeverHub in your app, install and configure the adapter before rendering `OnboardingChat`. No additional props are required.
|
|
316
|
+
|
|
317
|
+
### SSE Stream Protocol
|
|
318
|
+
|
|
319
|
+
The backend chat endpoint must respond with `Content-Type: text/event-stream`. Each event is a newline-delimited JSON frame:
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
data: {"type":"delta","delta":"Hello "}\n\n
|
|
323
|
+
data: {"type":"delta","delta":"world!"}\n\n
|
|
324
|
+
data: {"type":"tool_call","toolName":"preview_site","args":{"url":"https://example.com"}}\n\n
|
|
325
|
+
data: {"type":"status","status":{...GoalProgress...}}\n\n
|
|
326
|
+
data: {"type":"complete"}\n\n
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
| Event type | Payload fields | Effect |
|
|
330
|
+
|------------|---------------|--------|
|
|
331
|
+
| `delta` | `delta: string` | Appends to the current assistant message |
|
|
332
|
+
| `tool_call` | `toolName: string`, `args: unknown` | Pushes to `toolCallEvents`, updates `lastToolCall`, activates side panel |
|
|
333
|
+
| `status` | `status: GoalProgress` | Updates `setupStatus` and goal checklist |
|
|
334
|
+
| `complete` | — | Sets `isComplete: true`, calls `onComplete` callback |
|
|
335
|
+
|
|
336
|
+
### Testing Integration
|
|
337
|
+
|
|
338
|
+
The package provides `fetchFn` prop/option for full test isolation without mocking globals:
|
|
339
|
+
|
|
340
|
+
```tsx
|
|
341
|
+
import { useOnboardingChat } from '@bernierllc/onboarding-chat-ui';
|
|
342
|
+
import { renderHook, act } from '@testing-library/react';
|
|
343
|
+
|
|
344
|
+
const mockFetch: typeof fetch = async () => ({
|
|
345
|
+
ok: true,
|
|
346
|
+
status: 200,
|
|
347
|
+
body: { getReader: () => myCustomReader },
|
|
348
|
+
} as unknown as Response);
|
|
349
|
+
|
|
350
|
+
const { result } = renderHook(() =>
|
|
351
|
+
useOnboardingChat({ chatEndpoint: '/api/chat', sessionId: 's', userId: 'u', fetchFn: mockFetch })
|
|
352
|
+
);
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## Browser Requirements
|
|
356
|
+
|
|
357
|
+
This package is browser-only. It uses `fetch`, `ReadableStream`, `TextDecoder`, and `FileReader`. It does not import Node.js built-ins.
|
|
358
|
+
|
|
359
|
+
## License
|
|
360
|
+
|
|
361
|
+
Copyright (c) 2025 Bernier LLC. Limited-use license — see package header.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { AttachmentDropzoneProps } from '../types/ui-types';
|
|
3
|
+
/**
|
|
4
|
+
* Drag-and-drop (and click-to-browse) attachment dropzone.
|
|
5
|
+
*
|
|
6
|
+
* Converts dropped or selected files to base64 data URLs and calls
|
|
7
|
+
* `onAttach` with the resulting `Attachment[]`. Enforces `maxSizeMB`
|
|
8
|
+
* and renders image preview thumbnails.
|
|
9
|
+
*/
|
|
10
|
+
export declare function AttachmentDropzone({ accept, onAttach, maxSizeMB, className, }: AttachmentDropzoneProps): React.ReactElement;
|
|
11
|
+
//# sourceMappingURL=AttachmentDropzone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AttachmentDropzone.d.ts","sourceRoot":"","sources":["../../src/components/AttachmentDropzone.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAwC,MAAM,OAAO,CAAC;AAG7D,OAAO,KAAK,EAAc,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AA8B7E;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,QAAQ,EACR,SAA+B,EAC/B,SAAS,GACV,EAAE,uBAAuB,GAAG,KAAK,CAAC,YAAY,CAmL9C"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
import { useRef, useState, useCallback } from 'react';
|
|
10
|
+
import { OnboardingUIError } from '../errors';
|
|
11
|
+
import { logger } from '@bernierllc/logger';
|
|
12
|
+
const DEFAULT_MAX_SIZE_MB = 5;
|
|
13
|
+
/**
|
|
14
|
+
* Convert a `File` to a base64 data URL using `FileReader`.
|
|
15
|
+
* Browser-safe; no Node.js imports.
|
|
16
|
+
*/
|
|
17
|
+
function fileToDataUrl(file) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const reader = new FileReader();
|
|
20
|
+
reader.onload = () => {
|
|
21
|
+
if (typeof reader.result === 'string') {
|
|
22
|
+
resolve(reader.result);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
reject(new OnboardingUIError('FileReader returned unexpected result type', {
|
|
26
|
+
code: 'FILE_READ_ERROR',
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
reader.onerror = () => {
|
|
31
|
+
reject(new OnboardingUIError('FileReader error', {
|
|
32
|
+
code: 'FILE_READ_ERROR',
|
|
33
|
+
context: { fileName: file.name },
|
|
34
|
+
}));
|
|
35
|
+
};
|
|
36
|
+
reader.readAsDataURL(file);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Drag-and-drop (and click-to-browse) attachment dropzone.
|
|
41
|
+
*
|
|
42
|
+
* Converts dropped or selected files to base64 data URLs and calls
|
|
43
|
+
* `onAttach` with the resulting `Attachment[]`. Enforces `maxSizeMB`
|
|
44
|
+
* and renders image preview thumbnails.
|
|
45
|
+
*/
|
|
46
|
+
export function AttachmentDropzone({ accept, onAttach, maxSizeMB = DEFAULT_MAX_SIZE_MB, className, }) {
|
|
47
|
+
const inputRef = useRef(null);
|
|
48
|
+
const [isDragOver, setIsDragOver] = useState(false);
|
|
49
|
+
const [previews, setPreviews] = useState([]);
|
|
50
|
+
const [sizeError, setSizeError] = useState(null);
|
|
51
|
+
const processFiles = useCallback(async (files) => {
|
|
52
|
+
if (!files || files.length === 0) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
setSizeError(null);
|
|
56
|
+
const maxBytes = maxSizeMB * 1024 * 1024;
|
|
57
|
+
const valid = [];
|
|
58
|
+
for (let i = 0; i < files.length; i++) {
|
|
59
|
+
const file = files[i];
|
|
60
|
+
if (file === undefined) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (file.size > maxBytes) {
|
|
64
|
+
setSizeError(`"${file.name}" exceeds the ${maxSizeMB} MB size limit.`);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
valid.push(file);
|
|
68
|
+
}
|
|
69
|
+
if (valid.length === 0) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const attachments = await Promise.all(valid.map(async (file) => {
|
|
74
|
+
const dataUrl = await fileToDataUrl(file);
|
|
75
|
+
return {
|
|
76
|
+
name: file.name,
|
|
77
|
+
type: file.type,
|
|
78
|
+
dataUrl,
|
|
79
|
+
size: file.size,
|
|
80
|
+
};
|
|
81
|
+
}));
|
|
82
|
+
setPreviews(prev => [...prev, ...attachments]);
|
|
83
|
+
onAttach(attachments);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
if (err instanceof OnboardingUIError) {
|
|
87
|
+
logger.error('AttachmentDropzone file read failed', err);
|
|
88
|
+
}
|
|
89
|
+
else if (err instanceof Error) {
|
|
90
|
+
logger.error('AttachmentDropzone file read failed', new OnboardingUIError('File read failed', { cause: err, code: 'FILE_READ_ERROR' }));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}, [onAttach, maxSizeMB]);
|
|
94
|
+
const handleDrop = useCallback((e) => {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
setIsDragOver(false);
|
|
97
|
+
void processFiles(e.dataTransfer.files);
|
|
98
|
+
}, [processFiles]);
|
|
99
|
+
const handleDragOver = useCallback((e) => {
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
setIsDragOver(true);
|
|
102
|
+
}, []);
|
|
103
|
+
const handleDragLeave = useCallback(() => {
|
|
104
|
+
setIsDragOver(false);
|
|
105
|
+
}, []);
|
|
106
|
+
const handleInputChange = useCallback((e) => {
|
|
107
|
+
void processFiles(e.target.files);
|
|
108
|
+
}, [processFiles]);
|
|
109
|
+
const handleClick = useCallback(() => {
|
|
110
|
+
inputRef.current?.click();
|
|
111
|
+
}, []);
|
|
112
|
+
const handleKeyDown = useCallback((e) => {
|
|
113
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
114
|
+
e.preventDefault();
|
|
115
|
+
inputRef.current?.click();
|
|
116
|
+
}
|
|
117
|
+
}, []);
|
|
118
|
+
const removePreview = useCallback((index) => {
|
|
119
|
+
setPreviews(prev => prev.filter((_, i) => i !== index));
|
|
120
|
+
}, []);
|
|
121
|
+
return (_jsxs("div", { className: ['ocu-attachment-dropzone', className].filter(Boolean).join(' '), children: [_jsxs("div", { className: [
|
|
122
|
+
'ocu-attachment-dropzone__zone',
|
|
123
|
+
isDragOver ? 'ocu-attachment-dropzone__zone--drag-over' : '',
|
|
124
|
+
]
|
|
125
|
+
.filter(Boolean)
|
|
126
|
+
.join(' '), onDrop: handleDrop, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onClick: handleClick, onKeyDown: handleKeyDown, role: "button", tabIndex: 0, "aria-label": "Attach files", children: [_jsx("span", { className: "ocu-attachment-dropzone__icon", "aria-hidden": "true", children: "\uD83D\uDCCE" }), _jsx("span", { className: "ocu-attachment-dropzone__label", children: isDragOver ? 'Drop files here' : 'Attach files' }), _jsx("input", { ref: inputRef, type: "file", accept: accept, multiple: true, className: "ocu-attachment-dropzone__input", style: { display: 'none' }, onChange: handleInputChange, "aria-hidden": "true", tabIndex: -1 })] }), sizeError !== null && (_jsx("p", { className: "ocu-attachment-dropzone__error", role: "alert", children: sizeError })), previews.length > 0 && (_jsx("ul", { className: "ocu-attachment-dropzone__previews", children: previews.map((attachment, index) => (_jsxs("li", { className: "ocu-attachment-dropzone__preview-item", children: [attachment.type.startsWith('image/') && (_jsx("img", { src: attachment.dataUrl, alt: attachment.name, className: "ocu-attachment-dropzone__preview-img" })), _jsx("span", { className: "ocu-attachment-dropzone__preview-name", children: attachment.name }), _jsx("button", { type: "button", className: "ocu-attachment-dropzone__preview-remove", onClick: () => removePreview(index), "aria-label": `Remove ${attachment.name}`, children: "\u2715" })] }, `${attachment.name}-${index}`))) }))] }));
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=AttachmentDropzone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AttachmentDropzone.js","sourceRoot":"","sources":["../../src/components/AttachmentDropzone.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ChatPaneProps } from '../types/ui-types';
|
|
3
|
+
/**
|
|
4
|
+
* Left pane of the split-pane layout: renders the onboarding message
|
|
5
|
+
* list and the message input bar from @bernierllc/messaging-ui.
|
|
6
|
+
*
|
|
7
|
+
* Uses `MessageInput` for the input bar. Renders messages as a simple
|
|
8
|
+
* accessible list (not `MessageList`, which is designed for multi-user
|
|
9
|
+
* chat with reactions/replies).
|
|
10
|
+
*/
|
|
11
|
+
export declare function ChatPane({ messages, isStreaming, onSend, className, }: ChatPaneProps): React.ReactElement;
|
|
12
|
+
//# sourceMappingURL=ChatPane.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatPane.d.ts","sourceRoot":"","sources":["../../src/components/ChatPane.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAExE,OAAO,KAAK,EAAE,aAAa,EAAiC,MAAM,mBAAmB,CAAC;AAGtF;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,EACvB,QAAQ,EACR,WAAW,EACX,MAAM,EACN,SAAS,GACV,EAAE,aAAa,GAAG,KAAK,CAAC,YAAY,CAyEpC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
import { useRef, useEffect, useState, useCallback } from 'react';
|
|
10
|
+
import { MessageInput } from '@bernierllc/messaging-ui';
|
|
11
|
+
import { AttachmentDropzone } from './AttachmentDropzone';
|
|
12
|
+
/**
|
|
13
|
+
* Left pane of the split-pane layout: renders the onboarding message
|
|
14
|
+
* list and the message input bar from @bernierllc/messaging-ui.
|
|
15
|
+
*
|
|
16
|
+
* Uses `MessageInput` for the input bar. Renders messages as a simple
|
|
17
|
+
* accessible list (not `MessageList`, which is designed for multi-user
|
|
18
|
+
* chat with reactions/replies).
|
|
19
|
+
*/
|
|
20
|
+
export function ChatPane({ messages, isStreaming, onSend, className, }) {
|
|
21
|
+
const bottomRef = useRef(null);
|
|
22
|
+
const [pendingAttachments, setPendingAttachments] = useState([]);
|
|
23
|
+
// Auto-scroll to the latest message.
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
26
|
+
}, [messages]);
|
|
27
|
+
const handleSendMessage = useCallback((content, files) => {
|
|
28
|
+
if (files && files.length > 0) {
|
|
29
|
+
// Files from MessageInput are raw Files — convert via parent flow.
|
|
30
|
+
// For simplicity, send without attachments when files come via
|
|
31
|
+
// MessageInput (use AttachmentDropzone for base64 uploads).
|
|
32
|
+
onSend(content, pendingAttachments);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
onSend(content, pendingAttachments.length > 0 ? pendingAttachments : undefined);
|
|
36
|
+
}
|
|
37
|
+
setPendingAttachments([]);
|
|
38
|
+
}, [onSend, pendingAttachments]);
|
|
39
|
+
const handleTyping = useCallback(() => {
|
|
40
|
+
// Typing indicator is informational; no action required in this pane.
|
|
41
|
+
}, []);
|
|
42
|
+
const handleAttach = useCallback((attachments) => {
|
|
43
|
+
setPendingAttachments(prev => [...prev, ...attachments]);
|
|
44
|
+
}, []);
|
|
45
|
+
return (_jsxs("div", { className: ['ocu-chat-pane', className].filter(Boolean).join(' '), role: "region", "aria-label": "Onboarding chat", children: [_jsxs("div", { className: "ocu-chat-pane__messages", role: "log", "aria-live": "polite", "aria-label": "Messages", children: [messages.map(msg => (_jsx(MessageBubble, { message: msg }, msg.id))), isStreaming && (_jsxs("div", { className: "ocu-chat-pane__typing", "aria-label": "Assistant is typing", role: "status", children: [_jsx("span", { className: "ocu-chat-pane__typing-dot" }), _jsx("span", { className: "ocu-chat-pane__typing-dot" }), _jsx("span", { className: "ocu-chat-pane__typing-dot" })] })), _jsx("div", { ref: bottomRef })] }), _jsxs("div", { className: "ocu-chat-pane__input-area", children: [_jsx(AttachmentDropzone, { onAttach: handleAttach, accept: "image/*,application/pdf", className: "ocu-chat-pane__dropzone" }), pendingAttachments.length > 0 && (_jsxs("div", { className: "ocu-chat-pane__pending-count", "aria-live": "polite", children: [pendingAttachments.length, " file(s) ready to send"] })), _jsx(MessageInput, { onSendMessage: handleSendMessage, onTyping: handleTyping, placeholder: "Type a message\u2026", disabled: isStreaming, allowAttachments: false })] })] }));
|
|
46
|
+
}
|
|
47
|
+
function MessageBubble({ message }) {
|
|
48
|
+
const isUser = message.role === 'user';
|
|
49
|
+
return (_jsxs("div", { className: [
|
|
50
|
+
'ocu-chat-pane__bubble',
|
|
51
|
+
isUser ? 'ocu-chat-pane__bubble--user' : 'ocu-chat-pane__bubble--assistant',
|
|
52
|
+
].join(' '), "data-role": message.role, children: [_jsxs("span", { className: "ocu-chat-pane__bubble-role sr-only", children: [isUser ? 'You' : 'Assistant', ":"] }), _jsx("p", { className: "ocu-chat-pane__bubble-text", children: message.content }), message.attachments !== undefined && message.attachments.length > 0 && (_jsx("ul", { className: "ocu-chat-pane__bubble-attachments", children: message.attachments.map((att, i) => (_jsx("li", { className: "ocu-chat-pane__attachment", children: att.type.startsWith('image/') ? (_jsx("img", { src: att.dataUrl, alt: att.name, className: "ocu-chat-pane__attachment-img" })) : (_jsx("span", { className: "ocu-chat-pane__attachment-name", children: att.name })) }, `${att.name}-${i}`))) }))] }));
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=ChatPane.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatPane.js","sourceRoot":"","sources":["../../src/components/ChatPane.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { CompletionCardProps } from '../types/ui-types';
|
|
3
|
+
/**
|
|
4
|
+
* Post-completion summary card shown when the onboarding session finishes.
|
|
5
|
+
* Displays the onboarding name, any handoff targets, and slots for children
|
|
6
|
+
* (typically a `<RatingBar>`).
|
|
7
|
+
*/
|
|
8
|
+
export declare function CompletionCard({ onboardingName, handoffs, onHandoffClick, children, className, }: CompletionCardProps): React.ReactElement;
|
|
9
|
+
//# sourceMappingURL=CompletionCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CompletionCard.d.ts","sourceRoot":"","sources":["../../src/components/CompletionCard.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAG7D;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC7B,cAAc,EACd,QAAQ,EACR,cAAc,EACd,QAAQ,EACR,SAAS,GACV,EAAE,mBAAmB,GAAG,KAAK,CAAC,YAAY,CAsC1C"}
|