@handled-ai/design-system 0.8.0 → 0.9.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.
Files changed (336) hide show
  1. package/README.md +14 -4
  2. package/dist/charts/bar-chart-component.d.ts +24 -0
  3. package/dist/charts/bar-chart-component.js +123 -0
  4. package/dist/charts/bar-chart-component.js.map +1 -0
  5. package/dist/charts/chart-tooltip.d.ts +26 -0
  6. package/dist/charts/chart-tooltip.js +69 -0
  7. package/dist/charts/chart-tooltip.js.map +1 -0
  8. package/dist/charts/chart.d.ts +64 -0
  9. package/dist/charts/chart.js +285 -0
  10. package/dist/charts/chart.js.map +1 -0
  11. package/dist/charts/donut-chart.d.ts +21 -0
  12. package/dist/charts/donut-chart.js +96 -0
  13. package/dist/charts/donut-chart.js.map +1 -0
  14. package/dist/charts/index.d.ts +11 -0
  15. package/dist/charts/index.js +10 -0
  16. package/dist/charts/index.js.map +1 -0
  17. package/dist/charts/pipeline-overview.d.ts +76 -0
  18. package/dist/charts/pipeline-overview.js +372 -0
  19. package/dist/charts/pipeline-overview.js.map +1 -0
  20. package/dist/charts/sankey-chart.d.ts +52 -0
  21. package/dist/charts/sankey-chart.js +219 -0
  22. package/dist/charts/sankey-chart.js.map +1 -0
  23. package/dist/charts/top-line-metrics.d.ts +26 -0
  24. package/dist/charts/top-line-metrics.js +224 -0
  25. package/dist/charts/top-line-metrics.js.map +1 -0
  26. package/dist/charts/trend-area-chart.d.ts +21 -0
  27. package/dist/charts/trend-area-chart.js +150 -0
  28. package/dist/charts/trend-area-chart.js.map +1 -0
  29. package/dist/charts/volume-analysis-chart.d.ts +19 -0
  30. package/dist/charts/volume-analysis-chart.js +121 -0
  31. package/dist/charts/volume-analysis-chart.js.map +1 -0
  32. package/dist/components/activity-detail.d.ts +38 -0
  33. package/dist/components/activity-detail.js +163 -0
  34. package/dist/components/activity-detail.js.map +1 -0
  35. package/dist/components/activity-log.d.ts +21 -0
  36. package/dist/components/activity-log.js +61 -0
  37. package/dist/components/activity-log.js.map +1 -0
  38. package/dist/components/agent-popover.d.ts +71 -0
  39. package/dist/components/agent-popover.js +282 -0
  40. package/dist/components/agent-popover.js.map +1 -0
  41. package/dist/components/agent-widget.d.ts +24 -0
  42. package/dist/components/agent-widget.js +117 -0
  43. package/dist/components/agent-widget.js.map +1 -0
  44. package/dist/components/avatar.d.ts +13 -0
  45. package/dist/components/avatar.js +140 -0
  46. package/dist/components/avatar.js.map +1 -0
  47. package/dist/components/badge.d.ts +12 -0
  48. package/dist/components/badge.js +75 -0
  49. package/dist/components/badge.js.map +1 -0
  50. package/dist/components/button.d.ts +13 -0
  51. package/dist/components/button.js +83 -0
  52. package/dist/components/button.js.map +1 -0
  53. package/dist/components/card.d.ts +11 -0
  54. package/dist/components/card.js +119 -0
  55. package/dist/components/card.js.map +1 -0
  56. package/dist/components/contact-list.d.ts +34 -0
  57. package/dist/components/contact-list.js +84 -0
  58. package/dist/components/contact-list.js.map +1 -0
  59. package/dist/components/dashboard-cards.d.ts +10 -0
  60. package/dist/components/dashboard-cards.js +164 -0
  61. package/dist/components/dashboard-cards.js.map +1 -0
  62. package/dist/components/data-table-display.d.ts +19 -0
  63. package/dist/components/data-table-display.js +109 -0
  64. package/dist/components/data-table-display.js.map +1 -0
  65. package/dist/components/data-table-filter.d.ts +18 -0
  66. package/dist/components/data-table-filter.js +107 -0
  67. package/dist/components/data-table-filter.js.map +1 -0
  68. package/dist/components/data-table-quick-views.d.ts +13 -0
  69. package/dist/components/data-table-quick-views.js +90 -0
  70. package/dist/components/data-table-quick-views.js.map +1 -0
  71. package/dist/components/data-table-toolbar.d.ts +18 -0
  72. package/dist/components/data-table-toolbar.js +45 -0
  73. package/dist/components/data-table-toolbar.js.map +1 -0
  74. package/dist/components/data-table.d.ts +39 -0
  75. package/dist/components/data-table.js +821 -0
  76. package/dist/components/data-table.js.map +1 -0
  77. package/dist/components/detail-view.d.ts +44 -0
  78. package/dist/components/detail-view.js +165 -0
  79. package/dist/components/detail-view.js.map +1 -0
  80. package/dist/components/dialog.d.ts +19 -0
  81. package/dist/components/dialog.js +188 -0
  82. package/dist/components/dialog.js.map +1 -0
  83. package/dist/components/dropdown-menu.d.ts +27 -0
  84. package/dist/components/dropdown-menu.js +279 -0
  85. package/dist/components/dropdown-menu.js.map +1 -0
  86. package/dist/components/entity-panel.d.ts +69 -0
  87. package/dist/components/entity-panel.js +584 -0
  88. package/dist/components/entity-panel.js.map +1 -0
  89. package/dist/components/inbox-row.d.ts +27 -0
  90. package/dist/components/inbox-row.js +139 -0
  91. package/dist/components/inbox-row.js.map +1 -0
  92. package/dist/components/inbox-toolbar.d.ts +21 -0
  93. package/dist/components/inbox-toolbar.js +203 -0
  94. package/dist/components/inbox-toolbar.js.map +1 -0
  95. package/dist/components/input.d.ts +5 -0
  96. package/dist/components/input.js +50 -0
  97. package/dist/components/input.js.map +1 -0
  98. package/dist/components/insights-filter-bar.d.ts +21 -0
  99. package/dist/components/insights-filter-bar.js +99 -0
  100. package/dist/components/insights-filter-bar.js.map +1 -0
  101. package/dist/components/item-list-display.d.ts +22 -0
  102. package/dist/components/item-list-display.js +240 -0
  103. package/dist/components/item-list-display.js.map +1 -0
  104. package/dist/components/item-list-filter.d.ts +16 -0
  105. package/dist/components/item-list-filter.js +87 -0
  106. package/dist/components/item-list-filter.js.map +1 -0
  107. package/dist/components/item-list-toolbar.d.ts +25 -0
  108. package/dist/components/item-list-toolbar.js +79 -0
  109. package/dist/components/item-list-toolbar.js.map +1 -0
  110. package/dist/components/item-list.d.ts +20 -0
  111. package/dist/components/item-list.js +702 -0
  112. package/dist/components/item-list.js.map +1 -0
  113. package/dist/components/label.d.ts +6 -0
  114. package/dist/components/label.js +55 -0
  115. package/dist/components/label.js.map +1 -0
  116. package/dist/components/message.d.ts +23 -0
  117. package/dist/components/message.js +117 -0
  118. package/dist/components/message.js.map +1 -0
  119. package/dist/components/metric-card.d.ts +25 -0
  120. package/dist/components/metric-card.js +107 -0
  121. package/dist/components/metric-card.js.map +1 -0
  122. package/dist/components/performance-metrics-table.d.ts +38 -0
  123. package/dist/components/performance-metrics-table.js +342 -0
  124. package/dist/components/performance-metrics-table.js.map +1 -0
  125. package/dist/components/preview-list.d.ts +14 -0
  126. package/dist/components/preview-list.js +83 -0
  127. package/dist/components/preview-list.js.map +1 -0
  128. package/dist/components/progress.d.ts +6 -0
  129. package/dist/components/progress.js +69 -0
  130. package/dist/components/progress.js.map +1 -0
  131. package/dist/components/quick-action-chat-area.d.ts +24 -0
  132. package/dist/components/quick-action-chat-area.js +178 -0
  133. package/dist/components/quick-action-chat-area.js.map +1 -0
  134. package/dist/components/quick-action-modal.d.ts +30 -0
  135. package/dist/components/quick-action-modal.js +288 -0
  136. package/dist/components/quick-action-modal.js.map +1 -0
  137. package/dist/components/quick-action-sidebar-nav.d.ts +51 -0
  138. package/dist/components/quick-action-sidebar-nav.js +528 -0
  139. package/dist/components/quick-action-sidebar-nav.js.map +1 -0
  140. package/dist/components/recommended-actions-section.d.ts +23 -0
  141. package/dist/components/recommended-actions-section.js +215 -0
  142. package/dist/components/recommended-actions-section.js.map +1 -0
  143. package/dist/components/report-card.d.ts +26 -0
  144. package/dist/components/report-card.js +69 -0
  145. package/dist/components/report-card.js.map +1 -0
  146. package/dist/components/score-analysis-modal.d.ts +26 -0
  147. package/dist/components/score-analysis-modal.js +141 -0
  148. package/dist/components/score-analysis-modal.js.map +1 -0
  149. package/dist/components/score-breakdown.d.ts +17 -0
  150. package/dist/components/score-breakdown.js +162 -0
  151. package/dist/components/score-breakdown.js.map +1 -0
  152. package/dist/components/score-feedback.d.ts +40 -0
  153. package/dist/components/score-feedback.js +209 -0
  154. package/dist/components/score-feedback.js.map +1 -0
  155. package/dist/components/score-ring.d.ts +14 -0
  156. package/dist/components/score-ring.js +79 -0
  157. package/dist/components/score-ring.js.map +1 -0
  158. package/dist/components/scroll-area.d.ts +7 -0
  159. package/dist/components/scroll-area.js +101 -0
  160. package/dist/components/scroll-area.js.map +1 -0
  161. package/dist/components/select.d.ts +17 -0
  162. package/dist/components/select.js +228 -0
  163. package/dist/components/select.js.map +1 -0
  164. package/dist/components/separator.d.ts +6 -0
  165. package/dist/components/separator.js +61 -0
  166. package/dist/components/separator.js.map +1 -0
  167. package/dist/components/sheet.d.ts +16 -0
  168. package/dist/components/sheet.js +168 -0
  169. package/dist/components/sheet.js.map +1 -0
  170. package/dist/components/sidebar.d.ts +73 -0
  171. package/dist/components/sidebar.js +723 -0
  172. package/dist/components/sidebar.js.map +1 -0
  173. package/dist/components/signal-feedback-inline.d.ts +51 -0
  174. package/dist/components/signal-feedback-inline.js +548 -0
  175. package/dist/components/signal-feedback-inline.js.map +1 -0
  176. package/dist/components/simple-data-table.d.ts +15 -0
  177. package/dist/components/simple-data-table.js +91 -0
  178. package/dist/components/simple-data-table.js.map +1 -0
  179. package/dist/components/skeleton.d.ts +5 -0
  180. package/dist/components/skeleton.js +44 -0
  181. package/dist/components/skeleton.js.map +1 -0
  182. package/dist/components/status-badge.d.ts +10 -0
  183. package/dist/components/status-badge.js +82 -0
  184. package/dist/components/status-badge.js.map +1 -0
  185. package/dist/components/styled-bar-list.d.ts +20 -0
  186. package/dist/components/styled-bar-list.js +59 -0
  187. package/dist/components/styled-bar-list.js.map +1 -0
  188. package/dist/components/suggested-actions.d.ts +110 -0
  189. package/dist/components/suggested-actions.js +1538 -0
  190. package/dist/components/suggested-actions.js.map +1 -0
  191. package/dist/components/table.d.ts +12 -0
  192. package/dist/components/table.js +147 -0
  193. package/dist/components/table.js.map +1 -0
  194. package/dist/components/tabs.d.ts +14 -0
  195. package/dist/components/tabs.js +129 -0
  196. package/dist/components/tabs.js.map +1 -0
  197. package/dist/components/textarea.d.ts +5 -0
  198. package/dist/components/textarea.js +47 -0
  199. package/dist/components/textarea.js.map +1 -0
  200. package/dist/components/timeline-activity.d.ts +34 -0
  201. package/dist/components/timeline-activity.js +181 -0
  202. package/dist/components/timeline-activity.js.map +1 -0
  203. package/dist/components/tooltip.d.ts +9 -0
  204. package/dist/components/tooltip.js +93 -0
  205. package/dist/components/tooltip.js.map +1 -0
  206. package/dist/components/view-mode-toggle.d.ts +16 -0
  207. package/dist/components/view-mode-toggle.js +24 -0
  208. package/dist/components/view-mode-toggle.js.map +1 -0
  209. package/dist/hooks/use-mobile.d.ts +3 -0
  210. package/dist/hooks/use-mobile.js +21 -0
  211. package/dist/hooks/use-mobile.js.map +1 -0
  212. package/dist/index.d.ts +68 -1878
  213. package/dist/index.js +69 -10918
  214. package/dist/index.js.map +1 -1
  215. package/dist/lib/icons.d.ts +18 -0
  216. package/dist/lib/icons.js +21 -0
  217. package/dist/lib/icons.js.map +1 -0
  218. package/dist/lib/utils.d.ts +5 -0
  219. package/dist/lib/utils.js +9 -0
  220. package/dist/lib/utils.js.map +1 -0
  221. package/dist/prototype/index.d.ts +20 -0
  222. package/dist/prototype/index.js +8 -0
  223. package/dist/prototype/index.js.map +1 -0
  224. package/dist/prototype/prototype-accounts-view.d.ts +22 -0
  225. package/dist/prototype/prototype-accounts-view.js +70 -0
  226. package/dist/prototype/prototype-accounts-view.js.map +1 -0
  227. package/dist/prototype/prototype-admin-view.d.ts +21 -0
  228. package/dist/prototype/prototype-admin-view.js +53 -0
  229. package/dist/prototype/prototype-admin-view.js.map +1 -0
  230. package/dist/prototype/prototype-config.d.ts +226 -0
  231. package/dist/prototype/prototype-config.js +1 -0
  232. package/dist/prototype/prototype-config.js.map +1 -0
  233. package/dist/prototype/prototype-inbox-view.d.ts +48 -0
  234. package/dist/prototype/prototype-inbox-view.js +701 -0
  235. package/dist/prototype/prototype-inbox-view.js.map +1 -0
  236. package/dist/prototype/prototype-insights-view.d.ts +23 -0
  237. package/dist/prototype/prototype-insights-view.js +335 -0
  238. package/dist/prototype/prototype-insights-view.js.map +1 -0
  239. package/dist/prototype/prototype-shell.d.ts +40 -0
  240. package/dist/prototype/prototype-shell.js +190 -0
  241. package/dist/prototype/prototype-shell.js.map +1 -0
  242. package/dist/prototype/prototype-work-queue-view.d.ts +8 -0
  243. package/dist/prototype/prototype-work-queue-view.js +17 -0
  244. package/dist/prototype/prototype-work-queue-view.js.map +1 -0
  245. package/dist/three/agent-orb.d.ts +39 -0
  246. package/dist/three/agent-orb.js +500 -0
  247. package/dist/three/agent-orb.js.map +1 -0
  248. package/dist/three/index.d.ts +2 -0
  249. package/dist/three/index.js +2 -0
  250. package/dist/three/index.js.map +1 -0
  251. package/package.json +98 -17
  252. package/src/charts/bar-chart-component.tsx +150 -0
  253. package/src/charts/chart-tooltip.tsx +86 -0
  254. package/src/charts/chart.tsx +371 -0
  255. package/src/charts/donut-chart.tsx +112 -0
  256. package/src/charts/index.ts +13 -0
  257. package/src/charts/pipeline-overview.tsx +476 -0
  258. package/src/charts/sankey-chart.tsx +290 -0
  259. package/src/charts/top-line-metrics.tsx +261 -0
  260. package/src/charts/trend-area-chart.tsx +150 -0
  261. package/src/charts/volume-analysis-chart.tsx +124 -0
  262. package/src/components/activity-detail.tsx +233 -0
  263. package/src/components/activity-log.tsx +89 -0
  264. package/src/components/agent-popover.tsx +373 -0
  265. package/src/components/agent-widget.tsx +163 -0
  266. package/src/components/avatar.tsx +109 -0
  267. package/src/components/badge.tsx +48 -0
  268. package/src/components/button.tsx +59 -0
  269. package/src/components/card.tsx +92 -0
  270. package/src/components/contact-list.tsx +121 -0
  271. package/src/components/dashboard-cards.tsx +170 -0
  272. package/src/components/data-table-display.tsx +139 -0
  273. package/src/components/data-table-filter.tsx +138 -0
  274. package/src/components/data-table-quick-views.tsx +103 -0
  275. package/src/components/data-table-toolbar.tsx +56 -0
  276. package/src/components/data-table.tsx +915 -0
  277. package/src/components/detail-view.tsx +237 -0
  278. package/src/components/dialog.tsx +158 -0
  279. package/src/components/dropdown-menu.tsx +257 -0
  280. package/src/components/entity-panel.tsx +767 -0
  281. package/src/components/inbox-row.tsx +132 -0
  282. package/src/components/inbox-toolbar.tsx +213 -0
  283. package/src/components/input.tsx +21 -0
  284. package/src/components/insights-filter-bar.tsx +132 -0
  285. package/src/components/item-list-display.tsx +278 -0
  286. package/src/components/item-list-filter.tsx +118 -0
  287. package/src/components/item-list-toolbar.tsx +97 -0
  288. package/src/components/item-list.tsx +843 -0
  289. package/src/components/label.tsx +24 -0
  290. package/src/components/message.tsx +83 -0
  291. package/src/components/metric-card.tsx +178 -0
  292. package/src/components/performance-metrics-table.tsx +442 -0
  293. package/src/components/preview-list.tsx +62 -0
  294. package/src/components/progress.tsx +31 -0
  295. package/src/components/quick-action-chat-area.tsx +156 -0
  296. package/src/components/quick-action-modal.tsx +331 -0
  297. package/src/components/quick-action-sidebar-nav.tsx +592 -0
  298. package/src/components/recommended-actions-section.tsx +258 -0
  299. package/src/components/report-card.tsx +106 -0
  300. package/src/components/score-analysis-modal.tsx +172 -0
  301. package/src/components/score-breakdown.tsx +179 -0
  302. package/src/components/score-feedback.tsx +288 -0
  303. package/src/components/score-ring.tsx +87 -0
  304. package/src/components/scroll-area.tsx +58 -0
  305. package/src/components/select.tsx +190 -0
  306. package/src/components/separator.tsx +28 -0
  307. package/src/components/sheet.tsx +143 -0
  308. package/src/components/sidebar.tsx +726 -0
  309. package/src/components/signal-feedback-inline.tsx +591 -0
  310. package/src/components/simple-data-table.tsx +124 -0
  311. package/src/components/skeleton.tsx +15 -0
  312. package/src/components/status-badge.tsx +63 -0
  313. package/src/components/styled-bar-list.tsx +70 -0
  314. package/src/components/suggested-actions.tsx +1985 -0
  315. package/src/components/table.tsx +116 -0
  316. package/src/components/tabs.tsx +91 -0
  317. package/src/components/textarea.tsx +18 -0
  318. package/src/components/timeline-activity.tsx +234 -0
  319. package/src/components/tooltip.tsx +57 -0
  320. package/src/components/view-mode-toggle.tsx +39 -0
  321. package/src/hooks/use-mobile.ts +21 -0
  322. package/src/index.ts +77 -0
  323. package/src/lib/icons.ts +18 -0
  324. package/src/lib/utils.ts +6 -0
  325. package/src/prototype/index.ts +11 -0
  326. package/src/prototype/prototype-accounts-view.tsx +112 -0
  327. package/src/prototype/prototype-admin-view.tsx +67 -0
  328. package/src/prototype/prototype-config.ts +243 -0
  329. package/src/prototype/prototype-inbox-view.tsx +810 -0
  330. package/src/prototype/prototype-insights-view.tsx +379 -0
  331. package/src/prototype/prototype-shell.tsx +219 -0
  332. package/src/prototype/prototype-work-queue-view.tsx +30 -0
  333. package/src/styles/globals.css +299 -0
  334. package/src/three/agent-orb.tsx +557 -0
  335. package/src/three/index.ts +5 -0
  336. package/src/types/r3f.d.ts +8 -0
@@ -0,0 +1,557 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { useEffect, useMemo, useRef } from "react"
5
+ import { useTexture } from "@react-three/drei"
6
+ import { Canvas, useFrame, useThree } from "@react-three/fiber"
7
+ import {
8
+ Color,
9
+ Uniform,
10
+ RepeatWrapping,
11
+ type Mesh,
12
+ type CircleGeometry,
13
+ type ShaderMaterial,
14
+ } from "three"
15
+ import { cn } from "../lib/utils"
16
+
17
+ export type AgentOrbState = null | "thinking" | "listening" | "talking"
18
+
19
+ export interface AgentOrbProps {
20
+ state?: AgentOrbState
21
+ colors?: [string, string]
22
+ volumeMode?: "auto" | "manual"
23
+ manualInput?: number
24
+ manualOutput?: number
25
+ inputVolumeRef?: React.RefObject<number>
26
+ outputVolumeRef?: React.RefObject<number>
27
+ getInputVolume?: () => number
28
+ getOutputVolume?: () => number
29
+ seed?: number
30
+ showGlow?: boolean
31
+ className?: string
32
+ }
33
+
34
+ const PERLIN_NOISE_URL =
35
+ "https://storage.googleapis.com/eleven-public-cdn/images/perlin-noise.png"
36
+
37
+ /**
38
+ * Full AgentOrb with optional CSS glow wrapper.
39
+ * Colors default to CSS `--primary` via computed style at mount time.
40
+ */
41
+ export function AgentOrb({
42
+ state = null,
43
+ colors,
44
+ volumeMode = "auto",
45
+ manualInput,
46
+ manualOutput,
47
+ inputVolumeRef,
48
+ outputVolumeRef,
49
+ getInputVolume,
50
+ getOutputVolume,
51
+ seed,
52
+ showGlow = true,
53
+ className,
54
+ }: AgentOrbProps) {
55
+ const resolvedColors = useResolvedColors(colors)
56
+ const isSpeaking = state === "talking"
57
+ const isListening = state === "listening"
58
+
59
+ return (
60
+ <div className={cn("relative h-full w-full", className)}>
61
+ {showGlow && (
62
+ <GlowLayer
63
+ isSpeaking={isSpeaking}
64
+ isListening={isListening}
65
+ />
66
+ )}
67
+ <div
68
+ className={cn(
69
+ "relative z-10 w-full h-full transition-transform duration-700 ease-out",
70
+ isListening ? "scale-90" : "scale-100",
71
+ )}
72
+ >
73
+ <AgentOrbCanvas
74
+ colors={resolvedColors}
75
+ seed={seed}
76
+ agentState={state}
77
+ volumeMode={volumeMode}
78
+ manualInput={manualInput}
79
+ manualOutput={manualOutput}
80
+ inputVolumeRef={inputVolumeRef}
81
+ outputVolumeRef={outputVolumeRef}
82
+ getInputVolume={getInputVolume}
83
+ getOutputVolume={getOutputVolume}
84
+ className="w-full h-full"
85
+ />
86
+ </div>
87
+ </div>
88
+ )
89
+ }
90
+
91
+ function useResolvedColors(colors?: [string, string]): [string, string] {
92
+ const ref = useRef<[string, string]>(colors ?? ["#000000", "#000000"])
93
+
94
+ useEffect(() => {
95
+ if (colors) {
96
+ ref.current = colors
97
+ return
98
+ }
99
+ if (typeof document === "undefined") return
100
+ const style = getComputedStyle(document.documentElement)
101
+ const primary = style.getPropertyValue("--primary").trim() || "#000000"
102
+ ref.current = [primary, primary]
103
+ }, [colors])
104
+
105
+ return ref.current
106
+ }
107
+
108
+ /* ----- Glow Layer (CSS animations) ----- */
109
+
110
+ function GlowLayer({ isSpeaking, isListening }: { isSpeaking: boolean; isListening: boolean }) {
111
+ return (
112
+ <div
113
+ className={cn(
114
+ "absolute inset-0 flex items-center justify-center transition-all duration-700 ease-out",
115
+ isListening ? "scale-90" : "scale-100",
116
+ )}
117
+ >
118
+ {isListening && (
119
+ <>
120
+ <div
121
+ className="absolute inset-0 rounded-full animate-[spin_8s_linear_infinite]"
122
+ style={{
123
+ background:
124
+ "conic-gradient(from 0deg, color-mix(in srgb, var(--primary) 30%, transparent) 0deg, transparent 60deg, color-mix(in srgb, var(--primary) 30%, transparent) 120deg, transparent 180deg, color-mix(in srgb, var(--primary) 30%, transparent) 240deg, transparent 300deg)",
125
+ filter: "blur(20px)",
126
+ }}
127
+ />
128
+ <div
129
+ className="absolute inset-0 rounded-full animate-[spin_12s_linear_infinite_reverse]"
130
+ style={{
131
+ background:
132
+ "conic-gradient(from 45deg, transparent 0deg, color-mix(in srgb, var(--primary) 20%, transparent) 90deg, transparent 180deg, color-mix(in srgb, var(--primary) 20%, transparent) 270deg)",
133
+ filter: "blur(25px)",
134
+ }}
135
+ />
136
+ </>
137
+ )}
138
+
139
+ {isSpeaking && (
140
+ <>
141
+ <div
142
+ className="absolute inset-0 rounded-full animate-[spin_6s_linear_infinite]"
143
+ style={{
144
+ background:
145
+ "conic-gradient(from 0deg, color-mix(in srgb, var(--primary) 40%, transparent) 0deg, transparent 45deg, color-mix(in srgb, var(--primary) 40%, transparent) 90deg, transparent 135deg, color-mix(in srgb, var(--primary) 40%, transparent) 180deg, transparent 225deg, color-mix(in srgb, var(--primary) 40%, transparent) 270deg, transparent 315deg)",
146
+ filter: "blur(30px)",
147
+ }}
148
+ />
149
+ <div
150
+ className="absolute inset-0 rounded-full animate-pulse"
151
+ style={{
152
+ animationDuration: "2s",
153
+ background:
154
+ "radial-gradient(circle, color-mix(in srgb, var(--primary) 30%, transparent) 0%, transparent 70%)",
155
+ filter: "blur(20px)",
156
+ }}
157
+ />
158
+ </>
159
+ )}
160
+
161
+ {!isSpeaking && !isListening && (
162
+ <div
163
+ className="absolute inset-0 rounded-full"
164
+ style={{
165
+ background:
166
+ "radial-gradient(circle, color-mix(in srgb, var(--primary) 15%, transparent) 0%, transparent 70%)",
167
+ filter: "blur(25px)",
168
+ }}
169
+ />
170
+ )}
171
+ </div>
172
+ )
173
+ }
174
+
175
+ /* ----- Raw Three.js Canvas (exported for advanced use) ----- */
176
+
177
+ interface AgentOrbCanvasProps {
178
+ colors?: [string, string]
179
+ seed?: number
180
+ agentState?: AgentOrbState
181
+ volumeMode?: "auto" | "manual"
182
+ manualInput?: number
183
+ manualOutput?: number
184
+ inputVolumeRef?: React.RefObject<number>
185
+ outputVolumeRef?: React.RefObject<number>
186
+ getInputVolume?: () => number
187
+ getOutputVolume?: () => number
188
+ className?: string
189
+ resizeDebounce?: number
190
+ }
191
+
192
+ export function AgentOrbCanvas({
193
+ colors = ["#000000", "#000000"],
194
+ seed,
195
+ agentState = null,
196
+ volumeMode = "auto",
197
+ manualInput,
198
+ manualOutput,
199
+ inputVolumeRef,
200
+ outputVolumeRef,
201
+ getInputVolume,
202
+ getOutputVolume,
203
+ className,
204
+ resizeDebounce = 100,
205
+ }: AgentOrbCanvasProps) {
206
+ return (
207
+ <div className={className ?? "relative h-full w-full"}>
208
+ <Canvas
209
+ resize={{ debounce: resizeDebounce }}
210
+ gl={{ alpha: true, antialias: true, premultipliedAlpha: true }}
211
+ >
212
+ <OrbScene
213
+ colors={colors}
214
+ seed={seed}
215
+ agentState={agentState}
216
+ volumeMode={volumeMode}
217
+ manualInput={manualInput}
218
+ manualOutput={manualOutput}
219
+ inputVolumeRef={inputVolumeRef}
220
+ outputVolumeRef={outputVolumeRef}
221
+ getInputVolume={getInputVolume}
222
+ getOutputVolume={getOutputVolume}
223
+ />
224
+ </Canvas>
225
+ </div>
226
+ )
227
+ }
228
+
229
+ /* ----- Internal Scene ----- */
230
+
231
+ function OrbScene({
232
+ colors,
233
+ seed,
234
+ agentState,
235
+ volumeMode,
236
+ manualInput,
237
+ manualOutput,
238
+ inputVolumeRef,
239
+ outputVolumeRef,
240
+ getInputVolume,
241
+ getOutputVolume,
242
+ }: {
243
+ colors: [string, string]
244
+ seed?: number
245
+ agentState: AgentOrbState
246
+ volumeMode: "auto" | "manual"
247
+ manualInput?: number
248
+ manualOutput?: number
249
+ inputVolumeRef?: React.RefObject<number>
250
+ outputVolumeRef?: React.RefObject<number>
251
+ getInputVolume?: () => number
252
+ getOutputVolume?: () => number
253
+ }) {
254
+ const { gl } = useThree()
255
+ const circleRef = useRef<Mesh<CircleGeometry, ShaderMaterial>>(null)
256
+ const initialColorsRef = useRef<[string, string]>(colors)
257
+ const targetColor1Ref = useRef(new Color(colors[0]))
258
+ const targetColor2Ref = useRef(new Color(colors[1]))
259
+ const animSpeedRef = useRef(0.1)
260
+ const perlinNoiseTexture = useTexture(PERLIN_NOISE_URL)
261
+
262
+ const agentRef = useRef<AgentOrbState>(agentState)
263
+ const modeRef = useRef<"auto" | "manual">(volumeMode)
264
+ const curInRef = useRef(0)
265
+ const curOutRef = useRef(0)
266
+
267
+ useEffect(() => { agentRef.current = agentState }, [agentState])
268
+ useEffect(() => { modeRef.current = volumeMode }, [volumeMode])
269
+ useEffect(() => {
270
+ targetColor1Ref.current = new Color(colors[0])
271
+ targetColor2Ref.current = new Color(colors[1])
272
+ }, [colors])
273
+
274
+ const random = useMemo(
275
+ () => splitmix32(seed ?? Math.floor(Math.random() * 2 ** 32)),
276
+ [seed],
277
+ )
278
+ const offsets = useMemo(
279
+ () => new Float32Array(Array.from({ length: 7 }, () => random() * Math.PI * 2)),
280
+ [random],
281
+ )
282
+
283
+ useEffect(() => {
284
+ const apply = () => {
285
+ if (!circleRef.current) return
286
+ const isDark = document.documentElement.classList.contains("dark")
287
+ circleRef.current.material.uniforms.uInverted.value = isDark ? 1 : 0
288
+ }
289
+ apply()
290
+ const observer = new MutationObserver(apply)
291
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] })
292
+ return () => observer.disconnect()
293
+ }, [])
294
+
295
+ useFrame((_, delta: number) => {
296
+ const mat = circleRef.current?.material
297
+ if (!mat) return
298
+ const u = mat.uniforms
299
+ u.uTime.value += delta * 0.5
300
+
301
+ if (u.uOpacity.value < 1) {
302
+ u.uOpacity.value = Math.min(1, u.uOpacity.value + delta * 2)
303
+ }
304
+
305
+ let targetIn = 0
306
+ let targetOut = 0.3
307
+ if (modeRef.current === "manual") {
308
+ targetIn = clamp01(manualInput ?? inputVolumeRef?.current ?? getInputVolume?.() ?? 0)
309
+ targetOut = clamp01(manualOutput ?? outputVolumeRef?.current ?? getOutputVolume?.() ?? 0)
310
+ } else {
311
+ const t = u.uTime.value * 2
312
+ if (agentRef.current === null) {
313
+ targetIn = 0; targetOut = 0.3
314
+ } else if (agentRef.current === "listening") {
315
+ targetIn = clamp01(0.55 + Math.sin(t * 3.2) * 0.35); targetOut = 0.45
316
+ } else if (agentRef.current === "talking") {
317
+ targetIn = clamp01(0.65 + Math.sin(t * 4.8) * 0.22)
318
+ targetOut = clamp01(0.75 + Math.sin(t * 3.6) * 0.22)
319
+ } else {
320
+ const base = 0.38 + 0.07 * Math.sin(t * 0.7)
321
+ const wander = 0.05 * Math.sin(t * 2.1) * Math.sin(t * 0.37 + 1.2)
322
+ targetIn = clamp01(base + wander)
323
+ targetOut = clamp01(0.48 + 0.12 * Math.sin(t * 1.05 + 0.6))
324
+ }
325
+ }
326
+
327
+ curInRef.current += (targetIn - curInRef.current) * 0.2
328
+ curOutRef.current += (targetOut - curOutRef.current) * 0.2
329
+
330
+ const targetSpeed = 0.1 + (1 - Math.pow(curOutRef.current - 1, 2)) * 0.9
331
+ animSpeedRef.current += (targetSpeed - animSpeedRef.current) * 0.12
332
+
333
+ u.uAnimation.value += delta * animSpeedRef.current
334
+ u.uInputVolume.value = curInRef.current
335
+ u.uOutputVolume.value = curOutRef.current
336
+ u.uColor1.value.lerp(targetColor1Ref.current, 0.08)
337
+ u.uColor2.value.lerp(targetColor2Ref.current, 0.08)
338
+ })
339
+
340
+ useEffect(() => {
341
+ const canvas = gl.domElement
342
+ const onContextLost = (event: Event) => {
343
+ event.preventDefault()
344
+ setTimeout(() => { gl.forceContextRestore() }, 1)
345
+ }
346
+ canvas.addEventListener("webglcontextlost", onContextLost, false)
347
+ return () => canvas.removeEventListener("webglcontextlost", onContextLost, false)
348
+ }, [gl])
349
+
350
+ const uniforms = useMemo(() => {
351
+ perlinNoiseTexture.wrapS = RepeatWrapping
352
+ perlinNoiseTexture.wrapT = RepeatWrapping
353
+ const isDark =
354
+ typeof document !== "undefined" && document.documentElement.classList.contains("dark")
355
+ return {
356
+ uColor1: new Uniform(new Color(initialColorsRef.current[0])),
357
+ uColor2: new Uniform(new Color(initialColorsRef.current[1])),
358
+ uOffsets: { value: offsets },
359
+ uPerlinTexture: new Uniform(perlinNoiseTexture),
360
+ uTime: new Uniform(0),
361
+ uAnimation: new Uniform(0.1),
362
+ uInverted: new Uniform(isDark ? 1 : 0),
363
+ uInputVolume: new Uniform(0),
364
+ uOutputVolume: new Uniform(0),
365
+ uOpacity: new Uniform(0),
366
+ }
367
+ }, [perlinNoiseTexture, offsets])
368
+
369
+ return (
370
+ <mesh ref={circleRef}>
371
+ <circleGeometry args={[3.5, 64]} />
372
+ <shaderMaterial
373
+ uniforms={uniforms}
374
+ fragmentShader={fragmentShader}
375
+ vertexShader={vertexShader}
376
+ transparent
377
+ />
378
+ </mesh>
379
+ )
380
+ }
381
+
382
+ /* ----- Utilities ----- */
383
+
384
+ function splitmix32(a: number) {
385
+ return function () {
386
+ a |= 0
387
+ a = (a + 0x9e3779b9) | 0
388
+ let t = a ^ (a >>> 16)
389
+ t = Math.imul(t, 0x21f0aaad)
390
+ t = t ^ (t >>> 15)
391
+ t = Math.imul(t, 0x735a2d97)
392
+ return ((t = t ^ (t >>> 15)) >>> 0) / 4294967296
393
+ }
394
+ }
395
+
396
+ function clamp01(n: number) {
397
+ if (!Number.isFinite(n)) return 0
398
+ return Math.min(1, Math.max(0, n))
399
+ }
400
+
401
+ /* ----- Shaders ----- */
402
+
403
+ const vertexShader = /* glsl */ `
404
+ uniform float uTime;
405
+ uniform sampler2D uPerlinTexture;
406
+ varying vec2 vUv;
407
+ void main() {
408
+ vUv = uv;
409
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
410
+ }
411
+ `
412
+
413
+ const fragmentShader = /* glsl */ `
414
+ uniform float uTime;
415
+ uniform float uAnimation;
416
+ uniform float uInverted;
417
+ uniform float uOffsets[7];
418
+ uniform vec3 uColor1;
419
+ uniform vec3 uColor2;
420
+ uniform float uInputVolume;
421
+ uniform float uOutputVolume;
422
+ uniform float uOpacity;
423
+ uniform sampler2D uPerlinTexture;
424
+ varying vec2 vUv;
425
+
426
+ const float PI = 3.14159265358979323846;
427
+
428
+ bool drawOval(vec2 polarUv, vec2 polarCenter, float a, float b, bool reverseGradient, float softness, out vec4 color) {
429
+ vec2 p = polarUv - polarCenter;
430
+ float oval = (p.x * p.x) / (a * a) + (p.y * p.y) / (b * b);
431
+ float edge = smoothstep(1.0, 1.0 - softness, oval);
432
+ if (edge > 0.0) {
433
+ float gradient = reverseGradient ? (1.0 - (p.x / a + 1.0) / 2.0) : ((p.x / a + 1.0) / 2.0);
434
+ gradient = mix(0.5, gradient, 0.1);
435
+ color = vec4(vec3(gradient), 0.85 * edge);
436
+ return true;
437
+ }
438
+ return false;
439
+ }
440
+
441
+ vec3 colorRamp(float grayscale, vec3 color1, vec3 color2, vec3 color3, vec3 color4) {
442
+ if (grayscale < 0.33) return mix(color1, color2, grayscale * 3.0);
443
+ else if (grayscale < 0.66) return mix(color2, color3, (grayscale - 0.33) * 3.0);
444
+ else return mix(color3, color4, (grayscale - 0.66) * 3.0);
445
+ }
446
+
447
+ vec2 hash2(vec2 p) {
448
+ return fract(sin(vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)))) * 43758.5453);
449
+ }
450
+
451
+ float noise2D(vec2 p) {
452
+ vec2 i = floor(p);
453
+ vec2 f = fract(p);
454
+ vec2 u = f * f * (3.0 - 2.0 * f);
455
+ float n = mix(
456
+ mix(dot(hash2(i + vec2(0.0, 0.0)), f - vec2(0.0, 0.0)),
457
+ dot(hash2(i + vec2(1.0, 0.0)), f - vec2(1.0, 0.0)), u.x),
458
+ mix(dot(hash2(i + vec2(0.0, 1.0)), f - vec2(0.0, 1.0)),
459
+ dot(hash2(i + vec2(1.0, 1.0)), f - vec2(1.0, 1.0)), u.x),
460
+ u.y
461
+ );
462
+ return 0.5 + 0.5 * n;
463
+ }
464
+
465
+ float sharpRing(vec3 decomposed, float time) {
466
+ float ringStart = 1.0;
467
+ float ringWidth = 0.3;
468
+ float noiseScale = 5.0;
469
+ float noise = mix(
470
+ noise2D(vec2(decomposed.x, time) * noiseScale),
471
+ noise2D(vec2(decomposed.y, time) * noiseScale),
472
+ decomposed.z
473
+ );
474
+ noise = (noise - 0.5) * 2.5;
475
+ return ringStart + noise * ringWidth * 1.5;
476
+ }
477
+
478
+ float smoothRing(vec3 decomposed, float time) {
479
+ float ringStart = 0.9;
480
+ float ringWidth = 0.2;
481
+ float noiseScale = 6.0;
482
+ float noise = mix(
483
+ noise2D(vec2(decomposed.x, time) * noiseScale),
484
+ noise2D(vec2(decomposed.y, time) * noiseScale),
485
+ decomposed.z
486
+ );
487
+ noise = (noise - 0.5) * 5.0;
488
+ return ringStart + noise * ringWidth;
489
+ }
490
+
491
+ float flow(vec3 decomposed, float time) {
492
+ return mix(
493
+ texture(uPerlinTexture, vec2(time, decomposed.x / 2.0)).r,
494
+ texture(uPerlinTexture, vec2(time, decomposed.y / 2.0)).r,
495
+ decomposed.z
496
+ );
497
+ }
498
+
499
+ void main() {
500
+ vec2 uv = vUv * 2.0 - 1.0;
501
+ float radius = length(uv);
502
+ float theta = atan(uv.y, uv.x);
503
+ if (theta < 0.0) theta += 2.0 * PI;
504
+
505
+ vec3 decomposed = vec3(
506
+ theta / (2.0 * PI),
507
+ mod(theta / (2.0 * PI) + 0.5, 1.0) + 1.0,
508
+ abs(theta / PI - 1.0)
509
+ );
510
+
511
+ float noise = flow(decomposed, radius * 0.03 - uAnimation * 0.2) - 0.5;
512
+ theta += noise * mix(0.08, 0.25, uOutputVolume);
513
+
514
+ vec4 color = vec4(1.0, 1.0, 1.0, 1.0);
515
+ float originalCenters[7] = float[7](0.0, 0.5 * PI, 1.0 * PI, 1.5 * PI, 2.0 * PI, 2.5 * PI, 3.0 * PI);
516
+ float centers[7];
517
+ for (int i = 0; i < 7; i++) {
518
+ centers[i] = originalCenters[i] + 0.5 * sin(uTime / 20.0 + uOffsets[i]);
519
+ }
520
+ float a, b;
521
+ vec4 ovalColor;
522
+ for (int i = 0; i < 7; i++) {
523
+ float noise = texture(uPerlinTexture, vec2(mod(centers[i] + uTime * 0.05, 1.0), 0.5)).r;
524
+ a = 0.5 + noise * 0.3;
525
+ b = noise * mix(3.5, 2.5, uInputVolume);
526
+ bool reverseGradient = (i % 2 == 1);
527
+ float distTheta = min(abs(theta - centers[i]), min(abs(theta + 2.0 * PI - centers[i]), abs(theta - 2.0 * PI - centers[i])));
528
+ float distRadius = radius;
529
+ float softness = 0.6;
530
+ if (drawOval(vec2(distTheta, distRadius), vec2(0.0, 0.0), a, b, reverseGradient, softness, ovalColor)) {
531
+ color.rgb = mix(color.rgb, ovalColor.rgb, ovalColor.a);
532
+ color.a = max(color.a, ovalColor.a);
533
+ }
534
+ }
535
+
536
+ float ringRadius1 = sharpRing(decomposed, uTime * 0.1);
537
+ float ringRadius2 = smoothRing(decomposed, uTime * 0.1);
538
+ float inputRadius1 = radius + uInputVolume * 0.2;
539
+ float inputRadius2 = radius + uInputVolume * 0.15;
540
+ float opacity1 = mix(0.2, 0.6, uInputVolume);
541
+ float opacity2 = mix(0.15, 0.45, uInputVolume);
542
+ float ringAlpha1 = (inputRadius2 >= ringRadius1) ? opacity1 : 0.0;
543
+ float ringAlpha2 = smoothstep(ringRadius2 - 0.05, ringRadius2 + 0.05, inputRadius1) * opacity2;
544
+ float totalRingAlpha = max(ringAlpha1, ringAlpha2);
545
+ vec3 ringColor = vec3(1.0);
546
+ color.rgb = 1.0 - (1.0 - color.rgb) * (1.0 - ringColor * totalRingAlpha);
547
+
548
+ vec3 color1 = vec3(0.0, 0.0, 0.0);
549
+ vec3 color2 = uColor1;
550
+ vec3 color3 = uColor2;
551
+ vec3 color4 = vec3(1.0, 1.0, 1.0);
552
+ float luminance = mix(color.r, 1.0 - color.r, uInverted);
553
+ color.rgb = colorRamp(luminance, color1, color2, color3, color4);
554
+ color.a *= uOpacity;
555
+ gl_FragColor = color;
556
+ }
557
+ `
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @handled-ai/design-system/three
3
+ * Three.js / WebGL components
4
+ */
5
+ export * from "./agent-orb"
@@ -0,0 +1,8 @@
1
+ import type { ThreeElements } from "@react-three/fiber"
2
+
3
+ declare module "react" {
4
+ namespace JSX {
5
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
6
+ interface IntrinsicElements extends ThreeElements {}
7
+ }
8
+ }