@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,315 @@
1
+ 'use client'
2
+
3
+ import { useState, useCallback, useEffect, useRef } from 'react'
4
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
5
+ import { useTeamContext } from '@nextsparkjs/core/contexts/TeamContext'
6
+ import { authClient } from '@nextsparkjs/core/lib/auth-client'
7
+
8
+ export interface Message {
9
+ id: string
10
+ role: 'user' | 'assistant'
11
+ content: string
12
+ timestamp: number
13
+ }
14
+
15
+ interface ChatResponse {
16
+ success: boolean
17
+ data?: {
18
+ message: string
19
+ sessionId: string
20
+ isNewSession?: boolean
21
+ }
22
+ error?: string
23
+ }
24
+
25
+ interface HistoryResponse {
26
+ success: boolean
27
+ data?: {
28
+ sessionId: string
29
+ name: string | null
30
+ messageCount: number
31
+ isPinned: boolean
32
+ messages: Message[]
33
+ }
34
+ error?: string
35
+ }
36
+
37
+ interface ClearResponse {
38
+ success: boolean
39
+ message?: string
40
+ error?: string
41
+ }
42
+
43
+ const STORAGE_KEY = 'persistent-chat-messages'
44
+ const CONVERSATIONS_QUERY_KEY = 'conversations'
45
+
46
+ /**
47
+ * Persistent Chat Hook
48
+ *
49
+ * Manages chat messages for a specific conversation session.
50
+ * If sessionId is provided, uses that session.
51
+ * If not provided, creates a new session on first message.
52
+ *
53
+ * Features:
54
+ * - Loads message history from server when switching sessions
55
+ * - Caches messages in localStorage for quick UI
56
+ * - Supports multiple conversations
57
+ */
58
+ export function usePersistentChat(externalSessionId?: string | null) {
59
+ const [messages, setMessages] = useState<Message[]>([])
60
+ const [input, setInput] = useState('')
61
+ const [error, setError] = useState<string | null>(null)
62
+ const [isInitialized, setIsInitialized] = useState(false)
63
+ const [isLoadingHistory, setIsLoadingHistory] = useState(false)
64
+ const lastLoadedSessionRef = useRef<string | null>(null)
65
+ const { currentTeam } = useTeamContext()
66
+ const queryClient = useQueryClient()
67
+
68
+ // Get current user session
69
+ const { data: session } = useQuery({
70
+ queryKey: ['session'],
71
+ queryFn: async () => {
72
+ const { data } = await authClient.getSession()
73
+ return data
74
+ },
75
+ staleTime: 1000 * 60 * 5, // 5 minutes
76
+ })
77
+
78
+ const userId = session?.user?.id
79
+ const teamId = currentTeam?.id
80
+
81
+ // Use external session ID if provided
82
+ const sessionId = externalSessionId || undefined
83
+
84
+ // Storage key is specific to the session
85
+ const storageKey = sessionId ? `${STORAGE_KEY}-${sessionId}` : null
86
+
87
+ // Load messages from localStorage when session changes
88
+ useEffect(() => {
89
+ if (!userId || !sessionId) {
90
+ setMessages([])
91
+ setIsInitialized(true)
92
+ return
93
+ }
94
+
95
+ // Already loaded this session
96
+ if (lastLoadedSessionRef.current === sessionId) {
97
+ return
98
+ }
99
+
100
+ // Try to load from localStorage first for instant UI
101
+ if (storageKey) {
102
+ try {
103
+ const stored = localStorage.getItem(storageKey)
104
+ if (stored) {
105
+ const parsed = JSON.parse(stored) as Message[]
106
+ setMessages(parsed)
107
+ } else {
108
+ setMessages([])
109
+ }
110
+ } catch {
111
+ setMessages([])
112
+ }
113
+ }
114
+
115
+ setIsInitialized(true)
116
+ lastLoadedSessionRef.current = sessionId
117
+
118
+ // Then fetch from server to get the authoritative history
119
+ if (teamId) {
120
+ fetchHistory(sessionId)
121
+ }
122
+ }, [userId, sessionId, storageKey, teamId])
123
+
124
+ // Fetch message history from server
125
+ const fetchHistory = async (sid: string) => {
126
+ if (!teamId) return
127
+
128
+ setIsLoadingHistory(true)
129
+ try {
130
+ const headers: Record<string, string> = {
131
+ 'Content-Type': 'application/json',
132
+ 'x-team-id': teamId,
133
+ }
134
+
135
+ const response = await fetch(
136
+ `/api/v1/theme/default/ai/single-agent?sessionId=${encodeURIComponent(sid)}`,
137
+ { method: 'GET', headers }
138
+ )
139
+
140
+ if (response.ok) {
141
+ const data: HistoryResponse = await response.json()
142
+ if (data.success && data.data?.messages) {
143
+ setMessages(data.data.messages)
144
+ // Update localStorage
145
+ if (storageKey) {
146
+ try {
147
+ localStorage.setItem(storageKey, JSON.stringify(data.data.messages))
148
+ } catch {
149
+ // Ignore
150
+ }
151
+ }
152
+ }
153
+ }
154
+ } catch {
155
+ // Ignore fetch errors, we have localStorage data
156
+ } finally {
157
+ setIsLoadingHistory(false)
158
+ }
159
+ }
160
+
161
+ // Save messages to localStorage when they change
162
+ useEffect(() => {
163
+ if (!storageKey || !isInitialized) return
164
+
165
+ try {
166
+ localStorage.setItem(storageKey, JSON.stringify(messages))
167
+ } catch {
168
+ // Ignore storage errors (quota exceeded, etc.)
169
+ }
170
+ }, [messages, storageKey, isInitialized])
171
+
172
+ const sendMutation = useMutation({
173
+ mutationFn: async (message: string): Promise<ChatResponse> => {
174
+ const headers: Record<string, string> = {
175
+ 'Content-Type': 'application/json',
176
+ }
177
+
178
+ if (teamId) {
179
+ headers['x-team-id'] = teamId
180
+ }
181
+
182
+ const response = await fetch('/api/v1/theme/default/ai/single-agent', {
183
+ method: 'POST',
184
+ headers,
185
+ body: JSON.stringify({ message, sessionId }),
186
+ })
187
+
188
+ if (!response.ok) {
189
+ const errorData = await response.json().catch(() => ({}))
190
+ throw new Error(errorData.error || 'Failed to send message')
191
+ }
192
+
193
+ return response.json()
194
+ },
195
+ onMutate: (message) => {
196
+ setError(null)
197
+ // Optimistic update - add user message immediately
198
+ const userMessage: Message = {
199
+ id: crypto.randomUUID(),
200
+ role: 'user',
201
+ content: message,
202
+ timestamp: Date.now(),
203
+ }
204
+ setMessages((prev) => [...prev, userMessage])
205
+ setInput('')
206
+ },
207
+ onSuccess: (data) => {
208
+ if (data.success && data.data) {
209
+ const aiMessage: Message = {
210
+ id: crypto.randomUUID(),
211
+ role: 'assistant',
212
+ content: data.data.message,
213
+ timestamp: Date.now(),
214
+ }
215
+ setMessages((prev) => [...prev, aiMessage])
216
+
217
+ // If a new session was created, invalidate conversations list
218
+ if (data.data.isNewSession) {
219
+ queryClient.invalidateQueries({ queryKey: [CONVERSATIONS_QUERY_KEY] })
220
+ }
221
+ } else if (data.error) {
222
+ setError(data.error)
223
+ }
224
+ },
225
+ onError: (err: Error) => {
226
+ setError(err.message)
227
+ // Remove the optimistic user message on error
228
+ setMessages((prev) => prev.slice(0, -1))
229
+ },
230
+ })
231
+
232
+ const clearMutation = useMutation({
233
+ mutationFn: async (): Promise<ClearResponse> => {
234
+ if (!sessionId) {
235
+ throw new Error('No session to clear')
236
+ }
237
+
238
+ const headers: Record<string, string> = {
239
+ 'Content-Type': 'application/json',
240
+ }
241
+
242
+ if (teamId) {
243
+ headers['x-team-id'] = teamId
244
+ }
245
+
246
+ const response = await fetch('/api/v1/theme/default/ai/single-agent', {
247
+ method: 'DELETE',
248
+ headers,
249
+ body: JSON.stringify({ sessionId }),
250
+ })
251
+
252
+ if (!response.ok) {
253
+ const errorData = await response.json().catch(() => ({}))
254
+ throw new Error(errorData.error || 'Failed to clear session')
255
+ }
256
+
257
+ return response.json()
258
+ },
259
+ onSuccess: () => {
260
+ // Clear local messages
261
+ setMessages([])
262
+ setError(null)
263
+ setInput('')
264
+
265
+ // Clear localStorage for this session
266
+ if (storageKey) {
267
+ try {
268
+ localStorage.removeItem(storageKey)
269
+ } catch {
270
+ // Ignore
271
+ }
272
+ }
273
+
274
+ // Invalidate related queries
275
+ queryClient.invalidateQueries({ queryKey: ['chat-history'] })
276
+ queryClient.invalidateQueries({ queryKey: [CONVERSATIONS_QUERY_KEY] })
277
+ },
278
+ onError: (err: Error) => {
279
+ setError(`Failed to clear conversation: ${err.message}`)
280
+ },
281
+ })
282
+
283
+ const sendMessage = useCallback(() => {
284
+ if (!input.trim() || sendMutation.isPending) return
285
+ sendMutation.mutate(input)
286
+ }, [input, sendMutation])
287
+
288
+ const clearConversation = useCallback(() => {
289
+ if (clearMutation.isPending) return
290
+ clearMutation.mutate()
291
+ }, [clearMutation])
292
+
293
+ // Reload history from server
294
+ const reloadHistory = useCallback(() => {
295
+ if (sessionId && teamId) {
296
+ fetchHistory(sessionId)
297
+ }
298
+ }, [sessionId, teamId])
299
+
300
+ return {
301
+ messages,
302
+ input,
303
+ setInput,
304
+ error,
305
+ isLoading: sendMutation.isPending,
306
+ isClearing: clearMutation.isPending,
307
+ isLoadingHistory,
308
+ sendMessage,
309
+ clearConversation,
310
+ reloadHistory,
311
+ sessionId,
312
+ isReady: isInitialized && !!userId,
313
+ hasSession: !!sessionId,
314
+ }
315
+ }
@@ -0,0 +1,127 @@
1
+ 'use client'
2
+
3
+ import { useState, useCallback, useRef } from 'react'
4
+ import { useTeamContext } from '@nextsparkjs/core/contexts/TeamContext'
5
+
6
+ interface StreamChunk {
7
+ type: 'token' | 'done' | 'error' | 'tool_start' | 'tool_end'
8
+ content?: string
9
+ fullContent?: string
10
+ agentUsed?: string
11
+ tokenUsage?: {
12
+ inputTokens: number
13
+ outputTokens: number
14
+ totalTokens: number
15
+ }
16
+ error?: string
17
+ toolName?: string
18
+ result?: unknown
19
+ }
20
+
21
+ interface UseStreamingChatOptions {
22
+ agentName: string
23
+ sessionId?: string
24
+ onToken?: (token: string) => void
25
+ onComplete?: (fullContent: string) => void
26
+ onError?: (error: string) => void
27
+ }
28
+
29
+ export function useStreamingChat(options: UseStreamingChatOptions) {
30
+ const { agentName, sessionId, onToken, onComplete, onError } = options
31
+ const { currentTeam } = useTeamContext()
32
+
33
+ const [isStreaming, setIsStreaming] = useState(false)
34
+ const [partialContent, setPartialContent] = useState('')
35
+ const [error, setError] = useState<string | null>(null)
36
+ const abortControllerRef = useRef<AbortController | null>(null)
37
+
38
+ const streamMessage = useCallback(async (message: string) => {
39
+ setIsStreaming(true)
40
+ setPartialContent('')
41
+ setError(null)
42
+
43
+ abortControllerRef.current = new AbortController()
44
+
45
+ try {
46
+ const headers: Record<string, string> = {
47
+ 'Content-Type': 'application/json'
48
+ }
49
+
50
+ // Include team context
51
+ if (currentTeam?.id) {
52
+ headers['x-team-id'] = currentTeam.id
53
+ }
54
+
55
+ const response = await fetch('/api/ai/chat/stream', {
56
+ method: 'POST',
57
+ headers,
58
+ body: JSON.stringify({ message, agentName, sessionId }),
59
+ signal: abortControllerRef.current.signal,
60
+ })
61
+
62
+ if (!response.ok) {
63
+ throw new Error(`HTTP ${response.status}`)
64
+ }
65
+
66
+ const reader = response.body?.getReader()
67
+ if (!reader) throw new Error('No response body')
68
+
69
+ const decoder = new TextDecoder()
70
+ let fullContent = ''
71
+
72
+ while (true) {
73
+ const { done, value } = await reader.read()
74
+ if (done) break
75
+
76
+ const text = decoder.decode(value)
77
+ const lines = text.split('\n')
78
+
79
+ for (const line of lines) {
80
+ if (line.startsWith('data: ')) {
81
+ const data = line.slice(6)
82
+ if (data === '[DONE]') continue
83
+
84
+ try {
85
+ const chunk: StreamChunk = JSON.parse(data)
86
+
87
+ if (chunk.type === 'token' && chunk.content) {
88
+ fullContent += chunk.content
89
+ setPartialContent(fullContent)
90
+ onToken?.(chunk.content)
91
+ } else if (chunk.type === 'done' && chunk.fullContent) {
92
+ onComplete?.(chunk.fullContent)
93
+ } else if (chunk.type === 'error' && chunk.error) {
94
+ setError(chunk.error)
95
+ onError?.(chunk.error)
96
+ }
97
+ } catch {
98
+ // Ignore parse errors
99
+ }
100
+ }
101
+ }
102
+ }
103
+ } catch (err) {
104
+ if (err instanceof Error && err.name !== 'AbortError') {
105
+ const message = err.message
106
+ setError(message)
107
+ onError?.(message)
108
+ }
109
+ } finally {
110
+ setIsStreaming(false)
111
+ abortControllerRef.current = null
112
+ }
113
+ }, [agentName, sessionId, currentTeam?.id, onToken, onComplete, onError])
114
+
115
+ const cancel = useCallback(() => {
116
+ abortControllerRef.current?.abort()
117
+ setIsStreaming(false)
118
+ }, [])
119
+
120
+ return {
121
+ streamMessage,
122
+ isStreaming,
123
+ partialContent,
124
+ error,
125
+ cancel,
126
+ }
127
+ }
@@ -0,0 +1,63 @@
1
+ 'use client'
2
+
3
+ import { useQuery } from '@tanstack/react-query'
4
+ import { useTeamContext } from '@nextsparkjs/core/contexts/TeamContext'
5
+
6
+ type Period = 'today' | '7d' | '30d' | 'all'
7
+
8
+ interface UsageStats {
9
+ totalTokens: number
10
+ totalCost: number
11
+ inputTokens: number
12
+ outputTokens: number
13
+ requestCount: number
14
+ byModel: Record<string, { tokens: number; cost: number }>
15
+ }
16
+
17
+ interface DailyUsage {
18
+ date: string
19
+ tokens: number
20
+ cost: number
21
+ requests: number
22
+ }
23
+
24
+ interface UsageResponse {
25
+ success: boolean
26
+ data: {
27
+ stats: UsageStats
28
+ daily: DailyUsage[]
29
+ period: Period
30
+ type: 'user' | 'team'
31
+ }
32
+ error?: string
33
+ }
34
+
35
+ export function useTokenUsage(period: Period = '30d', type: 'user' | 'team' = 'user') {
36
+ const { currentTeam } = useTeamContext()
37
+
38
+ return useQuery<UsageResponse>({
39
+ queryKey: ['ai-usage', period, type],
40
+ queryFn: async () => {
41
+ const headers: Record<string, string> = {
42
+ 'Content-Type': 'application/json'
43
+ }
44
+
45
+ // Include team context
46
+ if (currentTeam?.id) {
47
+ headers['x-team-id'] = currentTeam.id
48
+ }
49
+
50
+ const response = await fetch(`/api/v1/theme/default/ai/usage?period=${period}&type=${type}`, {
51
+ headers
52
+ })
53
+
54
+ if (!response.ok) {
55
+ const errorData = await response.json().catch(() => ({}))
56
+ throw new Error(errorData.error || 'Failed to fetch usage')
57
+ }
58
+
59
+ return response.json()
60
+ },
61
+ enabled: !!currentTeam?.id,
62
+ })
63
+ }
@@ -0,0 +1,69 @@
1
+ You are a customer management AI assistant for the Boilerplate application.
2
+
3
+ ## CRITICAL RULE - MUST FOLLOW
4
+
5
+ **YOU MUST ALWAYS USE TOOLS TO GET DATA. NEVER FABRICATE OR IMAGINE CUSTOMER INFORMATION.**
6
+
7
+ Before responding with ANY customer information, you MUST:
8
+ 1. Call the appropriate tool (list_customers, search_customers, get_customer)
9
+ 2. Wait for the tool result
10
+ 3. ONLY THEN respond based on the REAL data from the tool
11
+
12
+ If a tool returns an error or empty results, tell the user honestly - NEVER make up fake customers.
13
+
14
+ ## Your Capabilities
15
+ - List, search, and view customer details (using tools)
16
+ - Create new customers with all their information
17
+ - Update existing customer data
18
+ - Delete customers (with confirmation)
19
+
20
+ ## Customer Fields
21
+ - **name**: Company or customer name (required)
22
+ - **account**: Account number (required, numeric, must be unique)
23
+ - **office**: Office location/branch (required)
24
+ - **phone**: Contact phone number (optional)
25
+ - **salesRep**: Assigned sales representative name (optional)
26
+ - **visitDays**: Days for in-person visits - lun, mar, mie, jue, vie (optional)
27
+ - **contactDays**: Days for phone/email contact - lun, mar, mie, jue, vie (optional)
28
+
29
+ ## Available Tools - USE THEM
30
+
31
+ | Tool | When to use |
32
+ |------|-------------|
33
+ | **list_customers** | User asks to see customers, all clients |
34
+ | **search_customers** | User wants to find specific customers by name, office, account |
35
+ | **get_customer** | User asks about a specific customer by ID |
36
+ | **create_customer** | User wants to create a new customer |
37
+ | **update_customer** | User wants to modify an existing customer |
38
+ | **delete_customer** | User wants to remove a customer |
39
+
40
+ ## Handling Contextual Updates
41
+
42
+ When the user says "modificalo", "cambialo", "actualízalo" (modify it, change it, update it) with new data:
43
+ 1. Look at the conversation history to identify which customer they're referring to
44
+ 2. Get the customer ID from your previous search/get results
45
+ 3. Call update_customer with that ID and the new values
46
+ 4. Confirm the update with a link
47
+
48
+ **Example:**
49
+ - Previous context: You showed StartupXYZ (id: customer-everpoint-002, phone: +1 512 555 0102)
50
+ - User: "modificalo, su nuevo telefono es +1 457 45465245"
51
+ - YOU: Call update_customer with customerId="customer-everpoint-002" and phone="+1 457 45465245"
52
+
53
+ ## Correct Workflow
54
+
55
+ 1. User: "Show me customers from office Central"
56
+ 2. YOU: Call search_customers tool with query "Central"
57
+ 3. Tool returns: [{id: "1", name: "TechStart", account: 1001, office: "Central"}, ...]
58
+ 4. YOU: Format and show the REAL customers from the tool result
59
+
60
+ ## Response Format
61
+ - Use Spanish when the user writes in Spanish, English otherwise
62
+ - After creating or updating a customer, provide a link: [Customer Name](/dashboard/customers/{id})
63
+ - When listing customers, summarize key info: name, office, salesRep
64
+ - Always confirm before deleting a customer
65
+
66
+ ## What NOT to do
67
+ - NEVER respond with example/fake customer data
68
+ - NEVER imagine what customers the user might have
69
+ - NEVER skip calling tools before responding about customers
@@ -0,0 +1,61 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+
4
+ const AGENTS_DIR = path.join(process.cwd(), 'contents/themes/default/lib/langchain/agents')
5
+
6
+ /**
7
+ * Agent names that have .md prompt files in the agents directory.
8
+ */
9
+ export type AgentName = 'orchestrator' | 'task-assistant' | 'customer-assistant' | 'page-assistant' | 'single-agent'
10
+
11
+ /**
12
+ * Load a system prompt from markdown file
13
+ *
14
+ * @param agentName - Name of the agent (without .md extension)
15
+ * @returns The system prompt content as a string
16
+ * @throws Error if the agent prompt file is not found
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const systemPrompt = loadSystemPrompt('task-assistant')
21
+ * const agent = await createAgent({ systemPrompt, ... })
22
+ * ```
23
+ */
24
+ export function loadSystemPrompt(agentName: AgentName): string {
25
+ const filePath = path.join(AGENTS_DIR, `${agentName}.md`)
26
+
27
+ if (!fs.existsSync(filePath)) {
28
+ throw new Error(`Agent prompt not found: ${agentName}. Expected file: ${filePath}`)
29
+ }
30
+
31
+ return fs.readFileSync(filePath, 'utf-8')
32
+ }
33
+
34
+ /**
35
+ * Get list of available agent names
36
+ *
37
+ * Scans the agents directory for .md files and returns their names
38
+ *
39
+ * @returns Array of agent names (without .md extension)
40
+ */
41
+ export function getAvailableAgents(): string[] {
42
+ if (!fs.existsSync(AGENTS_DIR)) {
43
+ return []
44
+ }
45
+
46
+ const files = fs.readdirSync(AGENTS_DIR)
47
+ return files
48
+ .filter(f => f.endsWith('.md'))
49
+ .map(f => f.replace('.md', ''))
50
+ }
51
+
52
+ /**
53
+ * Check if an agent exists
54
+ *
55
+ * @param agentName - Name of the agent to check
56
+ * @returns true if the agent prompt file exists
57
+ */
58
+ export function agentExists(agentName: string): boolean {
59
+ const filePath = path.join(AGENTS_DIR, `${agentName}.md`)
60
+ return fs.existsSync(filePath)
61
+ }
@@ -0,0 +1,59 @@
1
+ You are an AI Orchestrator that routes user requests to specialized agents.
2
+
3
+ ## CRITICAL RULE
4
+
5
+ **YOU CAN ONLY DO TWO THINGS:**
6
+ 1. Call a routing tool (route_to_task, route_to_customer, route_to_page)
7
+ 2. Respond to simple greetings
8
+
9
+ **NEVER claim to perform actions like creating, updating, or deleting data.** You don't have those tools. Only the specialized agents do.
10
+
11
+ ## Your Job
12
+
13
+ 1. Analyze the user's message AND the conversation history
14
+ 2. Decide which agent should handle it
15
+ 3. Call the appropriate routing tool OR respond to greetings only
16
+
17
+ ## Routing Rules
18
+
19
+ **route_to_customer** - Use when:
20
+ - User mentions customers, clients, accounts (cliente, customer, cuenta)
21
+ - User wants to modify, update, or change something about a previously discussed customer
22
+ - User references a customer from earlier in the conversation (e.g., "modificalo", "cambialo", "actualiza su...")
23
+
24
+ **route_to_task** - Use when:
25
+ - User mentions tasks, to-dos, work items (tarea, task, pendiente)
26
+ - User wants to create, update, or list tasks
27
+ - User asks for suggestions to add to a task
28
+
29
+ **route_to_page** - Use when:
30
+ - User mentions pages, content, website (página, page, contenido)
31
+ - User wants to create or modify landing pages, blocks
32
+
33
+ ## Context Awareness
34
+
35
+ **IMPORTANT:** When the user says "modificalo", "cambialo", "actualízalo", "bórralo" (modify it, change it, update it, delete it):
36
+ - Look at the conversation history to determine WHAT they're referring to
37
+ - If you were just discussing a customer → route_to_customer
38
+ - If you were just discussing a task → route_to_task
39
+ - If you were just discussing a page → route_to_page
40
+
41
+ ## Direct Response (ONLY for greetings)
42
+
43
+ Respond directly ONLY for:
44
+ - "Hola" → "¡Hola! ¿En qué puedo ayudarte?"
45
+ - "Hello" → "Hello! How can I help you?"
46
+ - "¿Quién eres?" → "Soy tu asistente para tareas, clientes y páginas."
47
+
48
+ For EVERYTHING else, use a routing tool.
49
+
50
+ ## Examples
51
+
52
+ | User says | Action |
53
+ |-----------|--------|
54
+ | "Hola" | Respond: "¡Hola! ¿En qué puedo ayudarte?" |
55
+ | "Muéstrame mis tareas" | route_to_task |
56
+ | "Para la tarea X, sugiereme recetas" | route_to_task |
57
+ | "Lista de clientes" | route_to_customer |
58
+ | "modificalo, su nuevo telefono es..." | route_to_customer (context: talking about customer) |
59
+ | "Crea una landing page" | route_to_page |