@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,226 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
4
+ import { processMessage } from '@/themes/default/lib/langchain/orchestrator'
5
+ import { dbMemoryStore } from '@/plugins/langchain/lib/db-memory-store'
6
+ import type { ChatMessage } from '@/plugins/langchain/types/langchain.types'
7
+
8
+ // Schema for request validation
9
+ const ChatRequestSchema = z.object({
10
+ message: z.string().min(1).max(2000),
11
+ sessionId: z.string().optional(),
12
+ })
13
+
14
+ // Helper to convert messages to API format
15
+ // Filters out internal messages (tool results, system messages, empty AI messages)
16
+ function convertToApiMessages(
17
+ messages: Awaited<ReturnType<typeof dbMemoryStore.getMessages>>
18
+ ): ChatMessage[] {
19
+ return messages
20
+ .filter((msg) => {
21
+ const type = msg._getType()
22
+
23
+ // Skip tool messages - these are internal tool results (e.g., API responses)
24
+ if (type === 'tool') return false
25
+
26
+ // Skip system messages - these are internal prompts
27
+ if (type === 'system') return false
28
+
29
+ // Skip AI messages with empty content (these are tool_call-only messages)
30
+ if (type === 'ai') {
31
+ const content = msg.content
32
+ if (!content || (typeof content === 'string' && content.trim() === '')) {
33
+ return false
34
+ }
35
+ }
36
+
37
+ return true
38
+ })
39
+ .map((msg, index) => ({
40
+ id: `msg-${index}`,
41
+ role: msg._getType() === 'human' ? 'user' : 'assistant',
42
+ content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content),
43
+ timestamp: Date.now() - (messages.length - index) * 1000,
44
+ }))
45
+ }
46
+
47
+ /**
48
+ * AI Chat Endpoint for Default Theme
49
+ *
50
+ * Uses the Multi-Agent Orchestrator pattern:
51
+ * - Orchestrator analyzes user intent and routes to specialized agents
52
+ * - Sub-agents: task-assistant, customer-assistant, page-assistant
53
+ * - Handles ambiguous requests by asking for clarification
54
+ */
55
+ export async function POST(req: NextRequest) {
56
+ try {
57
+ // 1. Dual authentication (API key or session)
58
+ const authResult = await authenticateRequest(req)
59
+ if (!authResult.success || !authResult.user) {
60
+ return NextResponse.json(
61
+ { success: false, error: 'Unauthorized' },
62
+ { status: 401 }
63
+ )
64
+ }
65
+
66
+ // 2. Team context required for team-scoped operations
67
+ const teamId = req.headers.get('x-team-id')
68
+ if (!teamId) {
69
+ return NextResponse.json(
70
+ { success: false, error: 'Team context required', code: 'TEAM_CONTEXT_REQUIRED' },
71
+ { status: 400 }
72
+ )
73
+ }
74
+
75
+ const userId = authResult.user.id
76
+ const userName = authResult.user.name || 'system'
77
+
78
+ // 3. Parse and Validate Request
79
+ const body = await req.json()
80
+ const result = ChatRequestSchema.safeParse(body)
81
+
82
+ if (!result.success) {
83
+ return NextResponse.json(
84
+ { success: false, error: 'Invalid request', details: result.error.issues },
85
+ { status: 400 }
86
+ )
87
+ }
88
+
89
+ const { message, sessionId: requestedSessionId } = result.data
90
+
91
+ // Generate a session ID if not provided
92
+ const sessionId = requestedSessionId || crypto.randomUUID()
93
+
94
+ // 4. Process through orchestrator (routes to appropriate sub-agent)
95
+ // Uses graph-based orchestrator if LANGCHAIN_USE_GRAPH_ORCHESTRATOR=true
96
+ const response = await processMessage(message, {
97
+ userId,
98
+ teamId,
99
+ sessionId,
100
+ userName,
101
+ })
102
+
103
+ // 5. Return Response
104
+ return NextResponse.json({
105
+ success: true,
106
+ data: {
107
+ message: response.content,
108
+ sessionId: response.sessionId,
109
+ agentUsed: response.agentUsed,
110
+ },
111
+ })
112
+ } catch (error) {
113
+ console.error('AI Chat Error:', error)
114
+ return NextResponse.json(
115
+ { success: false, error: 'Internal Server Error' },
116
+ { status: 500 }
117
+ )
118
+ }
119
+ }
120
+
121
+ /**
122
+ * GET - Retrieve conversation history
123
+ */
124
+ export async function GET(req: NextRequest) {
125
+ const { searchParams } = new URL(req.url)
126
+ const sessionId = searchParams.get('sessionId')
127
+
128
+ // Return API documentation if no sessionId
129
+ if (!sessionId) {
130
+ return NextResponse.json({
131
+ endpoint: '/api/v1/theme/default/ai/orchestrator',
132
+ description: 'Multi-agent orchestrator - routes to specialized sub-agents',
133
+ methods: {
134
+ POST: {
135
+ description: 'Send message to AI assistant',
136
+ body: { message: 'string', sessionId: 'string (optional)' },
137
+ },
138
+ GET: {
139
+ description: 'Get conversation history',
140
+ query: { sessionId: 'string (required)' },
141
+ },
142
+ DELETE: {
143
+ description: 'Clear conversation',
144
+ body: { sessionId: 'string (required)' },
145
+ },
146
+ },
147
+ })
148
+ }
149
+
150
+ // Auth
151
+ const authResult = await authenticateRequest(req)
152
+ if (!authResult.success || !authResult.user) {
153
+ return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
154
+ }
155
+
156
+ const teamId = req.headers.get('x-team-id')
157
+ if (!teamId) {
158
+ return NextResponse.json(
159
+ { success: false, error: 'Team context required' },
160
+ { status: 400 }
161
+ )
162
+ }
163
+
164
+ const context = { userId: authResult.user.id, teamId }
165
+
166
+ try {
167
+ const session = await dbMemoryStore.getSession(sessionId, context)
168
+ if (!session) {
169
+ return NextResponse.json({ success: false, error: 'Conversation not found' }, { status: 404 })
170
+ }
171
+
172
+ const messages = await dbMemoryStore.getMessages(sessionId, context)
173
+
174
+ return NextResponse.json({
175
+ success: true,
176
+ data: {
177
+ sessionId,
178
+ name: session.name,
179
+ messageCount: session.messageCount,
180
+ isPinned: session.isPinned,
181
+ messages: convertToApiMessages(messages),
182
+ },
183
+ })
184
+ } catch (error) {
185
+ console.error('[AI Chat] Get history error:', error)
186
+ return NextResponse.json({ success: false, error: 'Failed to get history' }, { status: 500 })
187
+ }
188
+ }
189
+
190
+ /**
191
+ * DELETE - Clear conversation
192
+ */
193
+ export async function DELETE(req: NextRequest) {
194
+ const authResult = await authenticateRequest(req)
195
+ if (!authResult.success || !authResult.user) {
196
+ return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
197
+ }
198
+
199
+ const teamId = req.headers.get('x-team-id')
200
+ if (!teamId) {
201
+ return NextResponse.json(
202
+ { success: false, error: 'Team context required' },
203
+ { status: 400 }
204
+ )
205
+ }
206
+
207
+ try {
208
+ const body = await req.json().catch(() => ({}))
209
+ const sessionId = body.sessionId
210
+
211
+ if (!sessionId) {
212
+ return NextResponse.json({ success: false, error: 'sessionId required' }, { status: 400 })
213
+ }
214
+
215
+ await dbMemoryStore.clearSession(sessionId, { userId: authResult.user.id, teamId })
216
+
217
+ return NextResponse.json({
218
+ success: true,
219
+ message: 'Conversation cleared',
220
+ sessionId,
221
+ })
222
+ } catch (error) {
223
+ console.error('[AI Chat] Clear error:', error)
224
+ return NextResponse.json({ success: false, error: 'Failed to clear' }, { status: 500 })
225
+ }
226
+ }
@@ -0,0 +1,291 @@
1
+ /**
2
+ * ============================================================================
3
+ * SINGLE AGENT CHAT ENDPOINT
4
+ * ============================================================================
5
+ *
6
+ * A unified agent with access to ALL entity tools (tasks, customers, pages).
7
+ * Use this for simpler interactions where routing overhead is unnecessary.
8
+ *
9
+ * Configuration is centralized in: config/langchain.config.ts
10
+ *
11
+ * ============================================================================
12
+ */
13
+
14
+ import { NextRequest, NextResponse } from 'next/server'
15
+ import { z } from 'zod'
16
+ import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
17
+ import { createAgent } from '@/plugins/langchain/lib/agent-factory'
18
+ import {
19
+ dbMemoryStore,
20
+ CONVERSATION_LIMITS,
21
+ generateSessionId,
22
+ } from '@/plugins/langchain/lib/db-memory-store'
23
+ import {
24
+ getAgentConfig,
25
+ getAgentModelConfig,
26
+ getAgentTools,
27
+ getAgentPromptName,
28
+ } from '@/themes/default/lib/langchain/langchain.config'
29
+ import { loadSystemPrompt } from '@/themes/default/lib/langchain/agents'
30
+ import type { ChatMessage } from '@/plugins/langchain/types/langchain.types'
31
+
32
+ // Agent name - matches key in AGENTS config
33
+ const AGENT_NAME = 'single-agent'
34
+
35
+ // Request validation
36
+ const ChatRequestSchema = z.object({
37
+ message: z.string().min(1).max(2000),
38
+ sessionId: z.string().optional(),
39
+ })
40
+
41
+ // Helper to convert messages to API format
42
+ // Filters out internal messages (tool results, system messages, empty AI messages)
43
+ function convertToApiMessages(
44
+ messages: Awaited<ReturnType<typeof dbMemoryStore.getMessages>>
45
+ ): ChatMessage[] {
46
+ return messages
47
+ .filter((msg) => {
48
+ const type = msg._getType()
49
+
50
+ // Skip tool messages - these are internal tool results (e.g., API responses)
51
+ if (type === 'tool') return false
52
+
53
+ // Skip system messages - these are internal prompts
54
+ if (type === 'system') return false
55
+
56
+ // Skip AI messages with empty content (these are tool_call-only messages)
57
+ if (type === 'ai') {
58
+ const content = msg.content
59
+ if (!content || (typeof content === 'string' && content.trim() === '')) {
60
+ return false
61
+ }
62
+ }
63
+
64
+ return true
65
+ })
66
+ .map((msg, index) => ({
67
+ id: `msg-${index}`,
68
+ role: msg._getType() === 'human' ? 'user' : 'assistant',
69
+ content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content),
70
+ timestamp: Date.now() - (messages.length - index) * 1000,
71
+ }))
72
+ }
73
+
74
+ /**
75
+ * POST - Send message to single agent
76
+ */
77
+ export async function POST(req: NextRequest) {
78
+ try {
79
+ // 1. Authentication
80
+ const authResult = await authenticateRequest(req)
81
+ if (!authResult.success || !authResult.user) {
82
+ return NextResponse.json(
83
+ { success: false, error: 'Unauthorized' },
84
+ { status: 401 }
85
+ )
86
+ }
87
+
88
+ // 2. Team context
89
+ const teamId = req.headers.get('x-team-id')
90
+ if (!teamId) {
91
+ return NextResponse.json(
92
+ { success: false, error: 'Team context required', code: 'TEAM_CONTEXT_REQUIRED' },
93
+ { status: 400 }
94
+ )
95
+ }
96
+
97
+ const userId = authResult.user.id
98
+ const userName = authResult.user.name || 'system'
99
+ const context = { userId, teamId, userName }
100
+
101
+ // 3. Validate request
102
+ const body = await req.json()
103
+ const validation = ChatRequestSchema.safeParse(body)
104
+
105
+ if (!validation.success) {
106
+ return NextResponse.json(
107
+ { success: false, error: 'Invalid request', details: validation.error.issues },
108
+ { status: 400 }
109
+ )
110
+ }
111
+
112
+ const { message, sessionId: requestedSessionId } = validation.data
113
+
114
+ // 4. Session management
115
+ let sessionId: string
116
+ let isNewSession = false
117
+
118
+ if (requestedSessionId) {
119
+ sessionId = requestedSessionId
120
+ const existingSession = await dbMemoryStore.getSession(sessionId, context)
121
+ isNewSession = !existingSession
122
+ } else {
123
+ sessionId = generateSessionId(userId)
124
+ isNewSession = true
125
+ }
126
+
127
+ // 5. Check conversation limits
128
+ if (isNewSession) {
129
+ const currentCount = await dbMemoryStore.countSessions(context)
130
+
131
+ if (currentCount >= CONVERSATION_LIMITS.MAX_CONVERSATIONS) {
132
+ return NextResponse.json({
133
+ success: false,
134
+ error: 'CONVERSATION_LIMIT_REACHED',
135
+ message: `Maximum of ${CONVERSATION_LIMITS.MAX_CONVERSATIONS} conversations reached.`,
136
+ data: { currentCount, maxAllowed: CONVERSATION_LIMITS.MAX_CONVERSATIONS },
137
+ }, { status: 400 })
138
+ }
139
+ }
140
+
141
+ // 6. Get agent configuration from centralized config
142
+ const agentConfig = getAgentConfig(AGENT_NAME)
143
+ if (!agentConfig) {
144
+ throw new Error(`Agent '${AGENT_NAME}' not configured`)
145
+ }
146
+
147
+ // 7. Load system prompt from .md file
148
+ const promptName = getAgentPromptName(AGENT_NAME)
149
+ if (!promptName) {
150
+ throw new Error(`No system prompt configured for agent '${AGENT_NAME}'`)
151
+ }
152
+ const systemPrompt = loadSystemPrompt(promptName as any)
153
+
154
+ // 8. Create agent with config from langchain.config.ts
155
+ const agent = await createAgent({
156
+ sessionId,
157
+ agentName: AGENT_NAME,
158
+ context,
159
+ systemPrompt,
160
+ tools: getAgentTools(AGENT_NAME, context),
161
+ modelConfig: getAgentModelConfig(AGENT_NAME),
162
+ })
163
+
164
+ // 9. Process message
165
+ const response = await agent.chat(message)
166
+
167
+ return NextResponse.json({
168
+ success: true,
169
+ data: {
170
+ message: response.content,
171
+ sessionId: response.sessionId,
172
+ isNewSession,
173
+ },
174
+ })
175
+ } catch (error) {
176
+ console.error('[Single Agent] Error:', error)
177
+ return NextResponse.json(
178
+ { success: false, error: 'Failed to process message' },
179
+ { status: 500 }
180
+ )
181
+ }
182
+ }
183
+
184
+ /**
185
+ * GET - Retrieve conversation history
186
+ */
187
+ export async function GET(req: NextRequest) {
188
+ const { searchParams } = new URL(req.url)
189
+ const sessionId = searchParams.get('sessionId')
190
+
191
+ // Return API documentation if no sessionId
192
+ if (!sessionId) {
193
+ const agentConfig = getAgentConfig(AGENT_NAME)
194
+ return NextResponse.json({
195
+ endpoint: '/api/v1/theme/default/ai/single-agent',
196
+ description: agentConfig?.description || 'Single agent chat endpoint',
197
+ agent: AGENT_NAME,
198
+ methods: {
199
+ POST: {
200
+ description: 'Send message to AI assistant',
201
+ body: { message: 'string', sessionId: 'string (optional)' },
202
+ },
203
+ GET: {
204
+ description: 'Get conversation history',
205
+ query: { sessionId: 'string (required)' },
206
+ },
207
+ DELETE: {
208
+ description: 'Clear conversation',
209
+ body: { sessionId: 'string (required)' },
210
+ },
211
+ },
212
+ })
213
+ }
214
+
215
+ // Auth
216
+ const authResult = await authenticateRequest(req)
217
+ if (!authResult.success || !authResult.user) {
218
+ return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
219
+ }
220
+
221
+ const teamId = req.headers.get('x-team-id')
222
+ if (!teamId) {
223
+ return NextResponse.json(
224
+ { success: false, error: 'Team context required' },
225
+ { status: 400 }
226
+ )
227
+ }
228
+
229
+ const context = { userId: authResult.user.id, teamId }
230
+
231
+ try {
232
+ const session = await dbMemoryStore.getSession(sessionId, context)
233
+ if (!session) {
234
+ return NextResponse.json({ success: false, error: 'Conversation not found' }, { status: 404 })
235
+ }
236
+
237
+ const messages = await dbMemoryStore.getMessages(sessionId, context)
238
+
239
+ return NextResponse.json({
240
+ success: true,
241
+ data: {
242
+ sessionId,
243
+ name: session.name,
244
+ messageCount: session.messageCount,
245
+ isPinned: session.isPinned,
246
+ messages: convertToApiMessages(messages),
247
+ },
248
+ })
249
+ } catch (error) {
250
+ console.error('[Single Agent] Get history error:', error)
251
+ return NextResponse.json({ success: false, error: 'Failed to get history' }, { status: 500 })
252
+ }
253
+ }
254
+
255
+ /**
256
+ * DELETE - Clear conversation
257
+ */
258
+ export async function DELETE(req: NextRequest) {
259
+ const authResult = await authenticateRequest(req)
260
+ if (!authResult.success || !authResult.user) {
261
+ return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
262
+ }
263
+
264
+ const teamId = req.headers.get('x-team-id')
265
+ if (!teamId) {
266
+ return NextResponse.json(
267
+ { success: false, error: 'Team context required' },
268
+ { status: 400 }
269
+ )
270
+ }
271
+
272
+ try {
273
+ const body = await req.json().catch(() => ({}))
274
+ const sessionId = body.sessionId
275
+
276
+ if (!sessionId) {
277
+ return NextResponse.json({ success: false, error: 'sessionId required' }, { status: 400 })
278
+ }
279
+
280
+ await dbMemoryStore.clearSession(sessionId, { userId: authResult.user.id, teamId })
281
+
282
+ return NextResponse.json({
283
+ success: true,
284
+ message: 'Conversation cleared',
285
+ sessionId,
286
+ })
287
+ } catch (error) {
288
+ console.error('[Single Agent] Clear error:', error)
289
+ return NextResponse.json({ success: false, error: 'Failed to clear' }, { status: 500 })
290
+ }
291
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * ============================================================================
3
+ * TOKEN USAGE API ENDPOINT
4
+ * ============================================================================
5
+ *
6
+ * Provides token usage statistics for users and teams.
7
+ * Supports different time periods and user/team views.
8
+ *
9
+ * GET /api/ai/usage
10
+ * Query params:
11
+ * - period: 'today' | '7d' | '30d' | 'all' (default: '30d')
12
+ * - type: 'user' | 'team' (default: 'user')
13
+ *
14
+ * Returns:
15
+ * - stats: Aggregated usage statistics
16
+ * - daily: Daily breakdown for charts
17
+ *
18
+ * ============================================================================
19
+ */
20
+
21
+ import { NextRequest, NextResponse } from 'next/server'
22
+ import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
23
+ import { tokenTracker } from '@/plugins/langchain/lib/token-tracker'
24
+ import { queryOne } from '@nextsparkjs/core/lib/db'
25
+
26
+ type Period = 'today' | '7d' | '30d' | 'all'
27
+
28
+ /**
29
+ * GET - Retrieve token usage statistics
30
+ */
31
+ export async function GET(request: NextRequest) {
32
+ try {
33
+ // 1. Authentication
34
+ const authResult = await authenticateRequest(request)
35
+ if (!authResult.success || !authResult.user) {
36
+ return NextResponse.json(
37
+ { success: false, error: 'Unauthorized' },
38
+ { status: 401 }
39
+ )
40
+ }
41
+
42
+ // 2. Team context
43
+ const teamId = request.headers.get('x-team-id')
44
+ if (!teamId) {
45
+ return NextResponse.json(
46
+ { success: false, error: 'Team context required', code: 'TEAM_CONTEXT_REQUIRED' },
47
+ { status: 400 }
48
+ )
49
+ }
50
+
51
+ const userId = authResult.user.id
52
+
53
+ // 3. Parse query parameters
54
+ const { searchParams } = new URL(request.url)
55
+ const period = (searchParams.get('period') || '30d') as Period
56
+ const type = searchParams.get('type') || 'user'
57
+
58
+ // Validate period
59
+ if (!['today', '7d', '30d', 'all'].includes(period)) {
60
+ return NextResponse.json(
61
+ { success: false, error: 'Invalid period. Must be: today, 7d, 30d, or all' },
62
+ { status: 400 }
63
+ )
64
+ }
65
+
66
+ // 4. Check permissions for team usage
67
+ if (type === 'team') {
68
+ // Get user's team role to check if they're admin
69
+ const teamMembership = await queryOne<{ role: string }>(
70
+ `SELECT role FROM public.team_members
71
+ WHERE "userId" = $1 AND "teamId" = $2`,
72
+ [userId, teamId]
73
+ )
74
+
75
+ const isTeamAdmin = teamMembership?.role === 'admin' || teamMembership?.role === 'owner'
76
+
77
+ if (!isTeamAdmin) {
78
+ return NextResponse.json(
79
+ { success: false, error: 'Team usage requires admin permissions' },
80
+ { status: 403 }
81
+ )
82
+ }
83
+ }
84
+
85
+ const context = {
86
+ userId,
87
+ teamId,
88
+ }
89
+
90
+ // 5. Fetch usage statistics
91
+ const [stats, daily] = await Promise.all([
92
+ type === 'team'
93
+ ? tokenTracker.getTeamUsage(teamId, period)
94
+ : tokenTracker.getUsage(context, period),
95
+ tokenTracker.getDailyUsage(
96
+ context,
97
+ period === 'all' ? 365 : period === '30d' ? 30 : period === '7d' ? 7 : 1
98
+ ),
99
+ ])
100
+
101
+ // 6. Return results
102
+ return NextResponse.json({
103
+ success: true,
104
+ data: {
105
+ stats,
106
+ daily,
107
+ period,
108
+ type,
109
+ },
110
+ })
111
+ } catch (error) {
112
+ console.error('[AI Usage] Error:', error)
113
+ return NextResponse.json(
114
+ {
115
+ success: false,
116
+ error: 'Failed to retrieve usage statistics',
117
+ details: error instanceof Error ? error.message : 'Unknown error',
118
+ },
119
+ { status: 500 }
120
+ )
121
+ }
122
+ }