@runtypelabs/persona 1.38.3 → 1.40.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 CHANGED
@@ -283,9 +283,9 @@ const chat = initAgentWidget({ /* ... */ })
283
283
  window.chatController = chat
284
284
  ```
285
285
 
286
- ### Events
286
+ ### DOM Events
287
287
 
288
- The widget dispatches custom events that you can listen to for integration with your application:
288
+ The widget dispatches custom DOM events that you can listen to for integration with your application:
289
289
 
290
290
  #### `persona:clear-chat`
291
291
 
@@ -309,6 +309,178 @@ window.addEventListener("persona:clear-chat", (event) => {
309
309
 
310
310
  **Note:** The widget automatically clears the `"persona-chat-history"` localStorage key by default when chat is cleared. If you set `clearChatHistoryStorageKey` in the config, it will also clear that additional key. You can still listen to this event for additional custom behavior.
311
311
 
312
+ ### Controller Events
313
+
314
+ The widget controller exposes an event system for reacting to chat events. Use `controller.on(eventName, callback)` to subscribe and `controller.off(eventName, callback)` to unsubscribe.
315
+
316
+ #### Available Events
317
+
318
+ | Event | Payload | Description |
319
+ |-------|---------|-------------|
320
+ | `user:message` | `AgentWidgetMessage` | Emitted when a new user message is detected. Includes `viaVoice: true` if sent via voice. |
321
+ | `assistant:message` | `AgentWidgetMessage` | Emitted when an assistant message starts streaming |
322
+ | `assistant:complete` | `AgentWidgetMessage` | Emitted when an assistant message finishes streaming |
323
+ | `voice:state` | `AgentWidgetVoiceStateEvent` | Emitted when voice recognition state changes |
324
+ | `action:detected` | `AgentWidgetActionEventPayload` | Emitted when an action is parsed from an assistant message |
325
+ | `widget:opened` | `AgentWidgetStateEvent` | Emitted when the widget panel opens |
326
+ | `widget:closed` | `AgentWidgetStateEvent` | Emitted when the widget panel closes |
327
+ | `widget:state` | `AgentWidgetStateSnapshot` | Emitted on any widget state change |
328
+ | `message:feedback` | `AgentWidgetMessageFeedback` | Emitted when user provides feedback (upvote/downvote) |
329
+ | `message:copy` | `AgentWidgetMessage` | Emitted when user copies a message |
330
+
331
+ #### Event Payload Types
332
+
333
+ ```typescript
334
+ // Voice state event
335
+ type AgentWidgetVoiceStateEvent = {
336
+ active: boolean;
337
+ source: "user" | "auto" | "restore" | "system";
338
+ timestamp: number;
339
+ };
340
+
341
+ // Widget state event (for opened/closed)
342
+ type AgentWidgetStateEvent = {
343
+ open: boolean;
344
+ source: "user" | "auto" | "api" | "system";
345
+ timestamp: number;
346
+ };
347
+
348
+ // Widget state snapshot
349
+ type AgentWidgetStateSnapshot = {
350
+ open: boolean;
351
+ launcherEnabled: boolean;
352
+ voiceActive: boolean;
353
+ streaming: boolean;
354
+ };
355
+
356
+ // Action event payload
357
+ type AgentWidgetActionEventPayload = {
358
+ action: AgentWidgetParsedAction;
359
+ message: AgentWidgetMessage;
360
+ };
361
+
362
+ // Message feedback
363
+ type AgentWidgetMessageFeedback = {
364
+ type: "upvote" | "downvote";
365
+ messageId: string;
366
+ message: AgentWidgetMessage;
367
+ };
368
+ ```
369
+
370
+ #### Example: Listening to Events
371
+
372
+ ```ts
373
+ const chat = initAgentWidget({
374
+ target: 'body',
375
+ config: { apiUrl: '/api/chat/dispatch' }
376
+ });
377
+
378
+ // Listen for new user messages
379
+ chat.on('user:message', (message) => {
380
+ console.log('User sent:', message.content);
381
+ if (message.viaVoice) {
382
+ console.log('Message was sent via voice recognition');
383
+ }
384
+ });
385
+
386
+ // Listen for completed assistant responses
387
+ chat.on('assistant:complete', (message) => {
388
+ console.log('Assistant replied:', message.content);
389
+ });
390
+
391
+ // Listen for voice state changes
392
+ chat.on('voice:state', (event) => {
393
+ console.log('Voice active:', event.active, 'Source:', event.source);
394
+ });
395
+
396
+ // Listen for widget open/close
397
+ chat.on('widget:opened', (event) => {
398
+ console.log('Widget opened by:', event.source);
399
+ });
400
+
401
+ chat.on('widget:closed', (event) => {
402
+ console.log('Widget closed by:', event.source);
403
+ });
404
+
405
+ // Listen for parsed actions from assistant messages
406
+ chat.on('action:detected', ({ action, message }) => {
407
+ console.log('Action detected:', action.type, action.payload);
408
+ });
409
+ ```
410
+
411
+ #### Example: Voice Mode Persistence
412
+
413
+ The `user:message` event is useful for implementing custom voice mode persistence across page navigations:
414
+
415
+ ```ts
416
+ const chat = initAgentWidget({
417
+ target: 'body',
418
+ config: {
419
+ apiUrl: '/api/chat/dispatch',
420
+ voiceRecognition: { enabled: true }
421
+ }
422
+ });
423
+
424
+ // Track if the user is in "voice mode"
425
+ chat.on('user:message', (message) => {
426
+ localStorage.setItem('voice-mode', message.viaVoice ? 'true' : 'false');
427
+ });
428
+
429
+ // On page load, restore voice mode if the user was using voice
430
+ if (localStorage.getItem('voice-mode') === 'true') {
431
+ chat.startVoiceRecognition();
432
+ }
433
+ ```
434
+
435
+ Note: The built-in `persistState` option handles this automatically when configured:
436
+
437
+ ```ts
438
+ initAgentWidget({
439
+ target: 'body',
440
+ config: {
441
+ persistState: true, // Automatically persists open state and voice mode
442
+ voiceRecognition: { enabled: true, autoResume: 'assistant' }
443
+ }
444
+ });
445
+ ```
446
+
447
+ ### State Loaded Hook
448
+
449
+ The `onStateLoaded` hook is called after state is loaded from the storage adapter, but before the widget initializes. Use this to transform or inject messages based on external state (e.g., navigation flags, checkout returns).
450
+
451
+ ```ts
452
+ initAgentWidget({
453
+ target: 'body',
454
+ config: {
455
+ storageAdapter: createLocalStorageAdapter('my-chat'),
456
+ onStateLoaded: (state) => {
457
+ // Check for pending navigation message
458
+ const navMessage = consumeNavigationFlag();
459
+ if (navMessage) {
460
+ return {
461
+ ...state,
462
+ messages: [...(state.messages || []), {
463
+ id: `nav-${Date.now()}`,
464
+ role: 'assistant',
465
+ content: navMessage,
466
+ createdAt: new Date().toISOString()
467
+ }]
468
+ };
469
+ }
470
+ return state;
471
+ }
472
+ }
473
+ });
474
+ ```
475
+
476
+ **Use cases:**
477
+ - Inject messages after page navigation (e.g., "Here are our products!")
478
+ - Add confirmation messages after checkout/payment returns
479
+ - Transform or filter loaded messages
480
+ - Inject system messages based on external state
481
+
482
+ The hook receives the loaded state and must return the (potentially modified) state synchronously.
483
+
312
484
  ### Message Actions (Copy, Upvote, Downvote)
313
485
 
314
486
  The widget includes built-in action buttons for assistant messages that allow users to copy message content and provide feedback through upvote/downvote buttons.