@nextsparkjs/theme-default 0.1.0-beta.1

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 (333) hide show
  1. package/about/business.md +49 -0
  2. package/about/features.json +302 -0
  3. package/about/team.md +79 -0
  4. package/api/ai/chat/stream/route.ts +212 -0
  5. package/api/ai/orchestrator/route.ts +226 -0
  6. package/api/ai/single-agent/route.ts +291 -0
  7. package/api/ai/usage/route.ts +122 -0
  8. package/blocks/benefits/component.tsx +100 -0
  9. package/blocks/benefits/config.ts +11 -0
  10. package/blocks/benefits/examples.ts +85 -0
  11. package/blocks/benefits/fields.ts +156 -0
  12. package/blocks/benefits/schema.ts +33 -0
  13. package/blocks/cta-section/component.tsx +100 -0
  14. package/blocks/cta-section/config.ts +11 -0
  15. package/blocks/cta-section/examples.ts +41 -0
  16. package/blocks/cta-section/fields.ts +89 -0
  17. package/blocks/cta-section/index.ts +6 -0
  18. package/blocks/cta-section/schema.ts +32 -0
  19. package/blocks/cta-section/thumbnail.png +1 -0
  20. package/blocks/faq-accordion/component.tsx +156 -0
  21. package/blocks/faq-accordion/config.ts +11 -0
  22. package/blocks/faq-accordion/examples.ts +77 -0
  23. package/blocks/faq-accordion/fields.ts +119 -0
  24. package/blocks/faq-accordion/index.ts +6 -0
  25. package/blocks/faq-accordion/schema.ts +45 -0
  26. package/blocks/features-grid/component.tsx +112 -0
  27. package/blocks/features-grid/config.ts +11 -0
  28. package/blocks/features-grid/examples.ts +63 -0
  29. package/blocks/features-grid/fields.ts +97 -0
  30. package/blocks/features-grid/index.ts +6 -0
  31. package/blocks/features-grid/schema.ts +40 -0
  32. package/blocks/features-grid/thumbnail.png +1 -0
  33. package/blocks/hero/component.tsx +100 -0
  34. package/blocks/hero/config.ts +11 -0
  35. package/blocks/hero/examples.ts +35 -0
  36. package/blocks/hero/fields.ts +60 -0
  37. package/blocks/hero/index.ts +6 -0
  38. package/blocks/hero/schema.ts +32 -0
  39. package/blocks/hero/thumbnail.png +1 -0
  40. package/blocks/hero/thumbnail.png.txt +6 -0
  41. package/blocks/hero-with-form/component.tsx +232 -0
  42. package/blocks/hero-with-form/config.ts +11 -0
  43. package/blocks/hero-with-form/examples.ts +16 -0
  44. package/blocks/hero-with-form/fields.ts +207 -0
  45. package/blocks/hero-with-form/index.ts +6 -0
  46. package/blocks/hero-with-form/schema.ts +54 -0
  47. package/blocks/jumbotron/component.tsx +136 -0
  48. package/blocks/jumbotron/config.ts +11 -0
  49. package/blocks/jumbotron/examples.ts +36 -0
  50. package/blocks/jumbotron/fields.ts +202 -0
  51. package/blocks/jumbotron/index.ts +6 -0
  52. package/blocks/jumbotron/schema.ts +55 -0
  53. package/blocks/logo-cloud/component.tsx +154 -0
  54. package/blocks/logo-cloud/config.ts +11 -0
  55. package/blocks/logo-cloud/examples.ts +34 -0
  56. package/blocks/logo-cloud/fields.ts +133 -0
  57. package/blocks/logo-cloud/index.ts +6 -0
  58. package/blocks/logo-cloud/schema.ts +46 -0
  59. package/blocks/post-content/component.tsx +197 -0
  60. package/blocks/post-content/config.ts +11 -0
  61. package/blocks/post-content/examples.ts +33 -0
  62. package/blocks/post-content/fields.ts +165 -0
  63. package/blocks/post-content/index.ts +4 -0
  64. package/blocks/post-content/schema.ts +46 -0
  65. package/blocks/pricing-table/component.tsx +154 -0
  66. package/blocks/pricing-table/config.ts +11 -0
  67. package/blocks/pricing-table/examples.ts +96 -0
  68. package/blocks/pricing-table/fields.ts +161 -0
  69. package/blocks/pricing-table/index.ts +4 -0
  70. package/blocks/pricing-table/schema.ts +50 -0
  71. package/blocks/split-content/component.tsx +135 -0
  72. package/blocks/split-content/config.ts +11 -0
  73. package/blocks/split-content/examples.ts +38 -0
  74. package/blocks/split-content/fields.ts +198 -0
  75. package/blocks/split-content/index.ts +6 -0
  76. package/blocks/split-content/schema.ts +67 -0
  77. package/blocks/stats-counter/component.tsx +124 -0
  78. package/blocks/stats-counter/config.ts +11 -0
  79. package/blocks/stats-counter/examples.ts +61 -0
  80. package/blocks/stats-counter/fields.ts +134 -0
  81. package/blocks/stats-counter/index.ts +6 -0
  82. package/blocks/stats-counter/schema.ts +47 -0
  83. package/blocks/testimonials/component.tsx +114 -0
  84. package/blocks/testimonials/config.ts +11 -0
  85. package/blocks/testimonials/examples.ts +65 -0
  86. package/blocks/testimonials/fields.ts +105 -0
  87. package/blocks/testimonials/index.ts +6 -0
  88. package/blocks/testimonials/schema.ts +41 -0
  89. package/blocks/testimonials/thumbnail.png +1 -0
  90. package/blocks/text-content/component.tsx +97 -0
  91. package/blocks/text-content/config.ts +11 -0
  92. package/blocks/text-content/examples.ts +30 -0
  93. package/blocks/text-content/fields.ts +88 -0
  94. package/blocks/text-content/index.ts +6 -0
  95. package/blocks/text-content/schema.ts +30 -0
  96. package/blocks/text-content/thumbnail.png +1 -0
  97. package/blocks/timeline/component.tsx +267 -0
  98. package/blocks/timeline/config.ts +11 -0
  99. package/blocks/timeline/examples.ts +68 -0
  100. package/blocks/timeline/fields.ts +147 -0
  101. package/blocks/timeline/index.ts +6 -0
  102. package/blocks/timeline/schema.ts +49 -0
  103. package/blocks/video-hero/component.tsx +270 -0
  104. package/blocks/video-hero/config.ts +11 -0
  105. package/blocks/video-hero/examples.ts +24 -0
  106. package/blocks/video-hero/fields.ts +98 -0
  107. package/blocks/video-hero/index.ts +6 -0
  108. package/blocks/video-hero/schema.ts +39 -0
  109. package/components/ai-chat/ChatPanel.tsx +575 -0
  110. package/components/ai-chat/ConversationItem.tsx +266 -0
  111. package/components/ai-chat/ConversationSidebar.tsx +99 -0
  112. package/components/ai-chat/MarkdownRenderer.tsx +15 -0
  113. package/components/ai-chat/Message.tsx +42 -0
  114. package/components/ai-chat/MessageInput.tsx +49 -0
  115. package/components/ai-chat/MessageList.tsx +46 -0
  116. package/components/ai-chat/TypingIndicator.tsx +11 -0
  117. package/config/app.config.ts +367 -0
  118. package/config/billing.config.ts +349 -0
  119. package/config/dashboard.config.ts +506 -0
  120. package/config/dev.config.ts +104 -0
  121. package/config/features.config.ts +203 -0
  122. package/config/flows.config.ts +129 -0
  123. package/config/permissions.config.ts +245 -0
  124. package/config/theme.config.ts +74 -0
  125. package/docs/01-overview/01-introduction.md +335 -0
  126. package/docs/01-overview/02-customization.md +671 -0
  127. package/docs/02-features/01-components.md +155 -0
  128. package/docs/02-features/02-styling.md +139 -0
  129. package/docs/02-features/03-tasks-entity.md +407 -0
  130. package/docs/03-ai/01-overview.md +211 -0
  131. package/docs/03-ai/02-customization.md +436 -0
  132. package/entities/customers/customers.config.ts +75 -0
  133. package/entities/customers/customers.fields.ts +165 -0
  134. package/entities/customers/customers.service.ts +516 -0
  135. package/entities/customers/customers.types.ts +83 -0
  136. package/entities/customers/messages/en.json +66 -0
  137. package/entities/customers/messages/es.json +66 -0
  138. package/entities/customers/migrations/001_customers_table.sql +102 -0
  139. package/entities/customers/migrations/002_customers_metas.sql +92 -0
  140. package/entities/pages/messages/en.json +41 -0
  141. package/entities/pages/messages/es.json +41 -0
  142. package/entities/pages/migrations/001_pages_table.sql +112 -0
  143. package/entities/pages/migrations/002_pages_metas.sql +56 -0
  144. package/entities/pages/migrations/003_add_status.sql +50 -0
  145. package/entities/pages/pages-management.service.ts +610 -0
  146. package/entities/pages/pages.config.ts +94 -0
  147. package/entities/pages/pages.fields.ts +101 -0
  148. package/entities/pages/pages.service.ts +290 -0
  149. package/entities/pages/pages.types.ts +124 -0
  150. package/entities/posts/components/post-header.tsx +97 -0
  151. package/entities/posts/messages/en.json +55 -0
  152. package/entities/posts/messages/es.json +55 -0
  153. package/entities/posts/migrations/001_posts_table.sql +115 -0
  154. package/entities/posts/migrations/003_add_status.sql +44 -0
  155. package/entities/posts/migrations/004_entity_taxonomy_relations.sql +129 -0
  156. package/entities/posts/migrations/006_posts_metas.sql +56 -0
  157. package/entities/posts/posts.config.ts +101 -0
  158. package/entities/posts/posts.fields.ts +116 -0
  159. package/entities/posts/posts.service.ts +376 -0
  160. package/entities/posts/posts.types.ts +74 -0
  161. package/entities/tasks/messages/en.json +204 -0
  162. package/entities/tasks/messages/es.json +204 -0
  163. package/entities/tasks/migrations/001_tasks_table.sql +105 -0
  164. package/entities/tasks/migrations/002_task_metas.sql +85 -0
  165. package/entities/tasks/migrations/sample_data.json +77 -0
  166. package/entities/tasks/tasks.config.ts +79 -0
  167. package/entities/tasks/tasks.fields.ts +196 -0
  168. package/entities/tasks/tasks.service.ts +541 -0
  169. package/entities/tasks/tasks.types.ts +56 -0
  170. package/lib/hooks/useAiChat.ts +114 -0
  171. package/lib/hooks/useConversations.ts +376 -0
  172. package/lib/hooks/useOrchestratorChat.ts +122 -0
  173. package/lib/hooks/usePersistentChat.ts +315 -0
  174. package/lib/hooks/useStreamingChat.ts +127 -0
  175. package/lib/hooks/useTokenUsage.ts +63 -0
  176. package/lib/langchain/agents/customer-assistant.md +69 -0
  177. package/lib/langchain/agents/index.ts +61 -0
  178. package/lib/langchain/agents/orchestrator.md +59 -0
  179. package/lib/langchain/agents/page-assistant.md +85 -0
  180. package/lib/langchain/agents/single-agent.md +46 -0
  181. package/lib/langchain/agents/task-assistant.md +55 -0
  182. package/lib/langchain/config.ts +45 -0
  183. package/lib/langchain/handlers/customer-handler.ts +338 -0
  184. package/lib/langchain/handlers/page-handler.ts +232 -0
  185. package/lib/langchain/handlers/task-handler.ts +323 -0
  186. package/lib/langchain/langchain.config.ts +223 -0
  187. package/lib/langchain/observability.config.ts +30 -0
  188. package/lib/langchain/orchestrator.ts +562 -0
  189. package/lib/langchain/tools/customers.ts +176 -0
  190. package/lib/langchain/tools/index.ts +10 -0
  191. package/lib/langchain/tools/orchestrator.ts +92 -0
  192. package/lib/langchain/tools/pages.ts +289 -0
  193. package/lib/langchain/tools/tasks.ts +167 -0
  194. package/lib/scheduled-actions/billing.ts +149 -0
  195. package/lib/scheduled-actions/index.ts +170 -0
  196. package/lib/scheduled-actions/webhook.ts +231 -0
  197. package/lib/selectors.ts +197 -0
  198. package/messages/de/admin.json +219 -0
  199. package/messages/de/aiUsage.json +36 -0
  200. package/messages/de/buttons.json +19 -0
  201. package/messages/de/categories.json +35 -0
  202. package/messages/de/common.json +16 -0
  203. package/messages/de/dev.json +101 -0
  204. package/messages/de/docs.json +27 -0
  205. package/messages/de/entities.json +7 -0
  206. package/messages/de/features.json +119 -0
  207. package/messages/de/footer.json +22 -0
  208. package/messages/de/home.json +57 -0
  209. package/messages/de/index.ts +39 -0
  210. package/messages/de/mobileNav.json +13 -0
  211. package/messages/de/navigation.json +8 -0
  212. package/messages/de/observability.json +74 -0
  213. package/messages/de/posts.json +54 -0
  214. package/messages/de/pricing.json +102 -0
  215. package/messages/de/support.json +9 -0
  216. package/messages/de/teams.json +8 -0
  217. package/messages/en/admin.json +219 -0
  218. package/messages/en/aiUsage.json +36 -0
  219. package/messages/en/buttons.json +19 -0
  220. package/messages/en/categories.json +35 -0
  221. package/messages/en/common.json +16 -0
  222. package/messages/en/dev.json +106 -0
  223. package/messages/en/docs.json +27 -0
  224. package/messages/en/entities.json +7 -0
  225. package/messages/en/features.json +119 -0
  226. package/messages/en/footer.json +22 -0
  227. package/messages/en/home.json +57 -0
  228. package/messages/en/index.ts +39 -0
  229. package/messages/en/mobileNav.json +13 -0
  230. package/messages/en/navigation.json +8 -0
  231. package/messages/en/observability.json +74 -0
  232. package/messages/en/posts.json +54 -0
  233. package/messages/en/pricing.json +102 -0
  234. package/messages/en/support.json +9 -0
  235. package/messages/en/teams.json +8 -0
  236. package/messages/es/admin.json +219 -0
  237. package/messages/es/aiUsage.json +36 -0
  238. package/messages/es/buttons.json +19 -0
  239. package/messages/es/categories.json +35 -0
  240. package/messages/es/common.json +16 -0
  241. package/messages/es/dev.json +101 -0
  242. package/messages/es/docs.json +27 -0
  243. package/messages/es/entities.json +7 -0
  244. package/messages/es/features.json +119 -0
  245. package/messages/es/footer.json +22 -0
  246. package/messages/es/home.json +57 -0
  247. package/messages/es/index.ts +39 -0
  248. package/messages/es/mobileNav.json +13 -0
  249. package/messages/es/navigation.json +8 -0
  250. package/messages/es/observability.json +74 -0
  251. package/messages/es/posts.json +54 -0
  252. package/messages/es/pricing.json +102 -0
  253. package/messages/es/support.json +9 -0
  254. package/messages/es/teams.json +8 -0
  255. package/messages/fr/admin.json +219 -0
  256. package/messages/fr/aiUsage.json +36 -0
  257. package/messages/fr/buttons.json +19 -0
  258. package/messages/fr/categories.json +35 -0
  259. package/messages/fr/common.json +16 -0
  260. package/messages/fr/dev.json +101 -0
  261. package/messages/fr/docs.json +27 -0
  262. package/messages/fr/entities.json +7 -0
  263. package/messages/fr/features.json +119 -0
  264. package/messages/fr/footer.json +22 -0
  265. package/messages/fr/home.json +57 -0
  266. package/messages/fr/index.ts +39 -0
  267. package/messages/fr/mobileNav.json +13 -0
  268. package/messages/fr/navigation.json +8 -0
  269. package/messages/fr/observability.json +74 -0
  270. package/messages/fr/posts.json +54 -0
  271. package/messages/fr/pricing.json +102 -0
  272. package/messages/fr/support.json +9 -0
  273. package/messages/fr/teams.json +8 -0
  274. package/messages/it/admin.json +219 -0
  275. package/messages/it/aiUsage.json +36 -0
  276. package/messages/it/buttons.json +19 -0
  277. package/messages/it/categories.json +35 -0
  278. package/messages/it/common.json +16 -0
  279. package/messages/it/dev.json +101 -0
  280. package/messages/it/docs.json +27 -0
  281. package/messages/it/entities.json +7 -0
  282. package/messages/it/features.json +119 -0
  283. package/messages/it/footer.json +22 -0
  284. package/messages/it/home.json +57 -0
  285. package/messages/it/index.ts +39 -0
  286. package/messages/it/mobileNav.json +13 -0
  287. package/messages/it/navigation.json +8 -0
  288. package/messages/it/observability.json +74 -0
  289. package/messages/it/posts.json +54 -0
  290. package/messages/it/pricing.json +102 -0
  291. package/messages/it/support.json +9 -0
  292. package/messages/it/teams.json +8 -0
  293. package/messages/pt/admin.json +219 -0
  294. package/messages/pt/aiUsage.json +36 -0
  295. package/messages/pt/buttons.json +19 -0
  296. package/messages/pt/categories.json +35 -0
  297. package/messages/pt/common.json +16 -0
  298. package/messages/pt/dev.json +101 -0
  299. package/messages/pt/docs.json +27 -0
  300. package/messages/pt/entities.json +7 -0
  301. package/messages/pt/features.json +119 -0
  302. package/messages/pt/footer.json +22 -0
  303. package/messages/pt/home.json +57 -0
  304. package/messages/pt/index.ts +39 -0
  305. package/messages/pt/mobileNav.json +13 -0
  306. package/messages/pt/navigation.json +8 -0
  307. package/messages/pt/observability.json +74 -0
  308. package/messages/pt/posts.json +54 -0
  309. package/messages/pt/pricing.json +102 -0
  310. package/messages/pt/support.json +9 -0
  311. package/messages/pt/teams.json +8 -0
  312. package/migrations/089_add_editor_team_role.sql +39 -0
  313. package/migrations/090_demo_users_teams.sql +540 -0
  314. package/migrations/091_greek_teams_billing.sql +523 -0
  315. package/migrations/092_billing_sample_data.sql +774 -0
  316. package/migrations/093_pages_sample_data.sql +1158 -0
  317. package/migrations/094_posts_sample_data.sql +278 -0
  318. package/migrations/095_tasks_sample_data.sql +440 -0
  319. package/migrations/096_customers_sample_data.sql +358 -0
  320. package/migrations/097_scheduled_actions_sample_data.sql +111 -0
  321. package/package.json +22 -0
  322. package/public/docs/desktop-layout-example.png +0 -0
  323. package/styles/components.css +11 -0
  324. package/styles/globals.css +179 -0
  325. package/templates/(public)/blog/[slug]/page.tsx +65 -0
  326. package/templates/(public)/layout.tsx +25 -0
  327. package/templates/(public)/page.tsx +200 -0
  328. package/templates/(public)/support/page.tsx +321 -0
  329. package/templates/dashboard/(main)/agent-multi/page.tsx +63 -0
  330. package/templates/dashboard/(main)/agent-single/page.tsx +142 -0
  331. package/templates/dashboard/(main)/settings/ai-usage/page.tsx +157 -0
  332. package/templates/superadmin/ai-observability/[traceId]/page.tsx +27 -0
  333. package/templates/superadmin/ai-observability/page.tsx +17 -0
@@ -0,0 +1,575 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useRef, type ReactNode } from 'react'
4
+ import { Card } from '@nextsparkjs/core/components/ui/card'
5
+ import { Button } from '@nextsparkjs/core/components/ui/button'
6
+ import { ScrollArea } from '@nextsparkjs/core/components/ui/scroll-area'
7
+ import { MessageInput } from './MessageInput'
8
+ import { MarkdownRenderer } from './MarkdownRenderer'
9
+ import { TypingIndicator } from './TypingIndicator'
10
+ import { ConversationSidebar } from './ConversationSidebar'
11
+ import { createCyId } from '@nextsparkjs/core/lib/testing-utils'
12
+ import { cn } from '@nextsparkjs/core/lib/utils'
13
+ import { Bot, User, Loader2, Trash2, ListTodo, Users, FileText, Sparkles, X } from 'lucide-react'
14
+ import type { ConversationInfo } from '../../lib/hooks/useConversations'
15
+ import type { LucideIcon } from 'lucide-react'
16
+
17
+ // ============================================================================
18
+ // TYPES
19
+ // ============================================================================
20
+
21
+ /**
22
+ * Agent badge configuration for multi-agent support
23
+ */
24
+ export interface AgentBadge {
25
+ label: string
26
+ icon: LucideIcon
27
+ className: string
28
+ }
29
+
30
+ /**
31
+ * Agent badge registry - maps agent names to their visual representation
32
+ */
33
+ export type AgentBadgeRegistry = Record<string, AgentBadge>
34
+
35
+ /**
36
+ * Message with optional agent information
37
+ */
38
+ export interface ChatMessage {
39
+ id: string
40
+ role: 'user' | 'assistant'
41
+ content: string
42
+ timestamp?: number
43
+ /** Agent that handled this message (for multi-agent mode) */
44
+ agentUsed?: string
45
+ }
46
+
47
+ /**
48
+ * Sidebar configuration for conversation management
49
+ */
50
+ export interface SidebarConfig {
51
+ conversations: ConversationInfo[]
52
+ activeSessionId: string | null
53
+ onSelect: (sessionId: string) => void
54
+ onNew: () => Promise<void>
55
+ onRename: (sessionId: string, name: string) => Promise<void>
56
+ onDelete: (sessionId: string) => Promise<void>
57
+ onTogglePin: (sessionId: string) => Promise<void>
58
+ isLoading: boolean
59
+ isCreating: boolean
60
+ canCreateNew: boolean
61
+ conversationCount: number
62
+ maxConversations: number
63
+ }
64
+
65
+ /**
66
+ * Header configuration
67
+ */
68
+ export interface HeaderConfig {
69
+ title: string
70
+ subtitle?: string
71
+ icon?: LucideIcon
72
+ iconClassName?: string
73
+ /** Show clear chat button */
74
+ showClearButton?: boolean
75
+ onClear?: () => void
76
+ /** Additional header content (e.g., agent legend) */
77
+ extra?: ReactNode
78
+ /** Mobile action button */
79
+ mobileAction?: ReactNode
80
+ }
81
+
82
+ /**
83
+ * Empty state configuration
84
+ */
85
+ export interface EmptyStateConfig {
86
+ icon?: LucideIcon
87
+ title: string
88
+ description?: string
89
+ suggestions?: string[]
90
+ action?: ReactNode
91
+ }
92
+
93
+ /**
94
+ * ChatPanel props
95
+ */
96
+ export interface ChatPanelProps {
97
+ /** Unique identifier for testing */
98
+ cyPrefix?: string
99
+ /** Messages to display */
100
+ messages: ChatMessage[]
101
+ /** Current input value */
102
+ input: string
103
+ /** Input change handler */
104
+ onInputChange: (value: string) => void
105
+ /** Send message handler */
106
+ onSend: () => void
107
+ /** Loading state (sending message) */
108
+ isLoading?: boolean
109
+ /** Error message */
110
+ error?: string | null
111
+ /** Header configuration */
112
+ header: HeaderConfig
113
+ /** Sidebar configuration (optional - enables conversation management) */
114
+ sidebar?: SidebarConfig
115
+ /** Agent badge registry (optional - enables multi-agent mode) */
116
+ agentBadges?: AgentBadgeRegistry
117
+ /** Show agent legend in header */
118
+ showAgentLegend?: boolean
119
+ /** Empty state when no messages */
120
+ emptyState?: EmptyStateConfig
121
+ /** Empty state when no session selected (sidebar mode) */
122
+ noSessionState?: EmptyStateConfig
123
+ /** Session ID for debugging */
124
+ sessionId?: string | null
125
+ /** Show session debug info in dev mode */
126
+ showSessionDebug?: boolean
127
+ /** Custom max width class */
128
+ maxWidthClass?: string
129
+ /** Component is ready */
130
+ isReady?: boolean
131
+ /** Loading history */
132
+ isLoadingHistory?: boolean
133
+ /** Has active session (for sidebar mode) */
134
+ hasSession?: boolean
135
+ /** Partial content during streaming */
136
+ streamingContent?: string
137
+ /** Whether currently streaming */
138
+ isStreamingMessage?: boolean
139
+ /** Cancel streaming callback */
140
+ onCancelStream?: () => void
141
+ }
142
+
143
+ // ============================================================================
144
+ // SUB-COMPONENTS
145
+ // ============================================================================
146
+
147
+ /**
148
+ * Single chat message with optional agent badge
149
+ */
150
+ function ChatMessageItem({
151
+ message,
152
+ agentBadges,
153
+ cyPrefix = 'chat',
154
+ }: {
155
+ message: ChatMessage
156
+ agentBadges?: AgentBadgeRegistry
157
+ cyPrefix?: string
158
+ }) {
159
+ const isUser = message.role === 'user'
160
+ const agentBadge = !isUser && message.agentUsed && agentBadges
161
+ ? agentBadges[message.agentUsed]
162
+ : null
163
+
164
+ return (
165
+ <div
166
+ data-cy={createCyId(cyPrefix, `message-${message.role}`)}
167
+ className={cn(
168
+ 'flex w-full gap-3 p-4',
169
+ isUser ? 'flex-row-reverse' : 'flex-row'
170
+ )}
171
+ >
172
+ <div
173
+ className={cn(
174
+ 'flex h-8 w-8 shrink-0 items-center justify-center rounded-full border',
175
+ isUser ? 'bg-primary text-primary-foreground' : 'bg-muted'
176
+ )}
177
+ >
178
+ {isUser ? <User className="h-4 w-4" /> : <Bot className="h-4 w-4" />}
179
+ </div>
180
+ <div className={cn('flex max-w-[80%] flex-col gap-1')}>
181
+ {agentBadge && (
182
+ <div className={cn(
183
+ 'inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-medium w-fit',
184
+ agentBadge.className
185
+ )}>
186
+ <agentBadge.icon className="h-3 w-3" />
187
+ {agentBadge.label}
188
+ </div>
189
+ )}
190
+ <div
191
+ className={cn(
192
+ 'rounded-lg px-4 py-2 text-sm',
193
+ isUser
194
+ ? 'bg-primary text-primary-foreground'
195
+ : 'bg-muted'
196
+ )}
197
+ >
198
+ <MarkdownRenderer content={message.content} />
199
+ </div>
200
+ </div>
201
+ </div>
202
+ )
203
+ }
204
+
205
+ /**
206
+ * Agent legend display
207
+ */
208
+ function AgentLegend({ badges }: { badges: AgentBadgeRegistry }) {
209
+ return (
210
+ <div className="mt-3 flex flex-wrap gap-2 text-xs">
211
+ {Object.entries(badges).map(([key, badge]) => (
212
+ <span
213
+ key={key}
214
+ className={cn(
215
+ 'px-2 py-1 rounded flex items-center gap-1',
216
+ badge.className
217
+ )}
218
+ >
219
+ <badge.icon className="h-3 w-3" />
220
+ {badge.label}
221
+ </span>
222
+ ))}
223
+ </div>
224
+ )
225
+ }
226
+
227
+ /**
228
+ * Empty state display
229
+ */
230
+ function EmptyState({ config, icon: DefaultIcon = Bot }: { config: EmptyStateConfig; icon?: LucideIcon }) {
231
+ const Icon = config.icon || DefaultIcon
232
+ return (
233
+ <div className="flex-1 flex items-center justify-center p-8">
234
+ <div className="text-center max-w-md">
235
+ <Icon className="h-16 w-16 mx-auto mb-4 text-muted-foreground/50" />
236
+ <h2 className="text-xl font-semibold mb-2">{config.title}</h2>
237
+ {config.description && (
238
+ <p className="text-muted-foreground mb-6">{config.description}</p>
239
+ )}
240
+ {config.suggestions && config.suggestions.length > 0 && (
241
+ <div className="space-y-2 text-sm text-left">
242
+ <p className="text-muted-foreground">Try asking:</p>
243
+ {config.suggestions.map((suggestion, i) => (
244
+ <p key={i} className="bg-muted p-2 rounded">
245
+ &quot;{suggestion}&quot;
246
+ </p>
247
+ ))}
248
+ </div>
249
+ )}
250
+ {config.action}
251
+ </div>
252
+ </div>
253
+ )
254
+ }
255
+
256
+ /**
257
+ * Loading state display
258
+ */
259
+ function LoadingState({ message = 'Loading...' }: { message?: string }) {
260
+ return (
261
+ <div className="flex-1 flex items-center justify-center">
262
+ <div className="text-center">
263
+ <Loader2 className="h-8 w-8 animate-spin mx-auto text-muted-foreground" />
264
+ <p className="mt-2 text-sm text-muted-foreground">{message}</p>
265
+ </div>
266
+ </div>
267
+ )
268
+ }
269
+
270
+ // ============================================================================
271
+ // MAIN COMPONENT
272
+ // ============================================================================
273
+
274
+ /**
275
+ * ChatPanel - Unified chat interface component
276
+ *
277
+ * A flexible chat panel that supports:
278
+ * - Optional sidebar for conversation management
279
+ * - Optional multi-agent badges
280
+ * - Customizable header, empty states, and styling
281
+ *
282
+ * @example
283
+ * // Simple chat (like orchestrator)
284
+ * <ChatPanel
285
+ * messages={messages}
286
+ * input={input}
287
+ * onInputChange={setInput}
288
+ * onSend={sendMessage}
289
+ * isLoading={isLoading}
290
+ * header={{ title: 'AI Chat', subtitle: 'Ask me anything' }}
291
+ * />
292
+ *
293
+ * @example
294
+ * // With sidebar (like single-agent)
295
+ * <ChatPanel
296
+ * messages={messages}
297
+ * input={input}
298
+ * onInputChange={setInput}
299
+ * onSend={sendMessage}
300
+ * sidebar={sidebarConfig}
301
+ * header={{ title: 'AI Assistant' }}
302
+ * />
303
+ *
304
+ * @example
305
+ * // With multi-agent badges
306
+ * <ChatPanel
307
+ * messages={messages}
308
+ * agentBadges={AGENT_BADGES}
309
+ * showAgentLegend
310
+ * header={{ title: 'Orchestrator' }}
311
+ * />
312
+ */
313
+ export function ChatPanel({
314
+ cyPrefix = 'chat',
315
+ messages,
316
+ input,
317
+ onInputChange,
318
+ onSend,
319
+ isLoading = false,
320
+ error,
321
+ header,
322
+ sidebar,
323
+ agentBadges,
324
+ showAgentLegend = false,
325
+ emptyState,
326
+ noSessionState,
327
+ sessionId,
328
+ showSessionDebug = false,
329
+ maxWidthClass = 'max-w-4xl',
330
+ isReady = true,
331
+ isLoadingHistory = false,
332
+ hasSession = true,
333
+ streamingContent,
334
+ isStreamingMessage = false,
335
+ onCancelStream,
336
+ }: ChatPanelProps) {
337
+ const scrollRef = useRef<HTMLDivElement>(null)
338
+ const HeaderIcon = header.icon || Bot
339
+
340
+ // Auto-scroll on new messages and streaming content
341
+ useEffect(() => {
342
+ if (scrollRef.current) {
343
+ scrollRef.current.scrollIntoView({ behavior: 'smooth' })
344
+ }
345
+ }, [messages, isLoading, streamingContent])
346
+
347
+ // Determine layout class based on sidebar presence
348
+ const hasSidebar = !!sidebar
349
+ const layoutClass = hasSidebar ? 'max-w-6xl' : maxWidthClass
350
+
351
+ return (
352
+ <div className="h-[calc(100vh-4rem)] p-4 md:p-6 lg:p-8">
353
+ <Card
354
+ data-cy={createCyId(cyPrefix, 'panel')}
355
+ className={cn(
356
+ 'flex h-full mx-auto overflow-hidden shadow-lg',
357
+ layoutClass
358
+ )}
359
+ >
360
+ {/* Optional Sidebar */}
361
+ {hasSidebar && (
362
+ <div className="w-64 flex-shrink-0 hidden md:block">
363
+ <ConversationSidebar
364
+ conversations={sidebar.conversations}
365
+ activeSessionId={sidebar.activeSessionId}
366
+ onSelect={sidebar.onSelect}
367
+ onNew={sidebar.onNew}
368
+ onRename={sidebar.onRename}
369
+ onDelete={sidebar.onDelete}
370
+ onTogglePin={sidebar.onTogglePin}
371
+ isLoading={sidebar.isLoading}
372
+ isCreating={sidebar.isCreating}
373
+ canCreateNew={sidebar.canCreateNew}
374
+ conversationCount={sidebar.conversationCount}
375
+ maxConversations={sidebar.maxConversations}
376
+ />
377
+ </div>
378
+ )}
379
+
380
+ {/* Chat Area */}
381
+ <div className="flex-1 flex flex-col min-w-0">
382
+ {/* Header */}
383
+ <div className="p-4 border-b bg-muted/50">
384
+ <div className="flex items-center justify-between">
385
+ <div className="flex items-center gap-3">
386
+ <div className={cn(
387
+ 'flex h-10 w-10 items-center justify-center rounded-full',
388
+ header.iconClassName || 'bg-primary/10'
389
+ )}>
390
+ <HeaderIcon className={cn(
391
+ 'h-5 w-5',
392
+ header.iconClassName ? '' : 'text-primary'
393
+ )} />
394
+ </div>
395
+ <div>
396
+ <h1 className="text-lg font-semibold flex items-center gap-2">
397
+ {header.title}
398
+ </h1>
399
+ {header.subtitle && (
400
+ <p className="text-sm text-muted-foreground">
401
+ {header.subtitle}
402
+ </p>
403
+ )}
404
+ </div>
405
+ </div>
406
+
407
+ <div className="flex items-center gap-2">
408
+ {/* Clear button */}
409
+ {header.showClearButton && header.onClear && (
410
+ <Button
411
+ data-cy={createCyId(cyPrefix, 'clear-btn')}
412
+ variant="ghost"
413
+ size="sm"
414
+ onClick={header.onClear}
415
+ disabled={messages.length === 0}
416
+ title="Clear chat"
417
+ >
418
+ <Trash2 className="h-4 w-4" />
419
+ </Button>
420
+ )}
421
+
422
+ {/* Mobile action (e.g., new conversation button) */}
423
+ {header.mobileAction && (
424
+ <div className="md:hidden">
425
+ {header.mobileAction}
426
+ </div>
427
+ )}
428
+ </div>
429
+ </div>
430
+
431
+ {/* Agent Legend */}
432
+ {showAgentLegend && agentBadges && (
433
+ <AgentLegend badges={agentBadges} />
434
+ )}
435
+
436
+ {/* Extra header content */}
437
+ {header.extra}
438
+
439
+ {/* Session Debug */}
440
+ {showSessionDebug && sessionId && process.env.NODE_ENV === 'development' && (
441
+ <div className="mt-2 text-xs text-muted-foreground">
442
+ Session: <code className="bg-muted px-1 rounded">
443
+ {sessionId.length > 16 ? `${sessionId.slice(0, 8)}...` : sessionId}
444
+ </code>
445
+ </div>
446
+ )}
447
+ </div>
448
+
449
+ {/* Content Area */}
450
+ {!isReady ? (
451
+ <LoadingState />
452
+ ) : hasSidebar && !hasSession ? (
453
+ /* No session selected (sidebar mode) */
454
+ noSessionState ? (
455
+ <EmptyState config={noSessionState} />
456
+ ) : (
457
+ <LoadingState message="Select a conversation" />
458
+ )
459
+ ) : isLoadingHistory && messages.length === 0 ? (
460
+ <LoadingState message="Loading conversation..." />
461
+ ) : messages.length === 0 && !isLoading ? (
462
+ /* Empty state */
463
+ emptyState ? (
464
+ <EmptyState config={emptyState} />
465
+ ) : (
466
+ <div className="flex-1 flex items-center justify-center p-8">
467
+ <p className="text-muted-foreground">Start a conversation</p>
468
+ </div>
469
+ )
470
+ ) : (
471
+ /* Messages */
472
+ <ScrollArea
473
+ data-cy={createCyId(cyPrefix, 'message-list')}
474
+ className="flex-1 p-4"
475
+ >
476
+ <div className="flex flex-col gap-4">
477
+ {error && (
478
+ <div
479
+ data-cy={createCyId(cyPrefix, 'error-message')}
480
+ className="p-4 mb-2 text-sm text-red-600 bg-red-50 dark:bg-red-900/20 dark:text-red-400 rounded-lg"
481
+ >
482
+ {error}
483
+ </div>
484
+ )}
485
+ {messages.map((message) => (
486
+ <ChatMessageItem
487
+ key={message.id}
488
+ message={message}
489
+ agentBadges={agentBadges}
490
+ cyPrefix={cyPrefix}
491
+ />
492
+ ))}
493
+ {isLoading && (
494
+ <div className="flex items-center gap-2 p-4">
495
+ {isStreamingMessage && streamingContent ? (
496
+ // Show streaming content with blinking cursor
497
+ <div className="flex w-full gap-3">
498
+ <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full border bg-muted">
499
+ <Bot className="h-4 w-4" />
500
+ </div>
501
+ <div className="flex max-w-[80%] flex-col gap-1">
502
+ <div className="rounded-lg px-4 py-2 text-sm bg-muted">
503
+ <MarkdownRenderer content={streamingContent} />
504
+ <span className="inline-block w-2 h-4 ml-1 bg-foreground animate-pulse">|</span>
505
+ </div>
506
+ </div>
507
+ </div>
508
+ ) : (
509
+ <TypingIndicator />
510
+ )}
511
+ </div>
512
+ )}
513
+ {/* Cancel button during streaming */}
514
+ {isStreamingMessage && onCancelStream && (
515
+ <div className="flex justify-center py-2">
516
+ <Button
517
+ variant="outline"
518
+ size="sm"
519
+ onClick={onCancelStream}
520
+ data-cy="chat-cancel-button"
521
+ >
522
+ <X className="h-4 w-4 mr-1" />
523
+ Stop generating
524
+ </Button>
525
+ </div>
526
+ )}
527
+ <div ref={scrollRef} />
528
+ </div>
529
+ </ScrollArea>
530
+ )}
531
+
532
+ {/* Input - always show if ready and has session */}
533
+ {isReady && (hasSidebar ? hasSession : true) && (
534
+ <MessageInput
535
+ value={input}
536
+ onChange={onInputChange}
537
+ onSend={onSend}
538
+ isLoading={isLoading}
539
+ />
540
+ )}
541
+ </div>
542
+ </Card>
543
+ </div>
544
+ )
545
+ }
546
+
547
+ // ============================================================================
548
+ // PRESETS
549
+ // ============================================================================
550
+
551
+ /**
552
+ * Default agent badges for orchestrator mode
553
+ */
554
+ export const ORCHESTRATOR_AGENT_BADGES: AgentBadgeRegistry = {
555
+ task: {
556
+ label: 'Task Agent',
557
+ icon: ListTodo,
558
+ className: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400'
559
+ },
560
+ customer: {
561
+ label: 'Customer Agent',
562
+ icon: Users,
563
+ className: 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400'
564
+ },
565
+ page: {
566
+ label: 'Page Agent',
567
+ icon: FileText,
568
+ className: 'bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400'
569
+ },
570
+ orchestrator: {
571
+ label: 'Orchestrator',
572
+ icon: Sparkles,
573
+ className: 'bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400'
574
+ },
575
+ }