@runtypelabs/persona 3.16.0 → 3.18.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 +142 -0
- package/dist/animations/glyph-cycle.cjs +279 -0
- package/dist/animations/glyph-cycle.d.cts +5 -0
- package/dist/animations/glyph-cycle.d.ts +5 -0
- package/dist/animations/glyph-cycle.js +252 -0
- package/dist/animations/types-cwY5HaFD.d.cts +307 -0
- package/dist/animations/types-cwY5HaFD.d.ts +307 -0
- package/dist/animations/wipe.cjs +107 -0
- package/dist/animations/wipe.d.cts +5 -0
- package/dist/animations/wipe.d.ts +5 -0
- package/dist/animations/wipe.js +80 -0
- package/dist/index.cjs +49 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +504 -1
- package/dist/index.d.ts +504 -1
- package/dist/index.global.js +143 -88
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +49 -48
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +85 -0
- package/dist/testing.d.cts +39 -0
- package/dist/testing.d.ts +39 -0
- package/dist/testing.js +56 -0
- package/dist/theme-editor.cjs +2095 -207
- package/dist/theme-editor.d.cts +432 -2
- package/dist/theme-editor.d.ts +432 -2
- package/dist/theme-editor.js +2093 -207
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +14 -0
- package/dist/theme-reference.d.ts +14 -0
- package/dist/widget.css +565 -0
- package/package.json +20 -3
- package/src/animations/glyph-cycle.ts +332 -0
- package/src/animations/wipe.ts +66 -0
- package/src/client.test.ts +275 -0
- package/src/client.ts +99 -0
- package/src/components/ask-user-question-bubble.test.ts +583 -0
- package/src/components/ask-user-question-bubble.ts +924 -0
- package/src/components/composer-builder.ts +61 -10
- package/src/components/message-bubble.test.ts +181 -2
- package/src/components/message-bubble.ts +209 -14
- package/src/components/messages.ts +33 -1
- package/src/components/panel.ts +45 -5
- package/src/defaults.ts +37 -0
- package/src/index-global.ts +31 -0
- package/src/index.ts +34 -1
- package/src/plugins/types.ts +57 -0
- package/src/session.test.ts +276 -1
- package/src/session.ts +247 -3
- package/src/styles/widget.css +565 -0
- package/src/testing/index.ts +11 -0
- package/src/testing/mock-stream.test.ts +80 -0
- package/src/testing/mock-stream.ts +94 -0
- package/src/testing.ts +2 -0
- package/src/theme-editor/index.ts +4 -0
- package/src/theme-editor/preview-utils.test.ts +60 -0
- package/src/theme-editor/preview-utils.ts +129 -0
- package/src/theme-editor/sections.test.ts +19 -0
- package/src/theme-editor/sections.ts +84 -1
- package/src/types/theme.ts +15 -0
- package/src/types.ts +360 -0
- package/src/ui.ask-user-question-plugin.test.ts +649 -0
- package/src/ui.stop-button.test.ts +165 -0
- package/src/ui.ts +706 -11
- package/src/utils/message-fingerprint.ts +2 -0
- package/src/utils/morph.ts +7 -0
- package/src/utils/storage.ts +10 -2
- package/src/utils/stream-animation.test.ts +417 -0
- package/src/utils/stream-animation.ts +449 -0
- package/src/utils/theme.test.ts +36 -0
- package/src/utils/tokens.ts +23 -0
package/src/styles/widget.css
CHANGED
|
@@ -1759,6 +1759,30 @@
|
|
|
1759
1759
|
animation: persona-message-actions-fade-in 0.3s ease-out forwards;
|
|
1760
1760
|
}
|
|
1761
1761
|
|
|
1762
|
+
/* ask_user_question — collapsed answered state.
|
|
1763
|
+
* When the user picks an option, the interactive card is replaced with a
|
|
1764
|
+
* plain assistant bubble showing the question text. A short fade + slight
|
|
1765
|
+
* upward slide sells the "card resolved into history" feel.
|
|
1766
|
+
*/
|
|
1767
|
+
@keyframes persona-ask-resolved {
|
|
1768
|
+
from {
|
|
1769
|
+
opacity: 0;
|
|
1770
|
+
transform: translateY(-4px);
|
|
1771
|
+
}
|
|
1772
|
+
to {
|
|
1773
|
+
opacity: 1;
|
|
1774
|
+
transform: translateY(0);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
[data-ask-answered="true"] {
|
|
1778
|
+
animation: persona-ask-resolved 0.22s ease-out;
|
|
1779
|
+
}
|
|
1780
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1781
|
+
[data-ask-answered="true"] {
|
|
1782
|
+
animation: none;
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1762
1786
|
/* Action bar alignment */
|
|
1763
1787
|
.persona-message-actions-left {
|
|
1764
1788
|
justify-content: flex-start;
|
|
@@ -2824,3 +2848,544 @@
|
|
|
2824
2848
|
transform: translateX(0);
|
|
2825
2849
|
}
|
|
2826
2850
|
}
|
|
2851
|
+
|
|
2852
|
+
/* ============================================================
|
|
2853
|
+
Stream animations — reveal effects for assistant message text
|
|
2854
|
+
while streaming. Opt-in via `features.streamAnimation.type`.
|
|
2855
|
+
Units are staggered via `--char-index` / `--word-index`
|
|
2856
|
+
(set inline on each wrapper span). Timing is configured via
|
|
2857
|
+
`--persona-stream-step` and `--persona-stream-duration` on
|
|
2858
|
+
the `.persona-message-content` container.
|
|
2859
|
+
============================================================ */
|
|
2860
|
+
|
|
2861
|
+
/* Per-char/per-word spans need to be inline-block so `transform` works for
|
|
2862
|
+
the rise/fade animations. Each span animates from the moment it is first
|
|
2863
|
+
added to the DOM — streaming itself provides the visible stagger, so the
|
|
2864
|
+
CSS animation has no per-index delay. An index-based delay would compound
|
|
2865
|
+
with the stream's arrival cadence and leave later chars permanently hidden. */
|
|
2866
|
+
[data-persona-root] .persona-stream-char,
|
|
2867
|
+
[data-persona-root] .persona-stream-word {
|
|
2868
|
+
display: inline-block;
|
|
2869
|
+
will-change: opacity, transform, filter;
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
/* Group chars belonging to the same word so the browser treats the word as a
|
|
2873
|
+
single break unit. Without this, every inline-block char introduces a break
|
|
2874
|
+
opportunity and words get split mid-letter during streaming, then snap back
|
|
2875
|
+
when the final content replaces the wrapped spans. */
|
|
2876
|
+
[data-persona-root] .persona-stream-word-group {
|
|
2877
|
+
white-space: nowrap;
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
/* ---------- typewriter: fade per arriving char + blinking caret ---------- */
|
|
2881
|
+
@keyframes persona-stream-typewriter-in {
|
|
2882
|
+
from { opacity: 0; }
|
|
2883
|
+
to { opacity: 1; }
|
|
2884
|
+
}
|
|
2885
|
+
[data-persona-root] .persona-stream-typewriter .persona-stream-char {
|
|
2886
|
+
animation: persona-stream-typewriter-in var(--persona-stream-step, 120ms) ease-out both;
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
/* ---------- letter-rise: per-char translateY + fade ---------- */
|
|
2890
|
+
@keyframes persona-stream-letter-rise {
|
|
2891
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
2892
|
+
to { opacity: 1; transform: translateY(0); }
|
|
2893
|
+
}
|
|
2894
|
+
[data-persona-root] .persona-stream-letter-rise .persona-stream-char {
|
|
2895
|
+
animation: persona-stream-letter-rise calc(var(--persona-stream-step, 120ms) * 2)
|
|
2896
|
+
ease-out both;
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
/* ---------- word-fade: per-word blur + translateY fade-in ---------- */
|
|
2900
|
+
@keyframes persona-stream-word-fade {
|
|
2901
|
+
from { opacity: 0; filter: blur(4px); transform: translateY(3px); }
|
|
2902
|
+
to { opacity: 1; filter: blur(0); transform: translateY(0); }
|
|
2903
|
+
}
|
|
2904
|
+
[data-persona-root] .persona-stream-word-fade .persona-stream-word {
|
|
2905
|
+
animation: persona-stream-word-fade calc(var(--persona-stream-step, 120ms) * 3)
|
|
2906
|
+
ease-out both;
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
/* The following animations live in subpath plugin modules — their CSS is
|
|
2910
|
+
injected by the plugin when activated, not by the core stylesheet:
|
|
2911
|
+
- `wipe` → @runtypelabs/persona/animations/wipe
|
|
2912
|
+
- `glyph-cycle` → @runtypelabs/persona/animations/glyph-cycle */
|
|
2913
|
+
|
|
2914
|
+
/* ---------- pop-bubble: scale + opacity entrance on the bubble ---------- */
|
|
2915
|
+
@keyframes persona-stream-pop-in {
|
|
2916
|
+
0% { transform: scale(0.6); opacity: 0; }
|
|
2917
|
+
100% { transform: scale(1); opacity: 1; }
|
|
2918
|
+
}
|
|
2919
|
+
[data-persona-root] .persona-stream-pop {
|
|
2920
|
+
transform-origin: bottom left;
|
|
2921
|
+
animation: persona-stream-pop-in 400ms cubic-bezier(0.2, 0.9, 0.3, 1.4) both;
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
/* ---------- caret used by typewriter ---------- */
|
|
2925
|
+
@keyframes persona-stream-blink {
|
|
2926
|
+
0%, 50% { opacity: 1; }
|
|
2927
|
+
50.01%, 100% { opacity: 0; }
|
|
2928
|
+
}
|
|
2929
|
+
[data-persona-root] .persona-stream-caret {
|
|
2930
|
+
display: inline-block;
|
|
2931
|
+
width: 2px;
|
|
2932
|
+
height: 1em;
|
|
2933
|
+
margin-left: 1px;
|
|
2934
|
+
vertical-align: -2px;
|
|
2935
|
+
background: currentColor;
|
|
2936
|
+
animation: persona-stream-blink 1s steps(1) infinite;
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2939
|
+
/* ---------- skeleton placeholder (pre-first-token) ---------- */
|
|
2940
|
+
@keyframes persona-stream-skeleton-shimmer {
|
|
2941
|
+
0% { background-position: 200% 0; }
|
|
2942
|
+
100% { background-position: -200% 0; }
|
|
2943
|
+
}
|
|
2944
|
+
[data-persona-root] .persona-stream-skeleton {
|
|
2945
|
+
padding: 2px 0;
|
|
2946
|
+
/* The assistant bubble sizes to content. Give the skeleton an intrinsic
|
|
2947
|
+
width so the bubble expands; the bubble's own `max-width: 85%` clamps
|
|
2948
|
+
the upper bound. */
|
|
2949
|
+
width: 260px;
|
|
2950
|
+
max-width: 100%;
|
|
2951
|
+
}
|
|
2952
|
+
[data-persona-root] .persona-stream-skeleton-line {
|
|
2953
|
+
width: 100%;
|
|
2954
|
+
height: 10px;
|
|
2955
|
+
border-radius: 3px;
|
|
2956
|
+
background: linear-gradient(
|
|
2957
|
+
90deg,
|
|
2958
|
+
color-mix(in srgb, currentColor 12%, transparent) 0%,
|
|
2959
|
+
color-mix(in srgb, currentColor 22%, transparent) 50%,
|
|
2960
|
+
color-mix(in srgb, currentColor 12%, transparent) 100%
|
|
2961
|
+
);
|
|
2962
|
+
background-size: 200% 100%;
|
|
2963
|
+
animation: persona-stream-skeleton-shimmer 1.4s linear infinite;
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
/* ---------- reduced-motion: disable per-unit and container animations ---------- */
|
|
2967
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2968
|
+
[data-persona-root] .persona-stream-typewriter .persona-stream-char,
|
|
2969
|
+
[data-persona-root] .persona-stream-letter-rise .persona-stream-char,
|
|
2970
|
+
[data-persona-root] .persona-stream-word-fade .persona-stream-word,
|
|
2971
|
+
[data-persona-root] .persona-stream-pop,
|
|
2972
|
+
[data-persona-root] .persona-stream-caret,
|
|
2973
|
+
[data-persona-root] .persona-stream-skeleton-line {
|
|
2974
|
+
animation: none !important;
|
|
2975
|
+
opacity: 1 !important;
|
|
2976
|
+
filter: none !important;
|
|
2977
|
+
transform: none !important;
|
|
2978
|
+
color: inherit !important;
|
|
2979
|
+
background: none !important;
|
|
2980
|
+
-webkit-background-clip: border-box !important;
|
|
2981
|
+
background-clip: border-box !important;
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
|
|
2985
|
+
/* ========================================================================
|
|
2986
|
+
* ask_user_question — built-in answer-pill sheet
|
|
2987
|
+
* Slides in over the composer when the assistant invokes `ask_user_question`.
|
|
2988
|
+
* Overridable via `config.features.askUserQuestion.styles`.
|
|
2989
|
+
* ======================================================================== */
|
|
2990
|
+
|
|
2991
|
+
[data-persona-root] .persona-hidden {
|
|
2992
|
+
display: none !important;
|
|
2993
|
+
}
|
|
2994
|
+
|
|
2995
|
+
/* In-transcript stub — small "Awaiting…" chip. */
|
|
2996
|
+
[data-persona-root] .persona-ask-stub {
|
|
2997
|
+
padding: 0.35rem 0.7rem;
|
|
2998
|
+
border-radius: 999px;
|
|
2999
|
+
font-size: 0.8rem;
|
|
3000
|
+
color: var(--persona-muted, #6b7280);
|
|
3001
|
+
background: var(--persona-container, #f3f4f6);
|
|
3002
|
+
border: 1px dashed var(--persona-border, #e5e7eb);
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
[data-persona-root] .persona-ask-stub-label {
|
|
3006
|
+
font-weight: 500;
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
/* Sheet container — absolute inside composerOverlay. */
|
|
3010
|
+
[data-persona-root] .persona-ask-sheet {
|
|
3011
|
+
position: absolute;
|
|
3012
|
+
left: 0.75rem;
|
|
3013
|
+
right: 0.75rem;
|
|
3014
|
+
bottom: calc(100% + 0.5rem);
|
|
3015
|
+
box-sizing: border-box;
|
|
3016
|
+
padding: 0.85rem 1rem;
|
|
3017
|
+
background: var(--persona-ask-sheet-bg, var(--persona-surface, #ffffff));
|
|
3018
|
+
border: 1px solid var(--persona-ask-sheet-border, var(--persona-border, #e5e7eb));
|
|
3019
|
+
border-radius: 1rem;
|
|
3020
|
+
box-shadow: var(--persona-ask-sheet-shadow, 0 12px 28px -10px rgba(0, 0, 0, 0.15), 0 4px 10px -6px rgba(0, 0, 0, 0.08));
|
|
3021
|
+
transition: transform var(--persona-ask-sheet-duration, 180ms) ease, opacity var(--persona-ask-sheet-duration, 180ms) ease;
|
|
3022
|
+
opacity: 1;
|
|
3023
|
+
transform: translateY(0);
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
[data-persona-root] .persona-ask-sheet.persona-ask-sheet-enter {
|
|
3027
|
+
opacity: 0;
|
|
3028
|
+
transform: translateY(8px);
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
[data-persona-root] .persona-ask-sheet.persona-ask-sheet-leave {
|
|
3032
|
+
opacity: 0;
|
|
3033
|
+
transform: translateY(8px);
|
|
3034
|
+
pointer-events: none;
|
|
3035
|
+
}
|
|
3036
|
+
|
|
3037
|
+
/* Header row — question + close button */
|
|
3038
|
+
[data-persona-root] .persona-ask-sheet-header {
|
|
3039
|
+
margin-bottom: 0.55rem;
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
[data-persona-root] .persona-ask-sheet-question {
|
|
3043
|
+
font-size: 0.92rem;
|
|
3044
|
+
font-weight: 500;
|
|
3045
|
+
line-height: 1.35;
|
|
3046
|
+
color: var(--persona-text, #1f2937);
|
|
3047
|
+
min-height: 1.35em;
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
[data-persona-root] .persona-ask-question-skeleton {
|
|
3051
|
+
display: block;
|
|
3052
|
+
width: 60%;
|
|
3053
|
+
height: 0.85rem;
|
|
3054
|
+
border-radius: 0.3rem;
|
|
3055
|
+
background: linear-gradient(90deg,
|
|
3056
|
+
var(--persona-container, #f3f4f6) 0%,
|
|
3057
|
+
var(--persona-border, #e5e7eb) 50%,
|
|
3058
|
+
var(--persona-container, #f3f4f6) 100%);
|
|
3059
|
+
background-size: 200% 100%;
|
|
3060
|
+
animation: persona-ask-skeleton-shimmer 1.2s ease-in-out infinite;
|
|
3061
|
+
color: transparent;
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
[data-persona-root] .persona-ask-sheet-step-inline {
|
|
3065
|
+
flex: 0 0 auto;
|
|
3066
|
+
font-size: 0.78rem;
|
|
3067
|
+
line-height: 1;
|
|
3068
|
+
color: var(--persona-text-muted, #6b7280);
|
|
3069
|
+
font-variant-numeric: tabular-nums;
|
|
3070
|
+
letter-spacing: 0.01em;
|
|
3071
|
+
white-space: nowrap;
|
|
3072
|
+
}
|
|
3073
|
+
|
|
3074
|
+
[data-persona-root] .persona-ask-sheet-step-inline:empty {
|
|
3075
|
+
display: none;
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
/* Option list — stacked one per row by default ("rows" layout). The "pills"
|
|
3079
|
+
layout opt-in switches to horizontal wrap with compact pills; see also
|
|
3080
|
+
horizontalPillsAskPlugin example for a custom variant. */
|
|
3081
|
+
[data-persona-root] .persona-ask-pills {
|
|
3082
|
+
display: flex;
|
|
3083
|
+
flex-direction: column;
|
|
3084
|
+
flex-wrap: nowrap;
|
|
3085
|
+
row-gap: 0.4rem;
|
|
3086
|
+
column-gap: 0;
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
[data-persona-root] .persona-ask-pills--rows {
|
|
3090
|
+
flex-direction: column;
|
|
3091
|
+
flex-wrap: nowrap;
|
|
3092
|
+
gap: 0.4rem;
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
/* Pills layout opt-in — horizontal wrap, auto-width compact pills. */
|
|
3096
|
+
[data-persona-root] .persona-ask-sheet--pills .persona-ask-pills:not(.persona-ask-pills--rows) {
|
|
3097
|
+
flex-direction: row;
|
|
3098
|
+
flex-wrap: wrap;
|
|
3099
|
+
gap: 0.4rem;
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
[data-persona-root] .persona-ask-sheet--pills .persona-ask-pills:not(.persona-ask-pills--rows) .persona-ask-pill {
|
|
3103
|
+
width: auto;
|
|
3104
|
+
padding: 0.4rem 0.9rem;
|
|
3105
|
+
border-radius: var(--persona-ask-pill-radius, 9999px);
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
[data-persona-root] .persona-ask-pill {
|
|
3109
|
+
display: flex;
|
|
3110
|
+
align-items: center;
|
|
3111
|
+
justify-content: flex-start;
|
|
3112
|
+
width: 100%;
|
|
3113
|
+
text-align: left;
|
|
3114
|
+
padding: 0.65rem 0.9rem;
|
|
3115
|
+
border-radius: var(--persona-ask-pill-radius, 0.6rem);
|
|
3116
|
+
background: var(--persona-ask-pill-bg, transparent);
|
|
3117
|
+
color: var(--persona-ask-pill-fg, var(--persona-text, #1f2937));
|
|
3118
|
+
border: 1px solid var(--persona-border, #e5e7eb);
|
|
3119
|
+
font-size: 0.9rem;
|
|
3120
|
+
font-weight: 500;
|
|
3121
|
+
cursor: pointer;
|
|
3122
|
+
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
[data-persona-root] .persona-ask-pill:hover:not(:disabled):not(.persona-ask-pill-skeleton):not(.persona-ask-pill-selected):not([aria-pressed="true"]) {
|
|
3126
|
+
border-color: var(--persona-text, #1f2937);
|
|
3127
|
+
background: var(--persona-container, #f3f4f6);
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
[data-persona-root] .persona-ask-pill:active {
|
|
3131
|
+
transform: translateY(1px);
|
|
3132
|
+
}
|
|
3133
|
+
|
|
3134
|
+
[data-persona-root] .persona-ask-pill-selected,
|
|
3135
|
+
[data-persona-root] .persona-ask-pill[aria-pressed="true"] {
|
|
3136
|
+
background: var(--persona-ask-pill-bg-selected, var(--persona-accent, #0f0f0f));
|
|
3137
|
+
color: var(--persona-ask-pill-fg-selected, #fafafa);
|
|
3138
|
+
border-color: var(--persona-ask-pill-bg-selected, var(--persona-accent, #0f0f0f));
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
[data-persona-root] .persona-ask-pill:focus:not(:focus-visible) {
|
|
3142
|
+
outline: none;
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
[data-persona-root] .persona-ask-pill:focus-visible {
|
|
3146
|
+
outline: 2px solid var(--persona-accent, #0f0f0f);
|
|
3147
|
+
outline-offset: 2px;
|
|
3148
|
+
}
|
|
3149
|
+
|
|
3150
|
+
[data-persona-root] .persona-ask-pill-custom {
|
|
3151
|
+
border-style: dashed;
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
[data-persona-root] .persona-ask-pill-skeleton {
|
|
3155
|
+
min-width: 5rem;
|
|
3156
|
+
height: 2rem;
|
|
3157
|
+
padding: 0;
|
|
3158
|
+
border: 1px solid var(--persona-border, #e5e7eb);
|
|
3159
|
+
background: linear-gradient(90deg,
|
|
3160
|
+
var(--persona-container, #f3f4f6) 0%,
|
|
3161
|
+
var(--persona-border, #e5e7eb) 50%,
|
|
3162
|
+
var(--persona-container, #f3f4f6) 100%);
|
|
3163
|
+
background-size: 200% 100%;
|
|
3164
|
+
animation: persona-ask-skeleton-shimmer 1.2s ease-in-out infinite;
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
@keyframes persona-ask-skeleton-shimmer {
|
|
3168
|
+
0% { background-position: 100% 50%; }
|
|
3169
|
+
100% { background-position: -100% 50%; }
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
/* Row layout — full-width stacked rows with always-visible description
|
|
3173
|
+
and a right-edge affordance (number badge for single-select, check for
|
|
3174
|
+
multi-select). Layered on top of .persona-ask-pill base styles. */
|
|
3175
|
+
[data-persona-root] .persona-ask-row {
|
|
3176
|
+
align-items: stretch;
|
|
3177
|
+
justify-content: space-between;
|
|
3178
|
+
gap: 0.75rem;
|
|
3179
|
+
padding: 0.7rem 0.9rem;
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
[data-persona-root] .persona-ask-row-content {
|
|
3183
|
+
display: flex;
|
|
3184
|
+
flex-direction: column;
|
|
3185
|
+
gap: 0.15rem;
|
|
3186
|
+
flex: 1 1 auto;
|
|
3187
|
+
min-width: 0;
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
[data-persona-root] .persona-ask-row-label {
|
|
3191
|
+
font-size: 0.92rem;
|
|
3192
|
+
font-weight: 600;
|
|
3193
|
+
line-height: 1.25;
|
|
3194
|
+
color: inherit;
|
|
3195
|
+
white-space: normal;
|
|
3196
|
+
overflow-wrap: anywhere;
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
[data-persona-root] .persona-ask-row-description {
|
|
3200
|
+
font-size: 0.82rem;
|
|
3201
|
+
font-weight: 400;
|
|
3202
|
+
line-height: 1.35;
|
|
3203
|
+
color: var(--persona-text-muted, #6b7280);
|
|
3204
|
+
white-space: normal;
|
|
3205
|
+
overflow-wrap: anywhere;
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
[data-persona-root] .persona-ask-pill-selected .persona-ask-row-description,
|
|
3209
|
+
[data-persona-root] .persona-ask-pill[aria-pressed="true"] .persona-ask-row-description {
|
|
3210
|
+
color: var(--persona-ask-pill-fg-selected, #fafafa);
|
|
3211
|
+
opacity: 0.85;
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
[data-persona-root] .persona-ask-row-affordance {
|
|
3215
|
+
display: inline-flex;
|
|
3216
|
+
align-items: center;
|
|
3217
|
+
justify-content: center;
|
|
3218
|
+
flex: 0 0 auto;
|
|
3219
|
+
align-self: center;
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
[data-persona-root] .persona-ask-row-badge {
|
|
3223
|
+
display: inline-flex;
|
|
3224
|
+
align-items: center;
|
|
3225
|
+
justify-content: center;
|
|
3226
|
+
width: 1.5rem;
|
|
3227
|
+
height: 1.5rem;
|
|
3228
|
+
border-radius: 0.4rem;
|
|
3229
|
+
border: 1px solid var(--persona-border, #e5e7eb);
|
|
3230
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
3231
|
+
font-size: 0.78rem;
|
|
3232
|
+
font-weight: 600;
|
|
3233
|
+
color: var(--persona-text-muted, #6b7280);
|
|
3234
|
+
background: transparent;
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
[data-persona-root] .persona-ask-pill-selected .persona-ask-row-badge,
|
|
3238
|
+
[data-persona-root] .persona-ask-pill[aria-pressed="true"] .persona-ask-row-badge {
|
|
3239
|
+
border-color: var(--persona-ask-pill-fg-selected, #fafafa);
|
|
3240
|
+
color: var(--persona-ask-pill-fg-selected, #fafafa);
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
[data-persona-root] .persona-ask-row-check {
|
|
3244
|
+
display: inline-flex;
|
|
3245
|
+
align-items: center;
|
|
3246
|
+
justify-content: center;
|
|
3247
|
+
width: 1.4rem;
|
|
3248
|
+
height: 1.4rem;
|
|
3249
|
+
border-radius: 0.35rem;
|
|
3250
|
+
border: 1.5px solid var(--persona-border, #d1d5db);
|
|
3251
|
+
background: transparent;
|
|
3252
|
+
position: relative;
|
|
3253
|
+
}
|
|
3254
|
+
|
|
3255
|
+
[data-persona-root] .persona-ask-pill-selected .persona-ask-row-check,
|
|
3256
|
+
[data-persona-root] .persona-ask-pill[aria-pressed="true"] .persona-ask-row-check {
|
|
3257
|
+
background: var(--persona-ask-pill-fg-selected, #fafafa);
|
|
3258
|
+
border-color: var(--persona-ask-pill-fg-selected, #fafafa);
|
|
3259
|
+
}
|
|
3260
|
+
|
|
3261
|
+
[data-persona-root] .persona-ask-pill-selected .persona-ask-row-check::after,
|
|
3262
|
+
[data-persona-root] .persona-ask-pill[aria-pressed="true"] .persona-ask-row-check::after {
|
|
3263
|
+
content: "";
|
|
3264
|
+
width: 0.45rem;
|
|
3265
|
+
height: 0.75rem;
|
|
3266
|
+
border-right: 2px solid var(--persona-ask-pill-bg-selected, var(--persona-accent, #0f0f0f));
|
|
3267
|
+
border-bottom: 2px solid var(--persona-ask-pill-bg-selected, var(--persona-accent, #0f0f0f));
|
|
3268
|
+
transform: rotate(45deg) translate(-1px, -1px);
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
[data-persona-root] .persona-ask-row.persona-ask-pill-skeleton {
|
|
3272
|
+
height: 3rem;
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
/* Other row (rows mode) — composite row that contains the free-text input
|
|
3276
|
+
* directly. The input fills the row body; the badge sits on the right. */
|
|
3277
|
+
[data-persona-root] .persona-ask-row--other {
|
|
3278
|
+
cursor: text;
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
[data-persona-root] .persona-ask-row-input {
|
|
3282
|
+
flex: 1;
|
|
3283
|
+
min-width: 0;
|
|
3284
|
+
border: none;
|
|
3285
|
+
background: transparent;
|
|
3286
|
+
padding: 0;
|
|
3287
|
+
margin: 0;
|
|
3288
|
+
font: inherit;
|
|
3289
|
+
font-size: 0.92rem;
|
|
3290
|
+
color: inherit;
|
|
3291
|
+
outline: none;
|
|
3292
|
+
width: 100%;
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
[data-persona-root] .persona-ask-row-input::placeholder {
|
|
3296
|
+
color: var(--persona-text-muted, #6b7280);
|
|
3297
|
+
opacity: 1;
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
/* Free-text expansion row (pills layout only) */
|
|
3301
|
+
[data-persona-root] .persona-ask-free-text {
|
|
3302
|
+
width: 100%;
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
[data-persona-root] .persona-ask-free-text--rows {
|
|
3306
|
+
margin-top: 0.4rem;
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
[data-persona-root] .persona-ask-free-text-input {
|
|
3310
|
+
padding: 0.5rem 0.8rem;
|
|
3311
|
+
border: 1px solid var(--persona-border, #e5e7eb);
|
|
3312
|
+
border-radius: 0.55rem;
|
|
3313
|
+
font-size: 0.88rem;
|
|
3314
|
+
background: var(--persona-ask-input-bg, var(--persona-surface, #ffffff));
|
|
3315
|
+
color: var(--persona-text, #1f2937);
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3318
|
+
[data-persona-root] .persona-ask-free-text-input:focus {
|
|
3319
|
+
outline: 2px solid var(--persona-accent, #0f0f0f);
|
|
3320
|
+
outline-offset: 1px;
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
[data-persona-root] .persona-ask-free-text-submit,
|
|
3324
|
+
[data-persona-root] .persona-ask-multi-submit {
|
|
3325
|
+
padding: 0.5rem 1rem;
|
|
3326
|
+
border: none;
|
|
3327
|
+
border-radius: 0.55rem;
|
|
3328
|
+
background: var(--persona-accent, #0f0f0f);
|
|
3329
|
+
color: #fafafa;
|
|
3330
|
+
font-size: 0.85rem;
|
|
3331
|
+
font-weight: 600;
|
|
3332
|
+
cursor: pointer;
|
|
3333
|
+
transition: opacity 0.15s ease;
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
[data-persona-root] .persona-ask-free-text-submit:disabled,
|
|
3337
|
+
[data-persona-root] .persona-ask-multi-submit:disabled {
|
|
3338
|
+
opacity: 0.4;
|
|
3339
|
+
cursor: not-allowed;
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
/* Nav row — Back / Skip / Next-or-Submit buttons for grouped payloads. */
|
|
3343
|
+
[data-persona-root] .persona-ask-nav-back,
|
|
3344
|
+
[data-persona-root] .persona-ask-nav-skip {
|
|
3345
|
+
padding: 0.45rem 0.85rem;
|
|
3346
|
+
border: 1px solid transparent;
|
|
3347
|
+
border-radius: 0.55rem;
|
|
3348
|
+
background: transparent;
|
|
3349
|
+
color: var(--persona-text-muted, #6b7280);
|
|
3350
|
+
font-size: 0.85rem;
|
|
3351
|
+
font-weight: 500;
|
|
3352
|
+
cursor: pointer;
|
|
3353
|
+
transition: background 0.15s ease, color 0.15s ease;
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
[data-persona-root] .persona-ask-nav-back:hover:not(:disabled),
|
|
3357
|
+
[data-persona-root] .persona-ask-nav-skip:hover:not(:disabled) {
|
|
3358
|
+
background: var(--persona-container, #f3f4f6);
|
|
3359
|
+
color: var(--persona-text, #1f2937);
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3362
|
+
[data-persona-root] .persona-ask-nav-back:disabled {
|
|
3363
|
+
opacity: 0.35;
|
|
3364
|
+
cursor: not-allowed;
|
|
3365
|
+
}
|
|
3366
|
+
|
|
3367
|
+
[data-persona-root] .persona-ask-nav-next {
|
|
3368
|
+
padding: 0.5rem 1rem;
|
|
3369
|
+
border: none;
|
|
3370
|
+
border-radius: 0.55rem;
|
|
3371
|
+
background: var(--persona-accent, #0f0f0f);
|
|
3372
|
+
color: #fafafa;
|
|
3373
|
+
font-size: 0.85rem;
|
|
3374
|
+
font-weight: 600;
|
|
3375
|
+
cursor: pointer;
|
|
3376
|
+
transition: opacity 0.15s ease;
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
[data-persona-root] .persona-ask-nav-next:disabled {
|
|
3380
|
+
opacity: 0.4;
|
|
3381
|
+
cursor: not-allowed;
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3385
|
+
[data-persona-root] .persona-ask-sheet,
|
|
3386
|
+
[data-persona-root] .persona-ask-pill-skeleton,
|
|
3387
|
+
[data-persona-root] .persona-ask-question-skeleton {
|
|
3388
|
+
transition: none !important;
|
|
3389
|
+
animation: none !important;
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** @runtypelabs/persona/testing — Helpers for mocking SSE streams in demos, previews, and tests. */
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
createMockSSEStream,
|
|
5
|
+
createMockSSEResponse,
|
|
6
|
+
buildAssistantTurnFrames,
|
|
7
|
+
type MockSSEFrame,
|
|
8
|
+
type CreateMockSSEStreamOptions,
|
|
9
|
+
type MockSSEResponseOptions,
|
|
10
|
+
type AssistantTurnFramesOptions,
|
|
11
|
+
} from "./mock-stream";
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildAssistantTurnFrames,
|
|
5
|
+
createMockSSEResponse,
|
|
6
|
+
createMockSSEStream,
|
|
7
|
+
} from "./mock-stream";
|
|
8
|
+
|
|
9
|
+
async function readAll(stream: ReadableStream<Uint8Array>): Promise<string> {
|
|
10
|
+
const reader = stream.getReader();
|
|
11
|
+
const decoder = new TextDecoder();
|
|
12
|
+
let out = "";
|
|
13
|
+
for (;;) {
|
|
14
|
+
const { done, value } = await reader.read();
|
|
15
|
+
if (done) break;
|
|
16
|
+
out += decoder.decode(value);
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe("createMockSSEStream", () => {
|
|
22
|
+
it("emits bare `data:` frames by default", async () => {
|
|
23
|
+
const frames = [
|
|
24
|
+
{ type: "agent_turn_start", executionId: "e-1", turnId: "t-1" },
|
|
25
|
+
{ type: "agent_turn_delta", executionId: "e-1", turnId: "t-1", delta: "hi" },
|
|
26
|
+
{ type: "agent_turn_complete", executionId: "e-1", turnId: "t-1" },
|
|
27
|
+
];
|
|
28
|
+
const text = await readAll(createMockSSEStream(frames, { delayMs: 0 }));
|
|
29
|
+
|
|
30
|
+
expect(text).not.toContain("event:");
|
|
31
|
+
expect(text.split("\n\n").filter(Boolean)).toHaveLength(3);
|
|
32
|
+
expect(text).toContain('"type":"agent_turn_delta"');
|
|
33
|
+
expect(text).toContain('"delta":"hi"');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("emits named event frames when eventName is set", async () => {
|
|
37
|
+
const text = await readAll(
|
|
38
|
+
createMockSSEStream([{ type: "ping" }], { delayMs: 0, eventName: "message" })
|
|
39
|
+
);
|
|
40
|
+
expect(text.startsWith("event: message\n")).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("buildAssistantTurnFrames", () => {
|
|
45
|
+
it("chunks text into delta frames bracketed by start/complete", () => {
|
|
46
|
+
const frames = buildAssistantTurnFrames({
|
|
47
|
+
executionId: "exec-1",
|
|
48
|
+
turnId: "turn-1",
|
|
49
|
+
text: "abcdefghij",
|
|
50
|
+
chunkSize: 4,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
expect(frames[0]).toEqual({ type: "agent_turn_start", executionId: "exec-1", turnId: "turn-1" });
|
|
54
|
+
expect(frames[frames.length - 1]).toEqual({
|
|
55
|
+
type: "agent_turn_complete",
|
|
56
|
+
executionId: "exec-1",
|
|
57
|
+
turnId: "turn-1",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const deltas = frames.filter((f) => f.type === "agent_turn_delta");
|
|
61
|
+
expect(deltas.map((f) => f.delta)).toEqual(["abcd", "efgh", "ij"]);
|
|
62
|
+
expect(deltas.every((f) => f.executionId === "exec-1" && f.turnId === "turn-1")).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("defaults turnId and chunkSize", () => {
|
|
66
|
+
const frames = buildAssistantTurnFrames({ executionId: "exec-2", text: "hello" });
|
|
67
|
+
expect(frames[0].turnId).toBe("turn-1");
|
|
68
|
+
const deltaCount = frames.filter((f) => f.type === "agent_turn_delta").length;
|
|
69
|
+
expect(deltaCount).toBeGreaterThanOrEqual(1);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("createMockSSEResponse", () => {
|
|
74
|
+
it("wraps the stream in a text/event-stream Response", async () => {
|
|
75
|
+
const res = createMockSSEResponse([{ type: "ping" }], { delayMs: 0 });
|
|
76
|
+
expect(res.status).toBe(200);
|
|
77
|
+
expect(res.headers.get("Content-Type")).toBe("text/event-stream");
|
|
78
|
+
expect(await res.text()).toContain('"type":"ping"');
|
|
79
|
+
});
|
|
80
|
+
});
|