@kitn.ai/ui 0.19.0 → 0.20.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 +6 -0
- package/NOTICE +54 -0
- package/dist/components/audio-visualizer/audio-visualizer.voice-fixture.d.ts +59 -0
- package/dist/components/audio-visualizer/aurora.glsl.d.ts +114 -0
- package/dist/components/audio-visualizer/index.d.ts +155 -0
- package/dist/components/audio-visualizer/shader-canvas.d.ts +130 -0
- package/dist/components/audio-visualizer/sizes.d.ts +25 -0
- package/dist/components/audio-visualizer/variant-aurora.d.ts +89 -0
- package/dist/components/audio-visualizer/variant-bar.d.ts +46 -0
- package/dist/components/audio-visualizer/variant-custom.d.ts +73 -0
- package/dist/components/audio-visualizer/variant-grid.d.ts +22 -0
- package/dist/components/audio-visualizer/variant-radial.d.ts +13 -0
- package/dist/components/audio-visualizer/variant-wave.d.ts +11 -0
- package/dist/components/audio-visualizer/wave.glsl.d.ts +16 -0
- package/dist/create-tween-hvpmSuwW.js +7 -0
- package/dist/custom-elements.json +282 -0
- package/dist/elements/agent-card.js +1 -1
- package/dist/elements/artifact.js +1 -1
- package/dist/elements/attachments.js +1 -1
- package/dist/elements/audio-visualizer.d.ts +1 -0
- package/dist/elements/audio-visualizer.js +1 -0
- package/dist/elements/autoloader.js +1 -1
- package/dist/elements/avatar.js +1 -1
- package/dist/elements/badge.js +1 -1
- package/dist/elements/button.js +1 -1
- package/dist/elements/card.js +1 -1
- package/dist/elements/cards.js +1 -1
- package/dist/elements/chain-of-thought.js +1 -1
- package/dist/elements/chat-scope-picker.js +1 -1
- package/dist/elements/chat-workspace.js +1 -1
- package/dist/elements/chat.js +1 -1
- package/dist/elements/checkpoint.js +1 -1
- package/dist/elements/choice.js +1 -1
- package/dist/elements/chunks/{Icon-11SRdjJ4.js → Icon-Tj58ES_d.js} +1 -1
- package/dist/elements/chunks/{action-icons-CtRg17uX.js → action-icons-ns1Fnedj.js} +1 -1
- package/dist/elements/chunks/arrow-left-DQocUFvy.js +1 -0
- package/dist/elements/chunks/{attachments-CBqQRh60.js → attachments-mCB5F1Fq.js} +1 -1
- package/dist/elements/chunks/audio-visualizer-BqNy7dfm.js +1 -0
- package/dist/elements/chunks/{badge-CxklEXQm.js → badge-B4Zuajl2.js} +1 -1
- package/dist/elements/chunks/{button-BD7lUvVB.js → button-ZWNif3--.js} +1 -1
- package/dist/elements/chunks/{card-B-PRGXdj.js → card-BkHbp4BA.js} +1 -1
- package/dist/elements/chunks/{chat-thread-DIZbRIKG.js → chat-thread-CkRN6pZ7.js} +1 -1
- package/dist/elements/chunks/check-B9-gn6SS.js +1 -0
- package/dist/elements/chunks/chevron-down-BNsxc2hk.js +1 -0
- package/dist/elements/chunks/chevron-right-Hv00StBO.js +1 -0
- package/dist/elements/chunks/circle-0e3KJTDr.js +1 -0
- package/dist/elements/chunks/circle-check-BNAS0JEy.js +1 -0
- package/dist/elements/chunks/{circle-x-mtjCO_Ok.js → circle-x-B7faJFlY.js} +1 -1
- package/dist/elements/chunks/{code-block-BeWVttmU.js → code-block-BFUHn6Va.js} +1 -1
- package/dist/elements/chunks/collapsible-DM1Ckd4e.js +1 -0
- package/dist/elements/chunks/{composer-CavJww3d.js → composer-BguS0QL_.js} +2 -2
- package/dist/elements/chunks/{context-CnlsNJSJ.js → context-Cb7k4F-v.js} +1 -1
- package/dist/elements/chunks/{conversation-list-5O_KdI_I.js → conversation-list-CvoAkWhN.js} +1 -1
- package/dist/elements/chunks/create-tween-oVvAIG8r.js +7 -0
- package/dist/elements/chunks/{default-input-CB46cJJ1.js → default-input-Cqqilg9t.js} +1 -1
- package/dist/elements/chunks/define-D1din7Ld.js +1 -0
- package/dist/elements/chunks/{disclosure-BtFK3a-M.js → disclosure-CIZ5c8nO.js} +1 -1
- package/dist/elements/chunks/{download-qJv1dS3M.js → download-Dsbg3ste.js} +1 -1
- package/dist/elements/chunks/{dropdown-UhtAueYR.js → dropdown-BDd1-Zgi.js} +1 -1
- package/dist/elements/chunks/{ellipsis-B1sOOqiJ.js → ellipsis-Dw6qrss-.js} +1 -1
- package/dist/elements/chunks/{external-link-DToP_VKE.js → external-link-Vt74zAoL.js} +1 -1
- package/dist/elements/chunks/{file-text-BwUs_Pj4.js → file-text-CZrZF6_v.js} +1 -1
- package/dist/elements/chunks/{file-tree-C6RhmSnX.js → file-tree-DF4okKyo.js} +1 -1
- package/dist/elements/chunks/{folder-H_Xp0GYP.js → folder-glP5rha4.js} +1 -1
- package/dist/elements/chunks/{hover-card-Dn3yrk1l.js → hover-card-Cmm6Q53m.js} +1 -1
- package/dist/elements/chunks/{icon-BrxJQXzC.js → icon-BLb8beoi.js} +1 -1
- package/dist/elements/chunks/{info-11eT2q5H.js → info-CIsv92JK.js} +1 -1
- package/dist/elements/chunks/{input-B9oZKDfb.js → input-Dds1kFyt.js} +1 -1
- package/dist/elements/chunks/{kbd-D2fx3rWh.js → kbd-De_xbgi9.js} +1 -1
- package/dist/elements/chunks/{link-B-RNYWNw.js → link-i4r_k7z4.js} +1 -1
- package/dist/elements/chunks/{loader-BJh_aRlp.js → loader-BmQH4s3V.js} +1 -1
- package/dist/elements/chunks/{markdown-BIpjrDFv.js → markdown-Zi21WhUz.js} +7 -7
- package/dist/elements/chunks/{message-CPfqj8Gi.js → message-BqyXDN3-.js} +1 -1
- package/dist/elements/chunks/{message-feedback-8uj0aVYh.js → message-feedback-Djr3z6Yb.js} +1 -1
- package/dist/elements/chunks/message-square-vFAgbDaX.js +1 -0
- package/dist/elements/chunks/{minimize-2-C9qkiTJg.js → minimize-2--ifeCGrl.js} +1 -1
- package/dist/elements/chunks/{model-switcher-DRrMcRXH.js → model-switcher-B94YAKsM.js} +1 -1
- package/dist/elements/chunks/{overlay-C8_FUL8q.js → overlay-kD8dwRv_.js} +1 -1
- package/dist/elements/chunks/{paperclip-MA9hxLhW.js → paperclip-q21dYOSK.js} +1 -1
- package/dist/elements/chunks/{progress-bar-y5G6HDjg.js → progress-bar-BrAIIaaJ.js} +1 -1
- package/dist/elements/chunks/{prompt-suggestion-LalWvCJy.js → prompt-suggestion-Bjn2K-fR.js} +1 -1
- package/dist/elements/chunks/reasoning-DTpCGtnY.js +1 -0
- package/dist/elements/chunks/{resizable-xhYF5lCQ.js → resizable-Dp4JExql.js} +1 -1
- package/dist/elements/chunks/{rotate-cw-2ndEXjgS.js → rotate-cw-CFWWkpj2.js} +1 -1
- package/dist/elements/chunks/{scroll-area-BzOPDXrZ.js → scroll-area-Bb0kHH1j.js} +1 -1
- package/dist/elements/chunks/scroll-button-B0cH5fXf.js +1 -0
- package/dist/elements/chunks/{separator-CPvL5Xdl.js → separator-9kRgr1X9.js} +1 -1
- package/dist/elements/chunks/{settings-hmzIuNXD.js → settings-B8lmec4D.js} +1 -1
- package/dist/elements/chunks/{settings-group-BiIym17O.js → settings-group-I0hEuXiN.js} +1 -1
- package/dist/elements/chunks/{share-BbziTAzn.js → share-C3lTiYGt.js} +1 -1
- package/dist/elements/chunks/{skeleton-Bxhs1thL.js → skeleton-DSXW5Yya.js} +1 -1
- package/dist/elements/chunks/{star-BHL3Va5p.js → star-CSJtGxgv.js} +1 -1
- package/dist/elements/chunks/{store-tpnnWlxY.js → store-CHyW95TP.js} +1 -1
- package/dist/elements/chunks/{text-shimmer-1DCKivKx.js → text-shimmer-Cn9TmNpI.js} +1 -1
- package/dist/elements/chunks/{textarea-Czaowo9F.js → textarea-CUwqAX0G.js} +1 -1
- package/dist/elements/chunks/{thumbs-up-CdLqA13R.js → thumbs-up-PNVVThEF.js} +1 -1
- package/dist/elements/chunks/{toast-store-BvBquUFA.js → toast-store-DKbQn3Wn.js} +1 -1
- package/dist/elements/chunks/{tool-4P_ne8Go.js → tool-DjTRpIpl.js} +1 -1
- package/dist/elements/chunks/tooltip-CgBZCVKp.js +1 -0
- package/dist/elements/chunks/{triangle-alert-BllqxXq8.js → triangle-alert-DsHTHCPm.js} +1 -1
- package/dist/elements/chunks/{use-card-resolution-73xiddIW.js → use-card-resolution-BSu_XHjH.js} +1 -1
- package/dist/elements/chunks/variant-aurora-Dc4nQUia.js +218 -0
- package/dist/elements/chunks/variant-custom-y_3kVQhS.js +1 -0
- package/dist/elements/chunks/variant-wave-D5OR1-9Z.js +116 -0
- package/dist/elements/chunks/{video-O9rA6k7S.js → video-B7vmenoC.js} +1 -1
- package/dist/elements/chunks/x-GRUeeQFT.js +1 -0
- package/dist/elements/coachmark.js +1 -1
- package/dist/elements/code-block.js +1 -1
- package/dist/elements/command.js +1 -1
- package/dist/elements/compare.js +1 -1
- package/dist/elements/composer.js +1 -1
- package/dist/elements/confirm-card.js +1 -1
- package/dist/elements/context-meter.js +1 -1
- package/dist/elements/conversation-list.js +1 -1
- package/dist/elements/define.d.ts +11 -0
- package/dist/elements/dialog.js +1 -1
- package/dist/elements/editable-label.js +1 -1
- package/dist/elements/embed.js +1 -1
- package/dist/elements/empty.js +1 -1
- package/dist/elements/feedback-bar.js +1 -1
- package/dist/elements/file-tree.js +1 -1
- package/dist/elements/file-upload.js +1 -1
- package/dist/elements/form.js +1 -1
- package/dist/elements/hover-card.js +1 -1
- package/dist/elements/icon.js +1 -1
- package/dist/elements/image.js +1 -1
- package/dist/elements/input.js +1 -1
- package/dist/elements/kbd.js +1 -1
- package/dist/elements/link-preview.js +1 -1
- package/dist/elements/loader.js +1 -1
- package/dist/elements/markdown.js +1 -1
- package/dist/elements/menu.js +1 -1
- package/dist/elements/message-skills.js +1 -1
- package/dist/elements/message.js +1 -1
- package/dist/elements/model-switcher.js +1 -1
- package/dist/elements/nav.js +1 -1
- package/dist/elements/notice.js +1 -1
- package/dist/elements/pane-group.js +1 -1
- package/dist/elements/pane.js +1 -1
- package/dist/elements/popover.js +1 -1
- package/dist/elements/progress-bar.js +1 -1
- package/dist/elements/prompt-dock.js +1 -1
- package/dist/elements/prompt-input.js +1 -1
- package/dist/elements/prompt-suggestions.js +1 -1
- package/dist/elements/reasoning.js +1 -1
- package/dist/elements/remote.js +1 -1
- package/dist/elements/resizable.js +1 -1
- package/dist/elements/response-stream.js +1 -1
- package/dist/elements/screen.js +1 -1
- package/dist/elements/scroll-area.js +1 -1
- package/dist/elements/scroll-button.js +1 -1
- package/dist/elements/search.js +1 -1
- package/dist/elements/segmented.js +1 -1
- package/dist/elements/separator.js +1 -1
- package/dist/elements/setting-item.js +1 -1
- package/dist/elements/settings-group.js +1 -1
- package/dist/elements/skeleton.js +1 -1
- package/dist/elements/slots.d.ts +12 -0
- package/dist/elements/source.js +1 -1
- package/dist/elements/status.js +1 -1
- package/dist/elements/switch.js +1 -1
- package/dist/elements/tabs.js +1 -1
- package/dist/elements/tasks.js +1 -1
- package/dist/elements/text-shimmer.js +1 -1
- package/dist/elements/thinking-bar.js +1 -1
- package/dist/elements/thread.js +1 -1
- package/dist/elements/toast.js +1 -1
- package/dist/elements/tool.js +1 -1
- package/dist/elements/tooltip.js +1 -1
- package/dist/elements/voice-input.js +1 -1
- package/dist/elements/voice-output.js +1 -1
- package/dist/elements.d.ts +38 -0
- package/dist/index.js +18 -17
- package/dist/kai.es.js +1 -1
- package/dist/llms/llms-full.txt +40 -4
- package/dist/llms/llms.txt +3 -3
- package/dist/primitives/audio-bands.d.ts +106 -0
- package/dist/primitives/create-tween.d.ts +33 -0
- package/dist/primitives/use-audio-analysis.d.ts +149 -0
- package/dist/primitives/use-sequencer.d.ts +13 -0
- package/dist/primitives/use-voice-recorder.d.ts +1 -0
- package/dist/primitives/visualizer-sequences.d.ts +39 -0
- package/dist/react/index.d.ts +41 -0
- package/dist/react.js +159 -153
- package/dist/register-impl-DQyejdfb.js +145 -0
- package/dist/test-utils/fake-clock.d.ts +45 -0
- package/dist/variant-aurora-DVtGqVH6.js +218 -0
- package/dist/variant-custom-Dif-IV4u.js +1 -0
- package/dist/variant-wave-DbfNdvSi.js +116 -0
- package/frameworks/react/index.tsx +42 -0
- package/llms-full.txt +40 -4
- package/llms.txt +3 -3
- package/package.json +6 -3
- package/src/components/audio-visualizer/audio-visualizer.voice-fixture.ts +257 -0
- package/src/components/audio-visualizer/aurora.glsl.ts +330 -0
- package/src/components/audio-visualizer/index.tsx +442 -0
- package/src/components/audio-visualizer/shader-canvas.tsx +846 -0
- package/src/components/audio-visualizer/sizes.ts +54 -0
- package/src/components/audio-visualizer/variant-aurora.tsx +299 -0
- package/src/components/audio-visualizer/variant-bar.tsx +143 -0
- package/src/components/audio-visualizer/variant-custom.tsx +338 -0
- package/src/components/audio-visualizer/variant-grid.tsx +170 -0
- package/src/components/audio-visualizer/variant-radial.tsx +158 -0
- package/src/components/audio-visualizer/variant-wave.tsx +125 -0
- package/src/components/audio-visualizer/wave.glsl.ts +130 -0
- package/src/components/component-meta.json +24 -24
- package/src/elements/audio-visualizer.tsx +183 -0
- package/src/elements/compiled.css +1 -1
- package/src/elements/define.tsx +12 -1
- package/src/elements/element-manifest.json +4 -0
- package/src/elements/element-meta.json +170 -0
- package/src/elements/element-types.d.ts +38 -0
- package/src/elements/register-impl.ts +1 -0
- package/src/elements/slots.ts +35 -0
- package/src/primitives/audio-bands.ts +174 -0
- package/src/primitives/create-tween.ts +214 -0
- package/src/primitives/use-audio-analysis.ts +321 -0
- package/src/primitives/use-sequencer.ts +53 -0
- package/src/primitives/use-voice-recorder.ts +16 -4
- package/src/primitives/visualizer-sequences.ts +350 -0
- package/src/test-utils/fake-clock.ts +88 -0
- package/dist/elements/chunks/arrow-left-CifsGq8x.js +0 -1
- package/dist/elements/chunks/check-CUKS495w.js +0 -1
- package/dist/elements/chunks/chevron-down-AELmYT1j.js +0 -1
- package/dist/elements/chunks/chevron-right-C8g26oKh.js +0 -1
- package/dist/elements/chunks/circle-DGcI8Cft.js +0 -1
- package/dist/elements/chunks/circle-check-DuU0IcBQ.js +0 -1
- package/dist/elements/chunks/collapsible-DGjADCN6.js +0 -1
- package/dist/elements/chunks/define-DfTe4zwZ.js +0 -1
- package/dist/elements/chunks/message-square-CCSQ4M9g.js +0 -1
- package/dist/elements/chunks/reasoning-CW3SDdb4.js +0 -1
- package/dist/elements/chunks/scroll-button-B1x4cYqM.js +0 -1
- package/dist/elements/chunks/tooltip-CBeFqJKk.js +0 -1
- package/dist/elements/chunks/x-DeS3rQhx.js +0 -1
- package/dist/register-impl-CaaHJcea.js +0 -145
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure `state -> highlight set` mappers for the audio visualizer variants.
|
|
3
|
+
*
|
|
4
|
+
* Ported from livekit/components-js `packages/shadcn/hooks/agents-ui/`
|
|
5
|
+
* (Apache License 2.0). Upstream runs four separate animator hooks, each with
|
|
6
|
+
* its own requestAnimationFrame loop. We keep the sequences (verbatim) but move
|
|
7
|
+
* the timing into one shared driver, `use-sequencer.ts`.
|
|
8
|
+
*
|
|
9
|
+
* Every generator returns a NON-EMPTY array so callers can index with
|
|
10
|
+
* `sequence[tick % sequence.length]` without a guard.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type VisualizerState =
|
|
14
|
+
| 'idle'
|
|
15
|
+
| 'connecting'
|
|
16
|
+
| 'listening'
|
|
17
|
+
| 'thinking'
|
|
18
|
+
| 'speaking'
|
|
19
|
+
| 'disconnected';
|
|
20
|
+
|
|
21
|
+
export interface Coordinate {
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const KNOWN: readonly VisualizerState[] = [
|
|
27
|
+
'idle',
|
|
28
|
+
'connecting',
|
|
29
|
+
'listening',
|
|
30
|
+
'thinking',
|
|
31
|
+
'speaking',
|
|
32
|
+
// First-class since 2026-08-10 (Rob): the dead-connection look (flat wave,
|
|
33
|
+
// nothing lit) upstream renders for its 'disconnected' state. It was an
|
|
34
|
+
// alias for 'idle' until idle adopted upstream's gentle wave, which left
|
|
35
|
+
// the flat line unreachable.
|
|
36
|
+
'disconnected',
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* LiveKit's AgentState carries room-lifecycle values that mean nothing here.
|
|
41
|
+
* Accept them so markup ported from LiveKit works unchanged.
|
|
42
|
+
*/
|
|
43
|
+
const ALIASES: Record<string, VisualizerState> = {
|
|
44
|
+
// Both are dead-connection looks upstream, so 'failed' rides with
|
|
45
|
+
// 'disconnected' rather than 'idle'.
|
|
46
|
+
failed: 'disconnected',
|
|
47
|
+
initializing: 'connecting',
|
|
48
|
+
'pre-connect-buffering': 'connecting',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export function normalizeState(input: string | undefined): VisualizerState {
|
|
52
|
+
if (!input) return 'idle';
|
|
53
|
+
if ((KNOWN as readonly string[]).includes(input)) return input as VisualizerState;
|
|
54
|
+
return ALIASES[input] ?? 'idle';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------- bar
|
|
58
|
+
|
|
59
|
+
export function barSequence(state: VisualizerState, barCount: number): number[][] {
|
|
60
|
+
switch (state) {
|
|
61
|
+
case 'connecting': {
|
|
62
|
+
// A mirrored pair sweeping outward: [0, n-1], [1, n-2], ...
|
|
63
|
+
const seq: number[][] = [];
|
|
64
|
+
for (let x = 0; x < barCount; x++) seq.push([x, barCount - 1 - x]);
|
|
65
|
+
return seq.length ? seq : [[]];
|
|
66
|
+
}
|
|
67
|
+
case 'listening':
|
|
68
|
+
case 'thinking': {
|
|
69
|
+
// Blink the center bar. -1 is a deliberate no-index frame (the "off" beat).
|
|
70
|
+
return [[Math.floor(barCount / 2)], [-1]];
|
|
71
|
+
}
|
|
72
|
+
case 'speaking':
|
|
73
|
+
return [Array.from({ length: barCount }, (_, i) => i)];
|
|
74
|
+
// Dead-connection look: mirrors idle (nothing lit) for now. A dedicated
|
|
75
|
+
// arm so the pending LiveKit disconnected-state measurement can adjust
|
|
76
|
+
// it in one line.
|
|
77
|
+
case 'disconnected':
|
|
78
|
+
return [[]];
|
|
79
|
+
case 'idle':
|
|
80
|
+
default:
|
|
81
|
+
return [[]];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function barInterval(state: VisualizerState, barCount: number): number {
|
|
86
|
+
switch (state) {
|
|
87
|
+
// The whole sweep should take 2s regardless of how many bars it crosses.
|
|
88
|
+
case 'connecting':
|
|
89
|
+
return 2000 / Math.max(1, barCount);
|
|
90
|
+
case 'listening':
|
|
91
|
+
return 500;
|
|
92
|
+
case 'thinking':
|
|
93
|
+
return 150;
|
|
94
|
+
default:
|
|
95
|
+
return 1000;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------- grid
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Walk the perimeter of a ring `spread` cells out from center, clockwise.
|
|
103
|
+
*
|
|
104
|
+
* NOTE: This diverges from upstream use-agent-audio-visualizer-grid.ts in two ways:
|
|
105
|
+
* (1) We use separate centerX and centerY (upstream uses row-center for both axes).
|
|
106
|
+
* Upstream never ships non-square grids, so the bug never manifested. Our public
|
|
107
|
+
* API is square-only too now (the grid's single `count` prop, 2026-08-09), but
|
|
108
|
+
* this function keeps taking rows/columns as plain params, so the correct
|
|
109
|
+
* arbitrary-shape handling stays -- it costs nothing and guards any future caller.
|
|
110
|
+
* (2) We distinguish spread === 0 from spread === undefined (upstream treats 0 as falsy).
|
|
111
|
+
* Again, upstream never ships spread=0 explicitly; we do for API completeness.
|
|
112
|
+
*/
|
|
113
|
+
function gridRing(rows: number, columns: number, spread: number): Coordinate[] {
|
|
114
|
+
const seq: Coordinate[] = [];
|
|
115
|
+
const centerX = Math.floor(columns / 2);
|
|
116
|
+
const centerY = Math.floor(rows / 2);
|
|
117
|
+
const topLeft = { x: Math.max(0, centerX - spread), y: Math.max(0, centerY - spread) };
|
|
118
|
+
const bottomRight = {
|
|
119
|
+
x: Math.min(columns - 1, centerX + spread),
|
|
120
|
+
y: Math.min(rows - 1, centerY + spread),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
for (let x = topLeft.x; x <= bottomRight.x; x++) seq.push({ x, y: topLeft.y });
|
|
124
|
+
for (let y = topLeft.y + 1; y <= bottomRight.y; y++) seq.push({ x: bottomRight.x, y });
|
|
125
|
+
for (let x = bottomRight.x - 1; x >= topLeft.x; x--) seq.push({ x, y: bottomRight.y });
|
|
126
|
+
for (let y = bottomRight.y - 1; y > topLeft.y; y--) seq.push({ x: topLeft.x, y });
|
|
127
|
+
|
|
128
|
+
return seq;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function gridSequence(
|
|
132
|
+
state: VisualizerState,
|
|
133
|
+
rows: number,
|
|
134
|
+
columns: number,
|
|
135
|
+
spread?: number,
|
|
136
|
+
): Coordinate[] {
|
|
137
|
+
const center = { x: Math.floor(columns / 2), y: Math.floor(rows / 2) };
|
|
138
|
+
|
|
139
|
+
switch (state) {
|
|
140
|
+
case 'connecting': {
|
|
141
|
+
const maxSpread = Math.floor(Math.max(rows, columns) / 2);
|
|
142
|
+
const clamped = spread !== undefined ? Math.min(spread, maxSpread) : maxSpread;
|
|
143
|
+
const ring = gridRing(rows, columns, clamped);
|
|
144
|
+
return ring.length ? ring : [center];
|
|
145
|
+
}
|
|
146
|
+
case 'listening': {
|
|
147
|
+
// One lit frame followed by eight dark ones: a slow heartbeat.
|
|
148
|
+
const off = { x: -1, y: -1 };
|
|
149
|
+
return [center, off, off, off, off, off, off, off, off];
|
|
150
|
+
}
|
|
151
|
+
case 'thinking': {
|
|
152
|
+
// Sweep the middle row left to right, then back.
|
|
153
|
+
const y = Math.floor(rows / 2);
|
|
154
|
+
const seq: Coordinate[] = [];
|
|
155
|
+
for (let x = 0; x < columns; x++) seq.push({ x, y });
|
|
156
|
+
for (let x = columns - 1; x >= 0; x--) seq.push({ x, y });
|
|
157
|
+
return seq.length ? seq : [center];
|
|
158
|
+
}
|
|
159
|
+
// Dead-connection look: the same centre resting frame as idle
|
|
160
|
+
// (variant-grid suppresses the highlight for both, so nothing lights).
|
|
161
|
+
// A dedicated arm so the pending LiveKit measurement can adjust it in
|
|
162
|
+
// one line.
|
|
163
|
+
case 'disconnected':
|
|
164
|
+
return [center];
|
|
165
|
+
default:
|
|
166
|
+
return [center];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ---------------------------------------------------------------- radial
|
|
171
|
+
|
|
172
|
+
/** Largest divisor of `n` that is at most `max`. Always at least 1. */
|
|
173
|
+
function largestDivisorAtMost(n: number, max: number): number {
|
|
174
|
+
const gcd = (a: number, b: number): number => {
|
|
175
|
+
while (b !== 0) {
|
|
176
|
+
const t = b;
|
|
177
|
+
b = a % b;
|
|
178
|
+
a = t;
|
|
179
|
+
}
|
|
180
|
+
return a;
|
|
181
|
+
};
|
|
182
|
+
for (let i = max; i >= 1; i--) if (gcd(n, i) === i) return i;
|
|
183
|
+
return 1;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Partition the ring into interleaved groups. Lighting one group per tick reads
|
|
188
|
+
* as a rotating pattern rather than a single travelling dot. Every index appears
|
|
189
|
+
* in exactly one group.
|
|
190
|
+
*/
|
|
191
|
+
function radialGroups(barCount: number): number[][] {
|
|
192
|
+
const divisor =
|
|
193
|
+
barCount > 8
|
|
194
|
+
? barCount / largestDivisorAtMost(barCount, 4)
|
|
195
|
+
: largestDivisorAtMost(barCount, 2);
|
|
196
|
+
const safe = Math.max(1, Math.floor(divisor));
|
|
197
|
+
const perGroup = Math.floor(barCount / safe);
|
|
198
|
+
return Array.from({ length: safe }, (_, i) =>
|
|
199
|
+
Array.from({ length: perGroup }, (_, j) => j * safe + i),
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function radialSequence(state: VisualizerState, barCount: number): number[][] {
|
|
204
|
+
switch (state) {
|
|
205
|
+
case 'connecting': {
|
|
206
|
+
// Pair each bar with the one opposite it, sweeping around the circle.
|
|
207
|
+
const center = Math.floor(barCount / 2);
|
|
208
|
+
const seq: number[][] = [];
|
|
209
|
+
for (let x = 0; x < barCount; x++) seq.push([x, (x + center) % barCount]);
|
|
210
|
+
return seq.length ? seq : [[]];
|
|
211
|
+
}
|
|
212
|
+
case 'listening':
|
|
213
|
+
case 'thinking': {
|
|
214
|
+
const groups = radialGroups(barCount);
|
|
215
|
+
return groups.length ? groups : [[]];
|
|
216
|
+
}
|
|
217
|
+
case 'speaking':
|
|
218
|
+
return [Array.from({ length: barCount }, (_, i) => i)];
|
|
219
|
+
// Dead-connection look: mirrors idle (nothing lit) for now, one-line
|
|
220
|
+
// adjustable once the LiveKit measurement lands.
|
|
221
|
+
case 'disconnected':
|
|
222
|
+
return [[]];
|
|
223
|
+
case 'idle':
|
|
224
|
+
default:
|
|
225
|
+
return [[]];
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function radialInterval(state: VisualizerState): number {
|
|
230
|
+
switch (state) {
|
|
231
|
+
case 'connecting':
|
|
232
|
+
case 'listening':
|
|
233
|
+
return 500;
|
|
234
|
+
// Infinity parks the sequencer: `thinking` spins the whole container in CSS
|
|
235
|
+
// with every bar lit, so ticking would fight the animation.
|
|
236
|
+
case 'thinking':
|
|
237
|
+
return Infinity;
|
|
238
|
+
default:
|
|
239
|
+
return 1000;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ---------------------------------------------------------------- shader
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Uniform targets for the aura and custom shaders. An array intensity means
|
|
247
|
+
* "ping-pong between these two" (see create-tween). `speaking` intensity is
|
|
248
|
+
* omitted from the pulse because live volume drives it instantly instead.
|
|
249
|
+
*/
|
|
250
|
+
export function shaderTargets(
|
|
251
|
+
state: VisualizerState,
|
|
252
|
+
): { intensity: number | [number, number]; speed: number } {
|
|
253
|
+
switch (state) {
|
|
254
|
+
case 'listening':
|
|
255
|
+
return { intensity: [0.5, 0.8], speed: 2.5 };
|
|
256
|
+
case 'thinking':
|
|
257
|
+
return { intensity: [0.25, 0.5], speed: 4.0 };
|
|
258
|
+
// Same pulse as thinking, its own speed: uSpeed is the only
|
|
259
|
+
// state-correlated scalar besides intensity that reaches a custom
|
|
260
|
+
// shader's GLSL (see customUniforms in variant-custom.tsx), so the two
|
|
261
|
+
// transitional states sharing 4.0 made them indistinguishable to any
|
|
262
|
+
// consumer shader keying off the kit's uniforms -- the Custom story's
|
|
263
|
+
// per-state looks are the concrete consumer. Every state now maps to a
|
|
264
|
+
// distinct speed: idle 1, listening/speaking 2.5 (told apart by band
|
|
265
|
+
// energy), thinking 4, connecting 6.
|
|
266
|
+
case 'connecting':
|
|
267
|
+
return { intensity: [0.25, 0.5], speed: 6.0 };
|
|
268
|
+
case 'speaking':
|
|
269
|
+
return { intensity: 0.3, speed: 2.5 };
|
|
270
|
+
// Dead-connection look: idle's dim intensity but its OWN speed, so a
|
|
271
|
+
// shader keying off uSpeed can draw a dead line distinct from idle's
|
|
272
|
+
// calm ridge (see the connecting arm's comment: uSpeed is the only
|
|
273
|
+
// other state-correlated scalar reaching consumer GLSL). Still
|
|
274
|
+
// one-line adjustable once the LiveKit measurement lands.
|
|
275
|
+
case 'disconnected':
|
|
276
|
+
return { intensity: 0.3, speed: 0.5 };
|
|
277
|
+
case 'idle':
|
|
278
|
+
default:
|
|
279
|
+
return { intensity: 0.3, speed: 1 };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const WAVE_SPEED = 5;
|
|
284
|
+
const WAVE_AMPLITUDE = 0.025;
|
|
285
|
+
const WAVE_FREQUENCY = 10;
|
|
286
|
+
|
|
287
|
+
/** Uniform targets for the wave shader. Same shape, different curve. */
|
|
288
|
+
export function waveTargets(state: VisualizerState): {
|
|
289
|
+
speed: number;
|
|
290
|
+
amplitude: number;
|
|
291
|
+
frequency: number;
|
|
292
|
+
opacity: number | [number, number];
|
|
293
|
+
pulseDuration: number;
|
|
294
|
+
} {
|
|
295
|
+
switch (state) {
|
|
296
|
+
case 'listening':
|
|
297
|
+
return {
|
|
298
|
+
speed: WAVE_SPEED,
|
|
299
|
+
amplitude: WAVE_AMPLITUDE,
|
|
300
|
+
frequency: WAVE_FREQUENCY,
|
|
301
|
+
opacity: [1.0, 0.3],
|
|
302
|
+
pulseDuration: 0.75,
|
|
303
|
+
};
|
|
304
|
+
case 'thinking':
|
|
305
|
+
case 'connecting':
|
|
306
|
+
return {
|
|
307
|
+
speed: WAVE_SPEED * 4,
|
|
308
|
+
amplitude: WAVE_AMPLITUDE / 4,
|
|
309
|
+
frequency: WAVE_FREQUENCY * 4,
|
|
310
|
+
opacity: [1.0, 0.3],
|
|
311
|
+
pulseDuration: 0.4,
|
|
312
|
+
};
|
|
313
|
+
case 'speaking':
|
|
314
|
+
// Upstream's wave switch has NO 'idle' arm: idle falls through to this
|
|
315
|
+
// same speaking/default arm (speed 10, amplitude 0.025, frequency 10,
|
|
316
|
+
// opacity 1), a gentle undulation (~1.05px centreline waviness, ~1.5px/s
|
|
317
|
+
// phase drift, measured on the md tile). Upstream's explicitly FLAT
|
|
318
|
+
// state is 'disconnected' -- the dedicated arm below. We had flattened
|
|
319
|
+
// idle instead; Rob wants upstream's look (2026-08-09).
|
|
320
|
+
case 'idle':
|
|
321
|
+
return {
|
|
322
|
+
speed: WAVE_SPEED * 2,
|
|
323
|
+
amplitude: WAVE_AMPLITUDE,
|
|
324
|
+
frequency: WAVE_FREQUENCY,
|
|
325
|
+
opacity: 1.0,
|
|
326
|
+
pulseDuration: 0,
|
|
327
|
+
};
|
|
328
|
+
// Upstream's EXPLICIT flat state, measured: 'disconnected' is the one
|
|
329
|
+
// arm of their wave switch that zeroes the line. First-class here since
|
|
330
|
+
// 2026-08-10 (Rob: 'the disconnected wave needs to be that kind of flat
|
|
331
|
+
// line'). `default` keeps the same flat targets for out-of-union
|
|
332
|
+
// strings, which normalizeState prevents real callers from reaching.
|
|
333
|
+
case 'disconnected':
|
|
334
|
+
return {
|
|
335
|
+
speed: WAVE_SPEED,
|
|
336
|
+
amplitude: 0,
|
|
337
|
+
frequency: 0,
|
|
338
|
+
opacity: 1.0,
|
|
339
|
+
pulseDuration: 0,
|
|
340
|
+
};
|
|
341
|
+
default:
|
|
342
|
+
return {
|
|
343
|
+
speed: WAVE_SPEED,
|
|
344
|
+
amplitude: 0,
|
|
345
|
+
frequency: 0,
|
|
346
|
+
opacity: 1.0,
|
|
347
|
+
pulseDuration: 0,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared fake `requestAnimationFrame` + `performance.now()` clock for tests
|
|
5
|
+
* that need to drive time deterministically instead of racing real timers.
|
|
6
|
+
*
|
|
7
|
+
* requestAnimationFrame's timestamp and performance.now() share a time origin
|
|
8
|
+
* per spec, so production code can read either. Stubbing them off the same
|
|
9
|
+
* fake `now` keeps the two from silently disagreeing.
|
|
10
|
+
*
|
|
11
|
+
* The stub holds a REAL per-registration queue, matching a real browser: a
|
|
12
|
+
* single frame fires every callback that was pending BEFORE that frame, all
|
|
13
|
+
* with the SAME advanced `now`, in registration order -- so two
|
|
14
|
+
* independently-animating things under test (e.g. a `createTween`'s own step
|
|
15
|
+
* loop and `ShaderCanvas`'s separate `draw` loop, both requestAnimationFrame
|
|
16
|
+
* consumers, both mounted at once) tick together on one `advance()` call
|
|
17
|
+
* instead of the second silently stealing the first's slot.
|
|
18
|
+
*
|
|
19
|
+
* This used to be a single PENDING-callback slot, not a queue: a second
|
|
20
|
+
* consumer scheduling a new frame before `advance()` ran overwrote the
|
|
21
|
+
* first rather than queueing behind it. That was fine for anything that only
|
|
22
|
+
* ever has ONE thing pending at a time (the common case, e.g. bar/grid/radial's
|
|
23
|
+
* single sequencer tick, or a lone `createTween`), which is why switching to a
|
|
24
|
+
* queue does not change any of those. It was NOT fine for a mounted shader
|
|
25
|
+
* variant, where a tween and ShaderCanvas's `draw` are both independently
|
|
26
|
+
* pending at once: `draw` always registers last and re-arms itself every
|
|
27
|
+
* `advance()`, so it permanently "won" the single slot and the tween's step
|
|
28
|
+
* never fired -- silently defeating any test trying to prove something holds
|
|
29
|
+
* while UNIFORM VALUES genuinely change every frame (see
|
|
30
|
+
* `variant-custom.test.tsx`'s recompile-guard test for the concrete case this
|
|
31
|
+
* was fixed for).
|
|
32
|
+
*
|
|
33
|
+
* `cancelAnimationFrame` cancels by id now (each `requestAnimationFrame` call
|
|
34
|
+
* gets its own), not by clearing whatever happens to be in the single slot --
|
|
35
|
+
* required once more than one callback can be pending at a time, so a
|
|
36
|
+
* `cancelAnimationFrame(idA)` cannot accidentally drop a DIFFERENT pending
|
|
37
|
+
* callback `idB`.
|
|
38
|
+
*
|
|
39
|
+
* Call once per `describe` block (or at file scope for the whole file); it
|
|
40
|
+
* registers `beforeEach`/`afterEach` hooks scoped to wherever it is called,
|
|
41
|
+
* exactly like inlining the hooks would, and returns the driver bound to
|
|
42
|
+
* that scope's fake clock.
|
|
43
|
+
*/
|
|
44
|
+
export function installFakeClock() {
|
|
45
|
+
let now = 0;
|
|
46
|
+
let nextId = 1;
|
|
47
|
+
let pending = new Map<number, (t: number) => void>();
|
|
48
|
+
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
now = 0;
|
|
51
|
+
nextId = 1;
|
|
52
|
+
pending = new Map();
|
|
53
|
+
vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => {
|
|
54
|
+
const id = nextId++;
|
|
55
|
+
pending.set(id, cb as (t: number) => void);
|
|
56
|
+
return id;
|
|
57
|
+
});
|
|
58
|
+
vi.stubGlobal('cancelAnimationFrame', (id: number) => {
|
|
59
|
+
pending.delete(id);
|
|
60
|
+
});
|
|
61
|
+
vi.stubGlobal('performance', { now: () => now });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
afterEach(() => vi.unstubAllGlobals());
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Advance the fake clock and run every callback pending BEFORE this call,
|
|
68
|
+
* all at the new `now` -- one simulated frame. A callback that reschedules
|
|
69
|
+
* itself (the normal RAF-loop pattern) lands in a FRESH pending set for the
|
|
70
|
+
* NEXT `advance()`, not this one: snapshotting the callback list before
|
|
71
|
+
* invoking any of them is what keeps a self-rescheduling loop from looping
|
|
72
|
+
* forever inside a single `advance()` call, matching how a real browser
|
|
73
|
+
* frame never re-enters itself either.
|
|
74
|
+
*/
|
|
75
|
+
function advance(ms: number) {
|
|
76
|
+
now += ms;
|
|
77
|
+
const callbacks = Array.from(pending.values());
|
|
78
|
+
pending = new Map();
|
|
79
|
+
for (const cb of callbacks) cb(now);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Whether at least one requestAnimationFrame callback is currently pending. */
|
|
83
|
+
function isFramePending(): boolean {
|
|
84
|
+
return pending.size > 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { advance, isFramePending };
|
|
88
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{I as o}from"./Icon-11SRdjJ4.js";import{c as m,m as r}from"./define-DfTe4zwZ.js";var e=[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]],t=a=>m(o,r(a,{name:"ArrowLeft",iconNode:e})),p=t;export{p as a};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{I as m}from"./Icon-11SRdjJ4.js";import{c as o,m as r}from"./define-DfTe4zwZ.js";var c=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],e=a=>o(m,r(a,{name:"Check",iconNode:c})),f=e;export{f as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{I as r}from"./Icon-11SRdjJ4.js";import{c as a,m}from"./define-DfTe4zwZ.js";var e=[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]],n=o=>a(r,m(o,{name:"ChevronDown",iconNode:e})),p=n;export{p as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{I as o}from"./Icon-11SRdjJ4.js";import{c as a,m as r}from"./define-DfTe4zwZ.js";var t=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],e=m=>a(o,r(m,{name:"ChevronRight",iconNode:t})),h=e;export{h as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{I as c}from"./Icon-11SRdjJ4.js";import{c as m,m as o}from"./define-DfTe4zwZ.js";var a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],e=r=>m(c,o(r,{name:"Circle",iconNode:a})),i=e;export{i as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{I as c}from"./Icon-11SRdjJ4.js";import{c as r,m as a}from"./define-DfTe4zwZ.js";var e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],o=m=>r(c,a(m,{name:"CircleCheck",iconNode:e})),i=o;export{i as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as c,g as k,r as y,c as I,l as p,m as u,i as v,n as h,a as R,N as E,b as N,t as g,y as P,z as q}from"./define-DfTe4zwZ.js";import{c as C}from"./cn-CU7UAWzk.js";var z=g("<div>"),B=g("<button>"),S=g("<div><div>");const m=P(),w=()=>{const o=q(m);if(!o)throw new Error("Collapsible parts must be used within <Collapsible>");return o};function T(o){var n;const[e,l]=c(o,["open","defaultOpen","onOpenChange","disabled","controllerRef","children","class"]),[t,a]=k(e.defaultOpen??!1),s=()=>e.open!==void 0,r=()=>s()?!!e.open:t(),i=()=>!!e.disabled,f=d=>{var b;r()!==d&&(s()||a(d),(b=e.onOpenChange)==null||b.call(e,d))},x=()=>{i()||f(!r())};(n=e.controllerRef)==null||n.call(e,{open:r,setOpen:f});const O=y();return I(m.Provider,{value:{open:r,toggle:x,contentId:O,disabled:i},get children(){var d=z();return p(d,u({get class(){return e.class}},l,{get"data-expanded"(){return r()?"":void 0},get"data-closed"(){return r()?void 0:""},get"data-state"(){return r()?"open":"closed"}}),!1,!0),v(d,()=>e.children),d}})}function U(o){const n=w(),[e,l]=c(o,["children","class","as","onClick"]),t=()=>({type:"button","aria-expanded":n.open(),"aria-controls":n.contentId,disabled:n.disabled()||void 0,"data-expanded":n.open()?"":void 0,"data-closed":n.open()?void 0:"","data-state":n.open()?"open":"closed",onClick:a=>{n.disabled()||(typeof e.onClick=="function"&&e.onClick(a),n.toggle())},class:e.class,...l});return h(()=>h(()=>!!e.as)()?e.as(t()):(()=>{var a=B();return p(a,u(t),!1,!0),v(a,()=>e.children),a})())}function _(o){const n=w(),[e,l]=c(o,["children","class"]);return(()=>{var t=S(),a=t.firstChild;return p(t,u(l,{get id(){return n.contentId},get"data-expanded"(){return n.open()?"":void 0},get"data-closed"(){return n.open()?void 0:""},get class(){return C("grid transition-[grid-template-rows] duration-200 ease-out",n.open()?"grid-rows-[1fr]":"grid-rows-[0fr]")}}),!1,!0),v(a,()=>e.children),R(s=>{var r=!n.open(),i=C("overflow-hidden",e.class);return r!==s.e&&E(t,"inert",s.e=r),i!==s.t&&N(a,s.t=i),s},{e:void 0,t:void 0}),t})()}export{T as C,U as a,_ as b};
|