@runtypelabs/persona 1.40.0 → 1.41.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
@@ -568,6 +568,157 @@ type AgentWidgetMessageActionsConfig = {
568
568
  - **Copy button**: Shows a checkmark briefly after successful copy
569
569
  - **Vote buttons**: Toggle active state and are mutually exclusive (upvoting clears downvote and vice versa)
570
570
 
571
+ ### Loading & Idle Indicators
572
+
573
+ The widget displays visual indicators during different states of the conversation:
574
+
575
+ - **Loading indicator**: Shown while waiting for a response (standalone) or when an assistant message is streaming but has no content yet (inline)
576
+ - **Idle indicator**: Shown when the widget is idle (not streaming) and has at least one message - useful for showing the assistant is "waiting" for user input
577
+
578
+ #### Configuration
579
+
580
+ ```ts
581
+ const controller = initAgentWidget({
582
+ target: '#app',
583
+ config: {
584
+ apiUrl: '/api/chat/dispatch',
585
+
586
+ loadingIndicator: {
587
+ // Show/hide bubble styling around standalone indicator (default: true)
588
+ showBubble: false,
589
+
590
+ // Custom loading indicator renderer
591
+ render: ({ location, config, defaultRenderer }) => {
592
+ // location: 'standalone' (separate bubble) or 'inline' (inside message)
593
+ if (location === 'standalone') {
594
+ const el = document.createElement('div');
595
+ el.innerHTML = '<svg class="spinner">...</svg>';
596
+ el.setAttribute('data-preserve-animation', 'true');
597
+ return el;
598
+ }
599
+ // Use default 3-dot bouncing indicator for inline
600
+ return defaultRenderer();
601
+ },
602
+
603
+ // Custom idle state indicator (shown after response completes)
604
+ renderIdle: ({ lastMessage, messageCount, config }) => {
605
+ // Only show after assistant messages
606
+ if (lastMessage?.role !== 'assistant') return null;
607
+
608
+ const el = document.createElement('div');
609
+ el.textContent = 'What would you like to do next?';
610
+ el.setAttribute('data-preserve-animation', 'true');
611
+ return el;
612
+ }
613
+ }
614
+ }
615
+ });
616
+ ```
617
+
618
+ #### Indicator Locations
619
+
620
+ | Location | When Shown | Description |
621
+ |----------|------------|-------------|
622
+ | `standalone` | Waiting for stream to start | Separate bubble shown after user sends a message |
623
+ | `inline` | Streaming with empty content | Inside the assistant message bubble |
624
+ | `idle` | Not streaming, has messages | After assistant finishes responding |
625
+
626
+ #### Animation Preservation
627
+
628
+ When using custom animated indicators, add the `data-preserve-animation="true"` attribute to prevent the DOM morpher from interrupting CSS animations during updates:
629
+
630
+ ```ts
631
+ render: () => {
632
+ const el = document.createElement('div');
633
+ el.setAttribute('data-preserve-animation', 'true');
634
+ el.innerHTML = `
635
+ <style>
636
+ @keyframes spin { to { transform: rotate(360deg); } }
637
+ .spinner { animation: spin 1s linear infinite; }
638
+ </style>
639
+ <div class="spinner">⟳</div>
640
+ `;
641
+ return el;
642
+ }
643
+ ```
644
+
645
+ #### Hiding Indicators
646
+
647
+ Return `null` from any render function to hide that indicator:
648
+
649
+ ```ts
650
+ loadingIndicator: {
651
+ // Hide loading indicator entirely
652
+ render: () => null,
653
+
654
+ // Hide idle indicator (default behavior)
655
+ renderIdle: () => null
656
+ }
657
+ ```
658
+
659
+ #### Using Plugins
660
+
661
+ You can also customize indicators via plugins, which take priority over config:
662
+
663
+ ```ts
664
+ const customIndicatorPlugin = {
665
+ id: 'custom-indicators',
666
+
667
+ renderLoadingIndicator: ({ location, defaultRenderer }) => {
668
+ if (location === 'standalone') {
669
+ return createCustomSpinner();
670
+ }
671
+ return defaultRenderer();
672
+ },
673
+
674
+ renderIdleIndicator: ({ lastMessage, messageCount }) => {
675
+ if (messageCount === 0) return null;
676
+ if (lastMessage?.role !== 'assistant') return null;
677
+ return createIdleAnimation();
678
+ }
679
+ };
680
+
681
+ initAgentWidget({
682
+ target: '#app',
683
+ config: {
684
+ plugins: [customIndicatorPlugin]
685
+ }
686
+ });
687
+ ```
688
+
689
+ #### Type Definitions
690
+
691
+ ```typescript
692
+ // Loading indicator context
693
+ type LoadingIndicatorRenderContext = {
694
+ config: AgentWidgetConfig;
695
+ streaming: boolean;
696
+ location: 'inline' | 'standalone';
697
+ defaultRenderer: () => HTMLElement;
698
+ };
699
+
700
+ // Idle indicator context
701
+ type IdleIndicatorRenderContext = {
702
+ config: AgentWidgetConfig;
703
+ lastMessage: AgentWidgetMessage | undefined;
704
+ messageCount: number;
705
+ };
706
+
707
+ // Configuration
708
+ type AgentWidgetLoadingIndicatorConfig = {
709
+ showBubble?: boolean;
710
+ render?: (context: LoadingIndicatorRenderContext) => HTMLElement | null;
711
+ renderIdle?: (context: IdleIndicatorRenderContext) => HTMLElement | null;
712
+ };
713
+ ```
714
+
715
+ #### Priority Chain
716
+
717
+ Indicators are resolved in this order:
718
+ 1. **Plugin hook** (`renderLoadingIndicator` / `renderIdleIndicator`)
719
+ 2. **Config function** (`loadingIndicator.render` / `loadingIndicator.renderIdle`)
720
+ 3. **Default** (3-dot bouncing animation for loading, `null` for idle)
721
+
571
722
  ### Runtype adapter
572
723
 
573
724
  This package ships with a Runtype adapter by default. The proxy handles all flow configuration, keeping the client lightweight and flexible.