@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,373 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { X } from "lucide-react"
5
+ import { cn } from "../lib/utils"
6
+ import { AgentWidget, type AgentWidgetMessage, type AgentWidgetStatus, type AgentWidgetMode } from "./agent-widget"
7
+
8
+ /* ========================================
9
+ AgentPopover (root)
10
+ ======================================== */
11
+
12
+ export type AgentPopoverStep = string
13
+
14
+ export interface AgentPopoverContextValue {
15
+ step: AgentPopoverStep
16
+ setStep: (step: AgentPopoverStep) => void
17
+ }
18
+
19
+ const AgentPopoverContext = React.createContext<AgentPopoverContextValue | null>(null)
20
+
21
+ export function useAgentPopover() {
22
+ const ctx = React.useContext(AgentPopoverContext)
23
+ if (!ctx) throw new Error("useAgentPopover must be used within <AgentPopover>")
24
+ return ctx
25
+ }
26
+
27
+ export interface AgentPopoverProps {
28
+ open: boolean
29
+ onOpenChange: (open: boolean) => void
30
+ defaultStep?: string
31
+ step?: string
32
+ onStepChange?: (step: string) => void
33
+ children: React.ReactNode
34
+ className?: string
35
+ }
36
+
37
+ export function AgentPopover({
38
+ open,
39
+ onOpenChange,
40
+ defaultStep = "details",
41
+ step: controlledStep,
42
+ onStepChange,
43
+ children,
44
+ className,
45
+ }: AgentPopoverProps) {
46
+ const [uncontrolledStep, setUncontrolledStep] = React.useState(defaultStep)
47
+ const step = controlledStep ?? uncontrolledStep
48
+ const setStep = React.useCallback(
49
+ (s: string) => {
50
+ if (controlledStep === undefined) setUncontrolledStep(s)
51
+ onStepChange?.(s)
52
+ },
53
+ [controlledStep, onStepChange],
54
+ )
55
+
56
+ React.useEffect(() => {
57
+ if (!open) return
58
+ const onKeyDown = (e: KeyboardEvent) => {
59
+ if (e.key === "Escape") onOpenChange(false)
60
+ }
61
+ document.addEventListener("keydown", onKeyDown)
62
+ return () => document.removeEventListener("keydown", onKeyDown)
63
+ }, [open, onOpenChange])
64
+
65
+ if (!open) return null
66
+
67
+ return (
68
+ <AgentPopoverContext.Provider value={{ step, setStep }}>
69
+ {/* Backdrop */}
70
+ <div
71
+ className="fixed inset-0 z-50 bg-black/30 backdrop-blur-sm animate-in fade-in duration-200"
72
+ onClick={() => onOpenChange(false)}
73
+ />
74
+ {/* Panel */}
75
+ <div
76
+ role="dialog"
77
+ aria-modal="true"
78
+ className={cn(
79
+ "fixed inset-4 z-50 flex overflow-hidden rounded-3xl bg-background shadow-2xl ring-1 ring-border/50 animate-in zoom-in-95 fade-in duration-300",
80
+ "md:inset-auto md:left-1/2 md:top-1/2 md:-translate-x-1/2 md:-translate-y-1/2 md:max-w-5xl md:w-[92vw] md:h-[85vh]",
81
+ className,
82
+ )}
83
+ >
84
+ {/* Close button */}
85
+ <button
86
+ type="button"
87
+ onClick={() => onOpenChange(false)}
88
+ className="absolute right-4 top-4 z-20 p-2 rounded-full bg-background/80 hover:bg-muted transition-colors text-foreground"
89
+ >
90
+ <X className="w-4 h-4" />
91
+ </button>
92
+
93
+ {children}
94
+ </div>
95
+ </AgentPopoverContext.Provider>
96
+ )
97
+ }
98
+
99
+ /* ========================================
100
+ AgentPopoverBranding (left panel)
101
+ ======================================== */
102
+
103
+ export type ConnectionStatus = "ready" | "connected" | "listening" | "speaking"
104
+
105
+ export interface AgentPopoverBrandingProps {
106
+ title?: string
107
+ subtitle?: string
108
+ badge?: string
109
+ visualSlot?: React.ReactNode
110
+ statusIndicator?: ConnectionStatus
111
+ className?: string
112
+ }
113
+
114
+ export function AgentPopoverBranding({
115
+ title,
116
+ subtitle,
117
+ badge,
118
+ visualSlot,
119
+ statusIndicator = "ready",
120
+ className,
121
+ }: AgentPopoverBrandingProps) {
122
+ return (
123
+ <div
124
+ className={cn(
125
+ "relative hidden w-[40%] shrink-0 flex-col items-center justify-center p-8 md:flex",
126
+ "bg-gradient-to-br from-primary/5 via-background to-primary/10",
127
+ className,
128
+ )}
129
+ >
130
+ {/* Dot pattern overlay */}
131
+ <div
132
+ className="absolute inset-0 pointer-events-none opacity-[0.06]"
133
+ style={{
134
+ backgroundImage: "radial-gradient(circle, var(--primary) 1px, transparent 1px)",
135
+ backgroundSize: "24px 24px",
136
+ }}
137
+ />
138
+
139
+ <div className="relative z-10 flex flex-col items-center gap-6 text-center">
140
+ {visualSlot && (
141
+ <div className="w-[200px] h-[200px] flex items-center justify-center">
142
+ {visualSlot}
143
+ </div>
144
+ )}
145
+
146
+ {badge && (
147
+ <span className="inline-flex px-3 py-1 rounded-full bg-primary/10 text-primary text-xs font-medium border border-primary/10">
148
+ {badge}
149
+ </span>
150
+ )}
151
+
152
+ {title && <h2 className="text-xl font-semibold text-foreground">{title}</h2>}
153
+ {subtitle && <p className="text-sm text-muted-foreground max-w-[240px]">{subtitle}</p>}
154
+
155
+ <StatusPill status={statusIndicator} />
156
+ </div>
157
+ </div>
158
+ )
159
+ }
160
+
161
+ function StatusPill({ status }: { status: ConnectionStatus }) {
162
+ const label: Record<ConnectionStatus, string> = {
163
+ ready: "Ready to connect",
164
+ connected: "Connected",
165
+ listening: "Listening...",
166
+ speaking: "Speaking...",
167
+ }
168
+
169
+ const dotColor: Record<ConnectionStatus, string> = {
170
+ ready: "bg-muted-foreground",
171
+ connected: "bg-green-500",
172
+ listening: "bg-green-500 animate-pulse",
173
+ speaking: "bg-primary animate-pulse",
174
+ }
175
+
176
+ return (
177
+ <div className="flex items-center gap-2 rounded-full border border-border px-3 py-1.5 bg-background/50">
178
+ <div className={cn("h-2 w-2 rounded-full", dotColor[status])} />
179
+ <span className="text-xs text-muted-foreground">{label[status]}</span>
180
+ </div>
181
+ )
182
+ }
183
+
184
+ /* ========================================
185
+ AgentPopoverStepContent (right panel step)
186
+ ======================================== */
187
+
188
+ export interface AgentPopoverStepContentProps {
189
+ step: string
190
+ children: React.ReactNode
191
+ className?: string
192
+ }
193
+
194
+ export function AgentPopoverStepContent({ step, children, className }: AgentPopoverStepContentProps) {
195
+ const { step: currentStep } = useAgentPopover()
196
+ if (currentStep !== step) return null
197
+ return <div className={cn("flex-1 flex flex-col overflow-hidden", className)}>{children}</div>
198
+ }
199
+
200
+ /* ========================================
201
+ AgentPopoverForm (details step)
202
+ ======================================== */
203
+
204
+ export interface AgentPopoverFormField {
205
+ name: string
206
+ label: string
207
+ type?: "text" | "email" | "tel" | "textarea"
208
+ placeholder?: string
209
+ required?: boolean
210
+ }
211
+
212
+ export interface AgentPopoverFormProps {
213
+ fields?: AgentPopoverFormField[]
214
+ submitLabel?: string
215
+ onSubmit?: (values: Record<string, string>) => void
216
+ className?: string
217
+ children?: React.ReactNode
218
+ }
219
+
220
+ export function AgentPopoverForm({
221
+ fields = [],
222
+ submitLabel = "Continue",
223
+ onSubmit,
224
+ className,
225
+ children,
226
+ }: AgentPopoverFormProps) {
227
+ const [values, setValues] = React.useState<Record<string, string>>({})
228
+
229
+ const handleSubmit = (e: React.FormEvent) => {
230
+ e.preventDefault()
231
+ onSubmit?.(values)
232
+ }
233
+
234
+ return (
235
+ <form
236
+ onSubmit={handleSubmit}
237
+ className={cn("flex flex-col h-full p-8 overflow-y-auto", className)}
238
+ >
239
+ <div className="flex-1 space-y-4">
240
+ {fields.map((field) =>
241
+ field.type === "textarea" ? (
242
+ <label key={field.name} className="block space-y-1.5">
243
+ <span className="text-sm font-medium text-foreground">
244
+ {field.label}
245
+ {field.required && <span className="text-destructive ml-0.5">*</span>}
246
+ </span>
247
+ <textarea
248
+ className="w-full rounded-xl border border-border bg-background px-4 py-3 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary min-h-[80px] resize-none"
249
+ placeholder={field.placeholder}
250
+ required={field.required}
251
+ value={values[field.name] ?? ""}
252
+ onChange={(e) => setValues((prev) => ({ ...prev, [field.name]: e.target.value }))}
253
+ />
254
+ </label>
255
+ ) : (
256
+ <label key={field.name} className="block space-y-1.5">
257
+ <span className="text-sm font-medium text-foreground">
258
+ {field.label}
259
+ {field.required && <span className="text-destructive ml-0.5">*</span>}
260
+ </span>
261
+ <input
262
+ type={field.type ?? "text"}
263
+ className="w-full rounded-xl border border-border bg-background px-4 py-3 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
264
+ placeholder={field.placeholder}
265
+ required={field.required}
266
+ value={values[field.name] ?? ""}
267
+ onChange={(e) => setValues((prev) => ({ ...prev, [field.name]: e.target.value }))}
268
+ />
269
+ </label>
270
+ ),
271
+ )}
272
+ {children}
273
+ </div>
274
+ <button
275
+ type="submit"
276
+ className="mt-6 w-full px-6 py-3 rounded-full bg-primary text-primary-foreground hover:bg-primary/90 font-medium transition-colors"
277
+ >
278
+ {submitLabel}
279
+ </button>
280
+ </form>
281
+ )
282
+ }
283
+
284
+ /* ========================================
285
+ AgentPopoverOverview
286
+ ======================================== */
287
+
288
+ export interface AgentPopoverOverviewProps {
289
+ userSummary?: Record<string, string>
290
+ discussionPoints?: string[]
291
+ actions?: React.ReactNode
292
+ className?: string
293
+ }
294
+
295
+ export function AgentPopoverOverview({
296
+ userSummary,
297
+ discussionPoints,
298
+ actions,
299
+ className,
300
+ }: AgentPopoverOverviewProps) {
301
+ return (
302
+ <div className={cn("flex flex-col h-full p-8 overflow-y-auto", className)}>
303
+ {userSummary && Object.keys(userSummary).length > 0 && (
304
+ <div className="mb-6 rounded-xl bg-muted/50 p-4 space-y-2">
305
+ {Object.entries(userSummary).map(([key, val]) => (
306
+ <div key={key} className="flex items-center justify-between text-sm">
307
+ <span className="text-muted-foreground capitalize">{key}</span>
308
+ <span className="text-foreground font-medium">{val}</span>
309
+ </div>
310
+ ))}
311
+ </div>
312
+ )}
313
+
314
+ {discussionPoints && discussionPoints.length > 0 && (
315
+ <div className="mb-6">
316
+ <h3 className="text-sm font-semibold text-foreground mb-3">Discussion Points</h3>
317
+ <ul className="space-y-2">
318
+ {discussionPoints.map((point, i) => (
319
+ <li key={i} className="flex items-start gap-2 text-sm text-foreground">
320
+ <div className="mt-1.5 w-1.5 h-1.5 rounded-full bg-primary shrink-0" />
321
+ {point}
322
+ </li>
323
+ ))}
324
+ </ul>
325
+ </div>
326
+ )}
327
+
328
+ {actions && <div className="mt-auto pt-6">{actions}</div>}
329
+ </div>
330
+ )
331
+ }
332
+
333
+ /* ========================================
334
+ AgentPopoverChat (interview step)
335
+ ======================================== */
336
+
337
+ export interface AgentPopoverChatProps {
338
+ messages?: AgentWidgetMessage[]
339
+ onSendMessage?: (text: string) => void
340
+ onEndSession?: () => void
341
+ inputMode?: "voice" | "text" | "voice+text"
342
+ visualSlot?: React.ReactNode
343
+ assistantAvatarSlot?: React.ReactNode
344
+ status?: AgentWidgetStatus
345
+ mode?: AgentWidgetMode
346
+ className?: string
347
+ }
348
+
349
+ export function AgentPopoverChat({
350
+ messages = [],
351
+ onSendMessage,
352
+ onEndSession,
353
+ inputMode = "voice+text",
354
+ visualSlot,
355
+ assistantAvatarSlot,
356
+ status = "connected",
357
+ mode = null,
358
+ className,
359
+ }: AgentPopoverChatProps) {
360
+ return (
361
+ <AgentWidget
362
+ messages={messages}
363
+ onSendMessage={onSendMessage}
364
+ onEndSession={onEndSession}
365
+ inputMode={inputMode}
366
+ visualSlot={visualSlot}
367
+ assistantAvatarSlot={assistantAvatarSlot}
368
+ status={status}
369
+ mode={mode}
370
+ className={cn("h-full", className)}
371
+ />
372
+ )
373
+ }
@@ -0,0 +1,163 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Mic, PhoneOff, Send, Sparkles } from "lucide-react"
5
+ import { cn } from "../lib/utils"
6
+ import { Message, MessageContent, MessageAvatar } from "./message"
7
+
8
+ export type AgentWidgetStatus = "idle" | "connecting" | "connected" | "error"
9
+ export type AgentWidgetMode = "listening" | "speaking" | null
10
+
11
+ export interface AgentWidgetMessage {
12
+ from: "user" | "assistant"
13
+ text: string
14
+ }
15
+
16
+ export interface AgentWidgetProps {
17
+ status?: AgentWidgetStatus
18
+ mode?: AgentWidgetMode
19
+ messages?: AgentWidgetMessage[]
20
+ onSendMessage?: (text: string) => void
21
+ onEndSession?: () => void
22
+ inputMode?: "voice" | "text" | "voice+text"
23
+ visualSlot?: React.ReactNode
24
+ assistantAvatarSlot?: React.ReactNode
25
+ header?: React.ReactNode
26
+ footer?: React.ReactNode
27
+ className?: string
28
+ }
29
+
30
+ export function AgentWidget({
31
+ status = "idle",
32
+ mode = null,
33
+ messages = [],
34
+ onSendMessage,
35
+ onEndSession,
36
+ inputMode = "voice+text",
37
+ visualSlot,
38
+ assistantAvatarSlot,
39
+ header,
40
+ footer,
41
+ className,
42
+ }: AgentWidgetProps) {
43
+ const [textInput, setTextInput] = React.useState("")
44
+ const messagesEndRef = React.useRef<HTMLDivElement>(null)
45
+
46
+ React.useEffect(() => {
47
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
48
+ }, [messages])
49
+
50
+ const handleSend = () => {
51
+ if (!textInput.trim()) return
52
+ onSendMessage?.(textInput)
53
+ setTextInput("")
54
+ }
55
+
56
+ const showTextInput = inputMode === "text" || inputMode === "voice+text"
57
+ const isConnected = status === "connected"
58
+
59
+ return (
60
+ <div className={cn("flex flex-col h-full overflow-hidden", className)}>
61
+ {header}
62
+
63
+ {/* Chat area */}
64
+ <div className="flex-1 overflow-y-auto p-4 space-y-4 bg-muted/20">
65
+ {messages.length === 0 ? (
66
+ <div className="flex flex-col items-center justify-center h-full text-muted-foreground space-y-2 opacity-50">
67
+ {visualSlot ?? (
68
+ <div className="w-12 h-12 rounded-full bg-muted flex items-center justify-center">
69
+ <Sparkles className="w-5 h-5" />
70
+ </div>
71
+ )}
72
+ <p className="text-sm">
73
+ {status === "connecting" ? "Connecting..." : "Conversation started..."}
74
+ </p>
75
+ </div>
76
+ ) : (
77
+ <>
78
+ {messages.map((msg, idx) => (
79
+ <Message key={idx} from={msg.from}>
80
+ {msg.from === "assistant" && assistantAvatarSlot ? (
81
+ <MessageAvatar>{assistantAvatarSlot}</MessageAvatar>
82
+ ) : null}
83
+ <MessageContent>{msg.text.replace(/^["']|["']$/g, "")}</MessageContent>
84
+ </Message>
85
+ ))}
86
+ <div ref={messagesEndRef} />
87
+ </>
88
+ )}
89
+ </div>
90
+
91
+ {/* Voice status indicator */}
92
+ {inputMode !== "text" && isConnected && (
93
+ <div className="flex justify-center py-2 bg-background">
94
+ {mode === "listening" ? (
95
+ <div className="inline-flex items-center gap-2 px-4 py-2 bg-primary/5 rounded-full border border-primary/10">
96
+ <div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
97
+ <span className="text-sm font-medium text-foreground">Listening</span>
98
+ </div>
99
+ ) : mode === "speaking" ? (
100
+ <button
101
+ type="button"
102
+ className="inline-flex items-center gap-2 px-4 py-2 bg-background rounded-full border border-border hover:border-primary transition-colors"
103
+ >
104
+ <Mic className="w-4 h-4 text-primary" />
105
+ <span className="text-sm font-medium text-foreground">Talk to interrupt</span>
106
+ </button>
107
+ ) : null}
108
+ </div>
109
+ )}
110
+
111
+ {/* Input area */}
112
+ <div className="flex-none p-4 bg-background border-t border-border">
113
+ {showTextInput ? (
114
+ <div className="relative border-2 border-foreground rounded-2xl p-4 bg-background shadow-sm transition-all focus-within:shadow-md">
115
+ <textarea
116
+ className="w-full resize-none border-none focus:ring-0 p-0 text-sm placeholder:text-muted-foreground min-h-[60px] bg-transparent outline-none focus:outline-none pr-24"
117
+ placeholder="Send a message..."
118
+ value={textInput}
119
+ onChange={(e) => setTextInput(e.target.value)}
120
+ onKeyDown={(e) => {
121
+ if (e.key === "Enter" && !e.shiftKey) {
122
+ e.preventDefault()
123
+ handleSend()
124
+ }
125
+ }}
126
+ />
127
+ <div className="absolute bottom-4 right-4 flex items-center gap-3">
128
+ {onEndSession && (
129
+ <button
130
+ type="button"
131
+ onClick={onEndSession}
132
+ className="w-10 h-10 flex items-center justify-center rounded-full bg-background border border-border shadow-sm hover:bg-muted transition-colors group"
133
+ title="End session"
134
+ >
135
+ <PhoneOff className="w-5 h-5 text-foreground group-hover:text-destructive transition-colors" />
136
+ </button>
137
+ )}
138
+ <button
139
+ type="button"
140
+ onClick={handleSend}
141
+ disabled={!textInput.trim()}
142
+ className="w-10 h-10 flex items-center justify-center rounded-full bg-muted-foreground hover:bg-foreground disabled:opacity-50 disabled:cursor-not-allowed transition-colors text-background shadow-sm"
143
+ >
144
+ <Send className="w-5 h-5 ml-0.5" />
145
+ </button>
146
+ </div>
147
+ </div>
148
+ ) : onEndSession ? (
149
+ <button
150
+ type="button"
151
+ onClick={onEndSession}
152
+ className="flex items-center justify-center gap-2 w-full px-6 py-3 bg-destructive/5 hover:bg-destructive/10 text-destructive border border-destructive/20 rounded-full font-medium transition-all"
153
+ >
154
+ <span className="w-2 h-2 bg-destructive rounded-full" />
155
+ End Session
156
+ </button>
157
+ ) : null}
158
+ </div>
159
+
160
+ {footer}
161
+ </div>
162
+ )
163
+ }
@@ -0,0 +1,109 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Avatar as AvatarPrimitive } from "radix-ui"
5
+
6
+ import { cn } from "../lib/utils"
7
+
8
+ function Avatar({
9
+ className,
10
+ size = "default",
11
+ ...props
12
+ }: React.ComponentProps<typeof AvatarPrimitive.Root> & {
13
+ size?: "default" | "sm" | "lg"
14
+ }) {
15
+ return (
16
+ <AvatarPrimitive.Root
17
+ data-slot="avatar"
18
+ data-size={size}
19
+ className={cn(
20
+ "group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6",
21
+ className
22
+ )}
23
+ {...props}
24
+ />
25
+ )
26
+ }
27
+
28
+ function AvatarImage({
29
+ className,
30
+ ...props
31
+ }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
32
+ return (
33
+ <AvatarPrimitive.Image
34
+ data-slot="avatar-image"
35
+ className={cn("aspect-square size-full", className)}
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+
41
+ function AvatarFallback({
42
+ className,
43
+ ...props
44
+ }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
45
+ return (
46
+ <AvatarPrimitive.Fallback
47
+ data-slot="avatar-fallback"
48
+ className={cn(
49
+ "bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs",
50
+ className
51
+ )}
52
+ {...props}
53
+ />
54
+ )
55
+ }
56
+
57
+ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
58
+ return (
59
+ <span
60
+ data-slot="avatar-badge"
61
+ className={cn(
62
+ "bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full ring-2 select-none",
63
+ "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
64
+ "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
65
+ "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
66
+ className
67
+ )}
68
+ {...props}
69
+ />
70
+ )
71
+ }
72
+
73
+ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
74
+ return (
75
+ <div
76
+ data-slot="avatar-group"
77
+ className={cn(
78
+ "*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2",
79
+ className
80
+ )}
81
+ {...props}
82
+ />
83
+ )
84
+ }
85
+
86
+ function AvatarGroupCount({
87
+ className,
88
+ ...props
89
+ }: React.ComponentProps<"div">) {
90
+ return (
91
+ <div
92
+ data-slot="avatar-group-count"
93
+ className={cn(
94
+ "bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-sm ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
95
+ className
96
+ )}
97
+ {...props}
98
+ />
99
+ )
100
+ }
101
+
102
+ export {
103
+ Avatar,
104
+ AvatarImage,
105
+ AvatarFallback,
106
+ AvatarBadge,
107
+ AvatarGroup,
108
+ AvatarGroupCount,
109
+ }
@@ -0,0 +1,48 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { Slot } from "radix-ui"
4
+
5
+ import { cn } from "../lib/utils"
6
+
7
+ const badgeVariants = cva(
8
+ "inline-flex items-center justify-center rounded-md border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
13
+ secondary:
14
+ "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
15
+ destructive:
16
+ "bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
17
+ outline:
18
+ "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
19
+ ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
20
+ link: "text-primary underline-offset-4 [a&]:hover:underline",
21
+ },
22
+ },
23
+ defaultVariants: {
24
+ variant: "default",
25
+ },
26
+ }
27
+ )
28
+
29
+ function Badge({
30
+ className,
31
+ variant = "default",
32
+ asChild = false,
33
+ ...props
34
+ }: React.ComponentProps<"span"> &
35
+ VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
36
+ const Comp = asChild ? Slot.Root : "span"
37
+
38
+ return (
39
+ <Comp
40
+ data-slot="badge"
41
+ data-variant={variant}
42
+ className={cn(badgeVariants({ variant }), className)}
43
+ {...props}
44
+ />
45
+ )
46
+ }
47
+
48
+ export { Badge, badgeVariants }