@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.
- package/about/business.md +49 -0
- package/about/features.json +302 -0
- package/about/team.md +79 -0
- package/api/ai/chat/stream/route.ts +212 -0
- package/api/ai/orchestrator/route.ts +226 -0
- package/api/ai/single-agent/route.ts +291 -0
- package/api/ai/usage/route.ts +122 -0
- package/blocks/benefits/component.tsx +100 -0
- package/blocks/benefits/config.ts +11 -0
- package/blocks/benefits/examples.ts +85 -0
- package/blocks/benefits/fields.ts +156 -0
- package/blocks/benefits/schema.ts +33 -0
- package/blocks/cta-section/component.tsx +100 -0
- package/blocks/cta-section/config.ts +11 -0
- package/blocks/cta-section/examples.ts +41 -0
- package/blocks/cta-section/fields.ts +89 -0
- package/blocks/cta-section/index.ts +6 -0
- package/blocks/cta-section/schema.ts +32 -0
- package/blocks/cta-section/thumbnail.png +1 -0
- package/blocks/faq-accordion/component.tsx +156 -0
- package/blocks/faq-accordion/config.ts +11 -0
- package/blocks/faq-accordion/examples.ts +77 -0
- package/blocks/faq-accordion/fields.ts +119 -0
- package/blocks/faq-accordion/index.ts +6 -0
- package/blocks/faq-accordion/schema.ts +45 -0
- package/blocks/features-grid/component.tsx +112 -0
- package/blocks/features-grid/config.ts +11 -0
- package/blocks/features-grid/examples.ts +63 -0
- package/blocks/features-grid/fields.ts +97 -0
- package/blocks/features-grid/index.ts +6 -0
- package/blocks/features-grid/schema.ts +40 -0
- package/blocks/features-grid/thumbnail.png +1 -0
- package/blocks/hero/component.tsx +100 -0
- package/blocks/hero/config.ts +11 -0
- package/blocks/hero/examples.ts +35 -0
- package/blocks/hero/fields.ts +60 -0
- package/blocks/hero/index.ts +6 -0
- package/blocks/hero/schema.ts +32 -0
- package/blocks/hero/thumbnail.png +1 -0
- package/blocks/hero/thumbnail.png.txt +6 -0
- package/blocks/hero-with-form/component.tsx +232 -0
- package/blocks/hero-with-form/config.ts +11 -0
- package/blocks/hero-with-form/examples.ts +16 -0
- package/blocks/hero-with-form/fields.ts +207 -0
- package/blocks/hero-with-form/index.ts +6 -0
- package/blocks/hero-with-form/schema.ts +54 -0
- package/blocks/jumbotron/component.tsx +136 -0
- package/blocks/jumbotron/config.ts +11 -0
- package/blocks/jumbotron/examples.ts +36 -0
- package/blocks/jumbotron/fields.ts +202 -0
- package/blocks/jumbotron/index.ts +6 -0
- package/blocks/jumbotron/schema.ts +55 -0
- package/blocks/logo-cloud/component.tsx +154 -0
- package/blocks/logo-cloud/config.ts +11 -0
- package/blocks/logo-cloud/examples.ts +34 -0
- package/blocks/logo-cloud/fields.ts +133 -0
- package/blocks/logo-cloud/index.ts +6 -0
- package/blocks/logo-cloud/schema.ts +46 -0
- package/blocks/post-content/component.tsx +197 -0
- package/blocks/post-content/config.ts +11 -0
- package/blocks/post-content/examples.ts +33 -0
- package/blocks/post-content/fields.ts +165 -0
- package/blocks/post-content/index.ts +4 -0
- package/blocks/post-content/schema.ts +46 -0
- package/blocks/pricing-table/component.tsx +154 -0
- package/blocks/pricing-table/config.ts +11 -0
- package/blocks/pricing-table/examples.ts +96 -0
- package/blocks/pricing-table/fields.ts +161 -0
- package/blocks/pricing-table/index.ts +4 -0
- package/blocks/pricing-table/schema.ts +50 -0
- package/blocks/split-content/component.tsx +135 -0
- package/blocks/split-content/config.ts +11 -0
- package/blocks/split-content/examples.ts +38 -0
- package/blocks/split-content/fields.ts +198 -0
- package/blocks/split-content/index.ts +6 -0
- package/blocks/split-content/schema.ts +67 -0
- package/blocks/stats-counter/component.tsx +124 -0
- package/blocks/stats-counter/config.ts +11 -0
- package/blocks/stats-counter/examples.ts +61 -0
- package/blocks/stats-counter/fields.ts +134 -0
- package/blocks/stats-counter/index.ts +6 -0
- package/blocks/stats-counter/schema.ts +47 -0
- package/blocks/testimonials/component.tsx +114 -0
- package/blocks/testimonials/config.ts +11 -0
- package/blocks/testimonials/examples.ts +65 -0
- package/blocks/testimonials/fields.ts +105 -0
- package/blocks/testimonials/index.ts +6 -0
- package/blocks/testimonials/schema.ts +41 -0
- package/blocks/testimonials/thumbnail.png +1 -0
- package/blocks/text-content/component.tsx +97 -0
- package/blocks/text-content/config.ts +11 -0
- package/blocks/text-content/examples.ts +30 -0
- package/blocks/text-content/fields.ts +88 -0
- package/blocks/text-content/index.ts +6 -0
- package/blocks/text-content/schema.ts +30 -0
- package/blocks/text-content/thumbnail.png +1 -0
- package/blocks/timeline/component.tsx +267 -0
- package/blocks/timeline/config.ts +11 -0
- package/blocks/timeline/examples.ts +68 -0
- package/blocks/timeline/fields.ts +147 -0
- package/blocks/timeline/index.ts +6 -0
- package/blocks/timeline/schema.ts +49 -0
- package/blocks/video-hero/component.tsx +270 -0
- package/blocks/video-hero/config.ts +11 -0
- package/blocks/video-hero/examples.ts +24 -0
- package/blocks/video-hero/fields.ts +98 -0
- package/blocks/video-hero/index.ts +6 -0
- package/blocks/video-hero/schema.ts +39 -0
- package/components/ai-chat/ChatPanel.tsx +575 -0
- package/components/ai-chat/ConversationItem.tsx +266 -0
- package/components/ai-chat/ConversationSidebar.tsx +99 -0
- package/components/ai-chat/MarkdownRenderer.tsx +15 -0
- package/components/ai-chat/Message.tsx +42 -0
- package/components/ai-chat/MessageInput.tsx +49 -0
- package/components/ai-chat/MessageList.tsx +46 -0
- package/components/ai-chat/TypingIndicator.tsx +11 -0
- package/config/app.config.ts +367 -0
- package/config/billing.config.ts +349 -0
- package/config/dashboard.config.ts +506 -0
- package/config/dev.config.ts +104 -0
- package/config/features.config.ts +203 -0
- package/config/flows.config.ts +129 -0
- package/config/permissions.config.ts +245 -0
- package/config/theme.config.ts +74 -0
- package/docs/01-overview/01-introduction.md +335 -0
- package/docs/01-overview/02-customization.md +671 -0
- package/docs/02-features/01-components.md +155 -0
- package/docs/02-features/02-styling.md +139 -0
- package/docs/02-features/03-tasks-entity.md +407 -0
- package/docs/03-ai/01-overview.md +211 -0
- package/docs/03-ai/02-customization.md +436 -0
- package/entities/customers/customers.config.ts +75 -0
- package/entities/customers/customers.fields.ts +165 -0
- package/entities/customers/customers.service.ts +516 -0
- package/entities/customers/customers.types.ts +83 -0
- package/entities/customers/messages/en.json +66 -0
- package/entities/customers/messages/es.json +66 -0
- package/entities/customers/migrations/001_customers_table.sql +102 -0
- package/entities/customers/migrations/002_customers_metas.sql +92 -0
- package/entities/pages/messages/en.json +41 -0
- package/entities/pages/messages/es.json +41 -0
- package/entities/pages/migrations/001_pages_table.sql +112 -0
- package/entities/pages/migrations/002_pages_metas.sql +56 -0
- package/entities/pages/migrations/003_add_status.sql +50 -0
- package/entities/pages/pages-management.service.ts +610 -0
- package/entities/pages/pages.config.ts +94 -0
- package/entities/pages/pages.fields.ts +101 -0
- package/entities/pages/pages.service.ts +290 -0
- package/entities/pages/pages.types.ts +124 -0
- package/entities/posts/components/post-header.tsx +97 -0
- package/entities/posts/messages/en.json +55 -0
- package/entities/posts/messages/es.json +55 -0
- package/entities/posts/migrations/001_posts_table.sql +115 -0
- package/entities/posts/migrations/003_add_status.sql +44 -0
- package/entities/posts/migrations/004_entity_taxonomy_relations.sql +129 -0
- package/entities/posts/migrations/006_posts_metas.sql +56 -0
- package/entities/posts/posts.config.ts +101 -0
- package/entities/posts/posts.fields.ts +116 -0
- package/entities/posts/posts.service.ts +376 -0
- package/entities/posts/posts.types.ts +74 -0
- package/entities/tasks/messages/en.json +204 -0
- package/entities/tasks/messages/es.json +204 -0
- package/entities/tasks/migrations/001_tasks_table.sql +105 -0
- package/entities/tasks/migrations/002_task_metas.sql +85 -0
- package/entities/tasks/migrations/sample_data.json +77 -0
- package/entities/tasks/tasks.config.ts +79 -0
- package/entities/tasks/tasks.fields.ts +196 -0
- package/entities/tasks/tasks.service.ts +541 -0
- package/entities/tasks/tasks.types.ts +56 -0
- package/lib/hooks/useAiChat.ts +114 -0
- package/lib/hooks/useConversations.ts +376 -0
- package/lib/hooks/useOrchestratorChat.ts +122 -0
- package/lib/hooks/usePersistentChat.ts +315 -0
- package/lib/hooks/useStreamingChat.ts +127 -0
- package/lib/hooks/useTokenUsage.ts +63 -0
- package/lib/langchain/agents/customer-assistant.md +69 -0
- package/lib/langchain/agents/index.ts +61 -0
- package/lib/langchain/agents/orchestrator.md +59 -0
- package/lib/langchain/agents/page-assistant.md +85 -0
- package/lib/langchain/agents/single-agent.md +46 -0
- package/lib/langchain/agents/task-assistant.md +55 -0
- package/lib/langchain/config.ts +45 -0
- package/lib/langchain/handlers/customer-handler.ts +338 -0
- package/lib/langchain/handlers/page-handler.ts +232 -0
- package/lib/langchain/handlers/task-handler.ts +323 -0
- package/lib/langchain/langchain.config.ts +223 -0
- package/lib/langchain/observability.config.ts +30 -0
- package/lib/langchain/orchestrator.ts +562 -0
- package/lib/langchain/tools/customers.ts +176 -0
- package/lib/langchain/tools/index.ts +10 -0
- package/lib/langchain/tools/orchestrator.ts +92 -0
- package/lib/langchain/tools/pages.ts +289 -0
- package/lib/langchain/tools/tasks.ts +167 -0
- package/lib/scheduled-actions/billing.ts +149 -0
- package/lib/scheduled-actions/index.ts +170 -0
- package/lib/scheduled-actions/webhook.ts +231 -0
- package/lib/selectors.ts +197 -0
- package/messages/de/admin.json +219 -0
- package/messages/de/aiUsage.json +36 -0
- package/messages/de/buttons.json +19 -0
- package/messages/de/categories.json +35 -0
- package/messages/de/common.json +16 -0
- package/messages/de/dev.json +101 -0
- package/messages/de/docs.json +27 -0
- package/messages/de/entities.json +7 -0
- package/messages/de/features.json +119 -0
- package/messages/de/footer.json +22 -0
- package/messages/de/home.json +57 -0
- package/messages/de/index.ts +39 -0
- package/messages/de/mobileNav.json +13 -0
- package/messages/de/navigation.json +8 -0
- package/messages/de/observability.json +74 -0
- package/messages/de/posts.json +54 -0
- package/messages/de/pricing.json +102 -0
- package/messages/de/support.json +9 -0
- package/messages/de/teams.json +8 -0
- package/messages/en/admin.json +219 -0
- package/messages/en/aiUsage.json +36 -0
- package/messages/en/buttons.json +19 -0
- package/messages/en/categories.json +35 -0
- package/messages/en/common.json +16 -0
- package/messages/en/dev.json +106 -0
- package/messages/en/docs.json +27 -0
- package/messages/en/entities.json +7 -0
- package/messages/en/features.json +119 -0
- package/messages/en/footer.json +22 -0
- package/messages/en/home.json +57 -0
- package/messages/en/index.ts +39 -0
- package/messages/en/mobileNav.json +13 -0
- package/messages/en/navigation.json +8 -0
- package/messages/en/observability.json +74 -0
- package/messages/en/posts.json +54 -0
- package/messages/en/pricing.json +102 -0
- package/messages/en/support.json +9 -0
- package/messages/en/teams.json +8 -0
- package/messages/es/admin.json +219 -0
- package/messages/es/aiUsage.json +36 -0
- package/messages/es/buttons.json +19 -0
- package/messages/es/categories.json +35 -0
- package/messages/es/common.json +16 -0
- package/messages/es/dev.json +101 -0
- package/messages/es/docs.json +27 -0
- package/messages/es/entities.json +7 -0
- package/messages/es/features.json +119 -0
- package/messages/es/footer.json +22 -0
- package/messages/es/home.json +57 -0
- package/messages/es/index.ts +39 -0
- package/messages/es/mobileNav.json +13 -0
- package/messages/es/navigation.json +8 -0
- package/messages/es/observability.json +74 -0
- package/messages/es/posts.json +54 -0
- package/messages/es/pricing.json +102 -0
- package/messages/es/support.json +9 -0
- package/messages/es/teams.json +8 -0
- package/messages/fr/admin.json +219 -0
- package/messages/fr/aiUsage.json +36 -0
- package/messages/fr/buttons.json +19 -0
- package/messages/fr/categories.json +35 -0
- package/messages/fr/common.json +16 -0
- package/messages/fr/dev.json +101 -0
- package/messages/fr/docs.json +27 -0
- package/messages/fr/entities.json +7 -0
- package/messages/fr/features.json +119 -0
- package/messages/fr/footer.json +22 -0
- package/messages/fr/home.json +57 -0
- package/messages/fr/index.ts +39 -0
- package/messages/fr/mobileNav.json +13 -0
- package/messages/fr/navigation.json +8 -0
- package/messages/fr/observability.json +74 -0
- package/messages/fr/posts.json +54 -0
- package/messages/fr/pricing.json +102 -0
- package/messages/fr/support.json +9 -0
- package/messages/fr/teams.json +8 -0
- package/messages/it/admin.json +219 -0
- package/messages/it/aiUsage.json +36 -0
- package/messages/it/buttons.json +19 -0
- package/messages/it/categories.json +35 -0
- package/messages/it/common.json +16 -0
- package/messages/it/dev.json +101 -0
- package/messages/it/docs.json +27 -0
- package/messages/it/entities.json +7 -0
- package/messages/it/features.json +119 -0
- package/messages/it/footer.json +22 -0
- package/messages/it/home.json +57 -0
- package/messages/it/index.ts +39 -0
- package/messages/it/mobileNav.json +13 -0
- package/messages/it/navigation.json +8 -0
- package/messages/it/observability.json +74 -0
- package/messages/it/posts.json +54 -0
- package/messages/it/pricing.json +102 -0
- package/messages/it/support.json +9 -0
- package/messages/it/teams.json +8 -0
- package/messages/pt/admin.json +219 -0
- package/messages/pt/aiUsage.json +36 -0
- package/messages/pt/buttons.json +19 -0
- package/messages/pt/categories.json +35 -0
- package/messages/pt/common.json +16 -0
- package/messages/pt/dev.json +101 -0
- package/messages/pt/docs.json +27 -0
- package/messages/pt/entities.json +7 -0
- package/messages/pt/features.json +119 -0
- package/messages/pt/footer.json +22 -0
- package/messages/pt/home.json +57 -0
- package/messages/pt/index.ts +39 -0
- package/messages/pt/mobileNav.json +13 -0
- package/messages/pt/navigation.json +8 -0
- package/messages/pt/observability.json +74 -0
- package/messages/pt/posts.json +54 -0
- package/messages/pt/pricing.json +102 -0
- package/messages/pt/support.json +9 -0
- package/messages/pt/teams.json +8 -0
- package/migrations/089_add_editor_team_role.sql +39 -0
- package/migrations/090_demo_users_teams.sql +540 -0
- package/migrations/091_greek_teams_billing.sql +523 -0
- package/migrations/092_billing_sample_data.sql +774 -0
- package/migrations/093_pages_sample_data.sql +1158 -0
- package/migrations/094_posts_sample_data.sql +278 -0
- package/migrations/095_tasks_sample_data.sql +440 -0
- package/migrations/096_customers_sample_data.sql +358 -0
- package/migrations/097_scheduled_actions_sample_data.sql +111 -0
- package/package.json +22 -0
- package/public/docs/desktop-layout-example.png +0 -0
- package/styles/components.css +11 -0
- package/styles/globals.css +179 -0
- package/templates/(public)/blog/[slug]/page.tsx +65 -0
- package/templates/(public)/layout.tsx +25 -0
- package/templates/(public)/page.tsx +200 -0
- package/templates/(public)/support/page.tsx +321 -0
- package/templates/dashboard/(main)/agent-multi/page.tsx +63 -0
- package/templates/dashboard/(main)/agent-single/page.tsx +142 -0
- package/templates/dashboard/(main)/settings/ai-usage/page.tsx +157 -0
- package/templates/superadmin/ai-observability/[traceId]/page.tsx +27 -0
- package/templates/superadmin/ai-observability/page.tsx +17 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tasks Service
|
|
3
|
+
*
|
|
4
|
+
* Provides data access methods for tasks.
|
|
5
|
+
* Tasks is a private entity with shared: false - users only see their own tasks.
|
|
6
|
+
*
|
|
7
|
+
* All methods require authentication (use RLS with userId filter).
|
|
8
|
+
*
|
|
9
|
+
* @module TasksService
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { queryOneWithRLS, queryWithRLS } from '@nextsparkjs/core/lib/db'
|
|
13
|
+
import type {
|
|
14
|
+
Task,
|
|
15
|
+
TaskListOptions,
|
|
16
|
+
TaskListResult,
|
|
17
|
+
TaskStatus,
|
|
18
|
+
TaskPriority,
|
|
19
|
+
} from './tasks.types'
|
|
20
|
+
|
|
21
|
+
// Database row type for task
|
|
22
|
+
interface DbTask {
|
|
23
|
+
id: string
|
|
24
|
+
title: string
|
|
25
|
+
description: string | null
|
|
26
|
+
status: TaskStatus
|
|
27
|
+
priority: TaskPriority
|
|
28
|
+
tags: string[] | null
|
|
29
|
+
dueDate: string | null
|
|
30
|
+
estimatedHours: number | null
|
|
31
|
+
completed: boolean | null
|
|
32
|
+
createdAt: string
|
|
33
|
+
updatedAt: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class TasksService {
|
|
37
|
+
// ============================================
|
|
38
|
+
// AUTHENTICATED METHODS (con RLS)
|
|
39
|
+
// ============================================
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get a task by ID
|
|
43
|
+
*
|
|
44
|
+
* Respects RLS policies. Since tasks has shared: false,
|
|
45
|
+
* only the task owner can access it.
|
|
46
|
+
*
|
|
47
|
+
* @param id - Task ID
|
|
48
|
+
* @param userId - Current user ID for RLS
|
|
49
|
+
* @returns Task data or null if not found/not authorized
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* const task = await TasksService.getById('task-uuid', currentUserId)
|
|
53
|
+
*/
|
|
54
|
+
static async getById(
|
|
55
|
+
id: string,
|
|
56
|
+
userId: string
|
|
57
|
+
): Promise<Task | null> {
|
|
58
|
+
try {
|
|
59
|
+
if (!id || id.trim() === '') {
|
|
60
|
+
throw new Error('Task ID is required')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!userId || userId.trim() === '') {
|
|
64
|
+
throw new Error('User ID is required for authentication')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const task = await queryOneWithRLS<DbTask>(
|
|
68
|
+
`
|
|
69
|
+
SELECT
|
|
70
|
+
id,
|
|
71
|
+
title,
|
|
72
|
+
description,
|
|
73
|
+
status,
|
|
74
|
+
priority,
|
|
75
|
+
tags,
|
|
76
|
+
"dueDate",
|
|
77
|
+
"estimatedHours",
|
|
78
|
+
completed,
|
|
79
|
+
"createdAt",
|
|
80
|
+
"updatedAt"
|
|
81
|
+
FROM tasks
|
|
82
|
+
WHERE id = $1
|
|
83
|
+
`,
|
|
84
|
+
[id],
|
|
85
|
+
userId
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if (!task) {
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
id: task.id,
|
|
94
|
+
title: task.title,
|
|
95
|
+
description: task.description ?? undefined,
|
|
96
|
+
status: task.status,
|
|
97
|
+
priority: task.priority,
|
|
98
|
+
tags: task.tags ?? undefined,
|
|
99
|
+
dueDate: task.dueDate ?? undefined,
|
|
100
|
+
estimatedHours: task.estimatedHours ?? undefined,
|
|
101
|
+
completed: task.completed ?? undefined,
|
|
102
|
+
createdAt: task.createdAt,
|
|
103
|
+
updatedAt: task.updatedAt,
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error('TasksService.getById error:', error)
|
|
107
|
+
throw new Error(
|
|
108
|
+
error instanceof Error ? error.message : 'Failed to fetch task'
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* List tasks with pagination and filtering
|
|
115
|
+
*
|
|
116
|
+
* @param userId - Current user ID for RLS
|
|
117
|
+
* @param options - List options (limit, offset, status, priority, orderBy, orderDir)
|
|
118
|
+
* @returns Object with tasks array and total count
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* const { tasks, total } = await TasksService.list(currentUserId, {
|
|
122
|
+
* status: 'todo',
|
|
123
|
+
* limit: 10
|
|
124
|
+
* })
|
|
125
|
+
*/
|
|
126
|
+
static async list(
|
|
127
|
+
userId: string,
|
|
128
|
+
options: TaskListOptions = {}
|
|
129
|
+
): Promise<TaskListResult> {
|
|
130
|
+
try {
|
|
131
|
+
if (!userId || userId.trim() === '') {
|
|
132
|
+
throw new Error('User ID is required for authentication')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const {
|
|
136
|
+
limit = 10,
|
|
137
|
+
offset = 0,
|
|
138
|
+
status,
|
|
139
|
+
priority,
|
|
140
|
+
orderBy = 'createdAt',
|
|
141
|
+
orderDir = 'desc',
|
|
142
|
+
} = options
|
|
143
|
+
|
|
144
|
+
// Build WHERE clause
|
|
145
|
+
const conditions: string[] = []
|
|
146
|
+
const params: unknown[] = []
|
|
147
|
+
let paramIndex = 1
|
|
148
|
+
|
|
149
|
+
if (status) {
|
|
150
|
+
conditions.push(`status = $${paramIndex}`)
|
|
151
|
+
params.push(status)
|
|
152
|
+
paramIndex++
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (priority) {
|
|
156
|
+
conditions.push(`priority = $${paramIndex}`)
|
|
157
|
+
params.push(priority)
|
|
158
|
+
paramIndex++
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (options.teamId) {
|
|
162
|
+
conditions.push(`"teamId" = $${paramIndex}`)
|
|
163
|
+
params.push(options.teamId)
|
|
164
|
+
paramIndex++
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
|
|
168
|
+
|
|
169
|
+
// Validate orderBy to prevent SQL injection
|
|
170
|
+
const validOrderBy = ['title', 'status', 'priority', 'dueDate', 'createdAt'].includes(orderBy)
|
|
171
|
+
? orderBy
|
|
172
|
+
: 'createdAt'
|
|
173
|
+
const validOrderDir = orderDir === 'asc' ? 'ASC' : 'DESC'
|
|
174
|
+
|
|
175
|
+
// Map field names to database columns
|
|
176
|
+
const orderColumnMap: Record<string, string> = {
|
|
177
|
+
title: 'title',
|
|
178
|
+
status: 'status',
|
|
179
|
+
priority: 'priority',
|
|
180
|
+
dueDate: '"dueDate"',
|
|
181
|
+
createdAt: '"createdAt"',
|
|
182
|
+
}
|
|
183
|
+
const orderColumn = orderColumnMap[validOrderBy] || '"createdAt"'
|
|
184
|
+
|
|
185
|
+
// Get total count
|
|
186
|
+
const countResult = await queryWithRLS<{ count: string }>(
|
|
187
|
+
`SELECT COUNT(*)::text as count FROM tasks ${whereClause}`,
|
|
188
|
+
params,
|
|
189
|
+
userId
|
|
190
|
+
)
|
|
191
|
+
const total = parseInt(countResult[0]?.count || '0', 10)
|
|
192
|
+
|
|
193
|
+
// Get tasks
|
|
194
|
+
params.push(limit, offset)
|
|
195
|
+
const tasks = await queryWithRLS<DbTask>(
|
|
196
|
+
`
|
|
197
|
+
SELECT
|
|
198
|
+
id,
|
|
199
|
+
title,
|
|
200
|
+
description,
|
|
201
|
+
status,
|
|
202
|
+
priority,
|
|
203
|
+
tags,
|
|
204
|
+
"dueDate",
|
|
205
|
+
"estimatedHours",
|
|
206
|
+
completed,
|
|
207
|
+
"createdAt",
|
|
208
|
+
"updatedAt"
|
|
209
|
+
FROM tasks
|
|
210
|
+
${whereClause}
|
|
211
|
+
ORDER BY ${orderColumn} ${validOrderDir}
|
|
212
|
+
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
|
|
213
|
+
`,
|
|
214
|
+
params,
|
|
215
|
+
userId
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
tasks: tasks.map((task) => ({
|
|
220
|
+
id: task.id,
|
|
221
|
+
title: task.title,
|
|
222
|
+
description: task.description ?? undefined,
|
|
223
|
+
status: task.status,
|
|
224
|
+
priority: task.priority,
|
|
225
|
+
tags: task.tags ?? undefined,
|
|
226
|
+
dueDate: task.dueDate ?? undefined,
|
|
227
|
+
estimatedHours: task.estimatedHours ?? undefined,
|
|
228
|
+
completed: task.completed ?? undefined,
|
|
229
|
+
createdAt: task.createdAt,
|
|
230
|
+
updatedAt: task.updatedAt,
|
|
231
|
+
})),
|
|
232
|
+
total,
|
|
233
|
+
}
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.error('TasksService.list error:', error)
|
|
236
|
+
throw new Error(
|
|
237
|
+
error instanceof Error ? error.message : 'Failed to list tasks'
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get tasks by status
|
|
244
|
+
*
|
|
245
|
+
* Convenience method to fetch all tasks with a specific status.
|
|
246
|
+
*
|
|
247
|
+
* @param userId - Current user ID for RLS
|
|
248
|
+
* @param status - Task status to filter by
|
|
249
|
+
* @returns Array of tasks with the given status
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* const todoTasks = await TasksService.getByStatus(currentUserId, 'todo')
|
|
253
|
+
*/
|
|
254
|
+
static async getByStatus(
|
|
255
|
+
userId: string,
|
|
256
|
+
status: TaskStatus
|
|
257
|
+
): Promise<Task[]> {
|
|
258
|
+
try {
|
|
259
|
+
const { tasks } = await this.list(userId, {
|
|
260
|
+
status,
|
|
261
|
+
limit: 1000, // Large limit to get all matching tasks
|
|
262
|
+
orderBy: 'priority',
|
|
263
|
+
orderDir: 'desc',
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
return tasks
|
|
267
|
+
} catch (error) {
|
|
268
|
+
console.error('TasksService.getByStatus error:', error)
|
|
269
|
+
throw new Error(
|
|
270
|
+
error instanceof Error ? error.message : 'Failed to fetch tasks by status'
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Get overdue tasks
|
|
277
|
+
*
|
|
278
|
+
* Fetches tasks that are past their due date and not completed.
|
|
279
|
+
*
|
|
280
|
+
* @param userId - Current user ID for RLS
|
|
281
|
+
* @returns Array of overdue tasks
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* const overdueTasks = await TasksService.getOverdue(currentUserId)
|
|
285
|
+
*/
|
|
286
|
+
static async getOverdue(userId: string): Promise<Task[]> {
|
|
287
|
+
try {
|
|
288
|
+
if (!userId || userId.trim() === '') {
|
|
289
|
+
throw new Error('User ID is required for authentication')
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const tasks = await queryWithRLS<DbTask>(
|
|
293
|
+
`
|
|
294
|
+
SELECT
|
|
295
|
+
id,
|
|
296
|
+
title,
|
|
297
|
+
description,
|
|
298
|
+
status,
|
|
299
|
+
priority,
|
|
300
|
+
tags,
|
|
301
|
+
"dueDate",
|
|
302
|
+
"estimatedHours",
|
|
303
|
+
completed,
|
|
304
|
+
"createdAt",
|
|
305
|
+
"updatedAt"
|
|
306
|
+
FROM tasks
|
|
307
|
+
WHERE "dueDate" < CURRENT_DATE
|
|
308
|
+
AND status != 'done'
|
|
309
|
+
AND (completed IS NULL OR completed = false)
|
|
310
|
+
ORDER BY "dueDate" ASC
|
|
311
|
+
`,
|
|
312
|
+
[],
|
|
313
|
+
userId
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
return tasks.map((task) => ({
|
|
317
|
+
id: task.id,
|
|
318
|
+
title: task.title,
|
|
319
|
+
description: task.description ?? undefined,
|
|
320
|
+
status: task.status,
|
|
321
|
+
priority: task.priority,
|
|
322
|
+
tags: task.tags ?? undefined,
|
|
323
|
+
dueDate: task.dueDate ?? undefined,
|
|
324
|
+
estimatedHours: task.estimatedHours ?? undefined,
|
|
325
|
+
completed: task.completed ?? undefined,
|
|
326
|
+
createdAt: task.createdAt,
|
|
327
|
+
updatedAt: task.updatedAt,
|
|
328
|
+
}))
|
|
329
|
+
} catch (error) {
|
|
330
|
+
console.error('TasksService.getOverdue error:', error)
|
|
331
|
+
throw new Error(
|
|
332
|
+
error instanceof Error ? error.message : 'Failed to fetch overdue tasks'
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ============================================
|
|
338
|
+
// WRITE METHODS (con RLS)
|
|
339
|
+
// ============================================
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Create a new task
|
|
343
|
+
*
|
|
344
|
+
* @param userId - Current user ID for RLS
|
|
345
|
+
* @param data - Task data
|
|
346
|
+
* @returns Created task
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* const task = await TasksService.create(currentUserId, {
|
|
350
|
+
* title: 'New Task',
|
|
351
|
+
* teamId: 'team-123'
|
|
352
|
+
* })
|
|
353
|
+
*/
|
|
354
|
+
static async create(
|
|
355
|
+
userId: string,
|
|
356
|
+
data: Partial<Omit<Task, 'id' | 'createdAt' | 'updatedAt'>> & { teamId: string }
|
|
357
|
+
): Promise<Task> {
|
|
358
|
+
try {
|
|
359
|
+
if (!userId) throw new Error('User ID is required')
|
|
360
|
+
if (!data.title) throw new Error('Title is required')
|
|
361
|
+
if (!data.teamId) throw new Error('Team ID is required')
|
|
362
|
+
|
|
363
|
+
const task = await queryOneWithRLS<DbTask>(
|
|
364
|
+
`
|
|
365
|
+
INSERT INTO tasks (
|
|
366
|
+
id,
|
|
367
|
+
title,
|
|
368
|
+
description,
|
|
369
|
+
status,
|
|
370
|
+
priority,
|
|
371
|
+
tags,
|
|
372
|
+
"dueDate",
|
|
373
|
+
"estimatedHours",
|
|
374
|
+
completed,
|
|
375
|
+
"createdAt",
|
|
376
|
+
"updatedAt",
|
|
377
|
+
"userId",
|
|
378
|
+
"teamId"
|
|
379
|
+
) VALUES (
|
|
380
|
+
gen_random_uuid(),
|
|
381
|
+
$1,
|
|
382
|
+
$2,
|
|
383
|
+
$3,
|
|
384
|
+
$4,
|
|
385
|
+
$5,
|
|
386
|
+
$6,
|
|
387
|
+
$7,
|
|
388
|
+
$8,
|
|
389
|
+
NOW(),
|
|
390
|
+
NOW(),
|
|
391
|
+
$9,
|
|
392
|
+
$10
|
|
393
|
+
)
|
|
394
|
+
RETURNING *
|
|
395
|
+
`,
|
|
396
|
+
[
|
|
397
|
+
data.title,
|
|
398
|
+
data.description || null,
|
|
399
|
+
data.status || 'todo',
|
|
400
|
+
data.priority || 'medium',
|
|
401
|
+
data.tags || [],
|
|
402
|
+
data.dueDate || null,
|
|
403
|
+
data.estimatedHours || null,
|
|
404
|
+
data.completed || false,
|
|
405
|
+
userId,
|
|
406
|
+
data.teamId
|
|
407
|
+
],
|
|
408
|
+
userId
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
if (!task) throw new Error('Failed to create task')
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
id: task.id,
|
|
415
|
+
title: task.title,
|
|
416
|
+
description: task.description ?? undefined,
|
|
417
|
+
status: task.status,
|
|
418
|
+
priority: task.priority,
|
|
419
|
+
tags: task.tags ?? undefined,
|
|
420
|
+
dueDate: task.dueDate ?? undefined,
|
|
421
|
+
estimatedHours: task.estimatedHours ?? undefined,
|
|
422
|
+
completed: task.completed ?? undefined,
|
|
423
|
+
createdAt: task.createdAt,
|
|
424
|
+
updatedAt: task.updatedAt,
|
|
425
|
+
}
|
|
426
|
+
} catch (error) {
|
|
427
|
+
console.error('TasksService.create error:', error)
|
|
428
|
+
throw error
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Update a task
|
|
434
|
+
*
|
|
435
|
+
* @param userId - Current user ID for RLS
|
|
436
|
+
* @param id - Task ID
|
|
437
|
+
* @param data - Data to update
|
|
438
|
+
* @returns Updated task
|
|
439
|
+
*
|
|
440
|
+
* @example
|
|
441
|
+
* const task = await TasksService.update(currentUserId, 'task-123', {
|
|
442
|
+
* status: 'done',
|
|
443
|
+
* completed: true
|
|
444
|
+
* })
|
|
445
|
+
*/
|
|
446
|
+
static async update(
|
|
447
|
+
userId: string,
|
|
448
|
+
id: string,
|
|
449
|
+
data: Partial<Omit<Task, 'id' | 'createdAt' | 'updatedAt'>>
|
|
450
|
+
): Promise<Task> {
|
|
451
|
+
try {
|
|
452
|
+
if (!userId) throw new Error('User ID is required')
|
|
453
|
+
if (!id) throw new Error('Task ID is required')
|
|
454
|
+
|
|
455
|
+
// Build dynamic update query
|
|
456
|
+
const updates: string[] = []
|
|
457
|
+
const params: unknown[] = [id]
|
|
458
|
+
let paramIndex = 2
|
|
459
|
+
|
|
460
|
+
if (data.title !== undefined) {
|
|
461
|
+
updates.push(`title = $${paramIndex}`)
|
|
462
|
+
params.push(data.title)
|
|
463
|
+
paramIndex++
|
|
464
|
+
}
|
|
465
|
+
if (data.description !== undefined) {
|
|
466
|
+
updates.push(`description = $${paramIndex}`)
|
|
467
|
+
params.push(data.description)
|
|
468
|
+
paramIndex++
|
|
469
|
+
}
|
|
470
|
+
if (data.status !== undefined) {
|
|
471
|
+
updates.push(`status = $${paramIndex}`)
|
|
472
|
+
params.push(data.status)
|
|
473
|
+
paramIndex++
|
|
474
|
+
}
|
|
475
|
+
if (data.priority !== undefined) {
|
|
476
|
+
updates.push(`priority = $${paramIndex}`)
|
|
477
|
+
params.push(data.priority)
|
|
478
|
+
paramIndex++
|
|
479
|
+
}
|
|
480
|
+
if (data.tags !== undefined) {
|
|
481
|
+
updates.push(`tags = $${paramIndex}`)
|
|
482
|
+
params.push(data.tags)
|
|
483
|
+
paramIndex++
|
|
484
|
+
}
|
|
485
|
+
if (data.dueDate !== undefined) {
|
|
486
|
+
updates.push(`"dueDate" = $${paramIndex}`)
|
|
487
|
+
params.push(data.dueDate)
|
|
488
|
+
paramIndex++
|
|
489
|
+
}
|
|
490
|
+
if (data.estimatedHours !== undefined) {
|
|
491
|
+
updates.push(`"estimatedHours" = $${paramIndex}`)
|
|
492
|
+
params.push(data.estimatedHours)
|
|
493
|
+
paramIndex++
|
|
494
|
+
}
|
|
495
|
+
if (data.completed !== undefined) {
|
|
496
|
+
updates.push(`completed = $${paramIndex}`)
|
|
497
|
+
params.push(data.completed)
|
|
498
|
+
paramIndex++
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
updates.push(`"updatedAt" = NOW()`)
|
|
502
|
+
|
|
503
|
+
if (updates.length === 1) { // Only updatedAt
|
|
504
|
+
// Nothing to update
|
|
505
|
+
const current = await this.getById(id, userId)
|
|
506
|
+
if (!current) throw new Error('Task not found')
|
|
507
|
+
return current
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const task = await queryOneWithRLS<DbTask>(
|
|
511
|
+
`
|
|
512
|
+
UPDATE tasks
|
|
513
|
+
SET ${updates.join(', ')}
|
|
514
|
+
WHERE id = $1
|
|
515
|
+
RETURNING *
|
|
516
|
+
`,
|
|
517
|
+
params,
|
|
518
|
+
userId
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
if (!task) throw new Error('Task not found or update failed')
|
|
522
|
+
|
|
523
|
+
return {
|
|
524
|
+
id: task.id,
|
|
525
|
+
title: task.title,
|
|
526
|
+
description: task.description ?? undefined,
|
|
527
|
+
status: task.status,
|
|
528
|
+
priority: task.priority,
|
|
529
|
+
tags: task.tags ?? undefined,
|
|
530
|
+
dueDate: task.dueDate ?? undefined,
|
|
531
|
+
estimatedHours: task.estimatedHours ?? undefined,
|
|
532
|
+
completed: task.completed ?? undefined,
|
|
533
|
+
createdAt: task.createdAt,
|
|
534
|
+
updatedAt: task.updatedAt,
|
|
535
|
+
}
|
|
536
|
+
} catch (error) {
|
|
537
|
+
console.error('TasksService.update error:', error)
|
|
538
|
+
throw error
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tasks Service Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for the TasksService.
|
|
5
|
+
* Tasks is a private entity with shared: false - users only see their own tasks.
|
|
6
|
+
*
|
|
7
|
+
* @module TasksTypes
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Task status values
|
|
12
|
+
*/
|
|
13
|
+
export type TaskStatus = 'todo' | 'in-progress' | 'review' | 'done' | 'blocked'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Task priority values
|
|
17
|
+
*/
|
|
18
|
+
export type TaskPriority = 'low' | 'medium' | 'high' | 'urgent'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Task entity
|
|
22
|
+
*/
|
|
23
|
+
export interface Task {
|
|
24
|
+
id: string
|
|
25
|
+
title: string
|
|
26
|
+
description?: string
|
|
27
|
+
status: TaskStatus
|
|
28
|
+
priority: TaskPriority
|
|
29
|
+
tags?: string[]
|
|
30
|
+
dueDate?: string
|
|
31
|
+
estimatedHours?: number
|
|
32
|
+
completed?: boolean
|
|
33
|
+
createdAt?: string
|
|
34
|
+
updatedAt?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Options for listing tasks
|
|
39
|
+
*/
|
|
40
|
+
export interface TaskListOptions {
|
|
41
|
+
limit?: number
|
|
42
|
+
offset?: number
|
|
43
|
+
status?: TaskStatus
|
|
44
|
+
priority?: TaskPriority
|
|
45
|
+
teamId?: string
|
|
46
|
+
orderBy?: 'title' | 'status' | 'priority' | 'dueDate' | 'createdAt'
|
|
47
|
+
orderDir?: 'asc' | 'desc'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Result of listing tasks with pagination
|
|
52
|
+
*/
|
|
53
|
+
export interface TaskListResult {
|
|
54
|
+
tasks: Task[]
|
|
55
|
+
total: number
|
|
56
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback } from 'react'
|
|
4
|
+
import { useMutation } from '@tanstack/react-query'
|
|
5
|
+
import { useTeamContext } from '@nextsparkjs/core/contexts/TeamContext'
|
|
6
|
+
|
|
7
|
+
export interface Message {
|
|
8
|
+
id: string
|
|
9
|
+
role: 'user' | 'assistant'
|
|
10
|
+
content: string
|
|
11
|
+
timestamp: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ChatResponse {
|
|
15
|
+
success: boolean
|
|
16
|
+
data?: {
|
|
17
|
+
message: string
|
|
18
|
+
sessionId: string
|
|
19
|
+
}
|
|
20
|
+
error?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function useAiChat() {
|
|
24
|
+
const [messages, setMessages] = useState<Message[]>([])
|
|
25
|
+
const [input, setInput] = useState('')
|
|
26
|
+
const [sessionId, setSessionId] = useState<string | undefined>(undefined)
|
|
27
|
+
const [error, setError] = useState<string | null>(null)
|
|
28
|
+
const { currentTeam } = useTeamContext()
|
|
29
|
+
|
|
30
|
+
const sendMutation = useMutation({
|
|
31
|
+
mutationFn: async (message: string): Promise<ChatResponse> => {
|
|
32
|
+
const headers: Record<string, string> = {
|
|
33
|
+
'Content-Type': 'application/json'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Include team context if available
|
|
37
|
+
if (currentTeam?.id) {
|
|
38
|
+
headers['x-team-id'] = currentTeam.id
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const response = await fetch('/api/v1/theme/default/ai/chat', {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers,
|
|
44
|
+
body: JSON.stringify({ message, sessionId })
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
const errorData = await response.json().catch(() => ({}))
|
|
49
|
+
throw new Error(errorData.error || 'Failed to send message')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return response.json()
|
|
53
|
+
},
|
|
54
|
+
onMutate: (message) => {
|
|
55
|
+
setError(null)
|
|
56
|
+
// Optimistic update - add user message immediately
|
|
57
|
+
const userMessage: Message = {
|
|
58
|
+
id: crypto.randomUUID(),
|
|
59
|
+
role: 'user',
|
|
60
|
+
content: message,
|
|
61
|
+
timestamp: Date.now()
|
|
62
|
+
}
|
|
63
|
+
setMessages(prev => [...prev, userMessage])
|
|
64
|
+
setInput('')
|
|
65
|
+
},
|
|
66
|
+
onSuccess: (data) => {
|
|
67
|
+
if (data.success && data.data) {
|
|
68
|
+
setSessionId(data.data.sessionId)
|
|
69
|
+
const aiMessage: Message = {
|
|
70
|
+
id: crypto.randomUUID(),
|
|
71
|
+
role: 'assistant',
|
|
72
|
+
content: data.data.message,
|
|
73
|
+
timestamp: Date.now()
|
|
74
|
+
}
|
|
75
|
+
setMessages(prev => [...prev, aiMessage])
|
|
76
|
+
} else if (data.error) {
|
|
77
|
+
setError(data.error)
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
onError: (err: Error) => {
|
|
81
|
+
setError(err.message)
|
|
82
|
+
// Add error message to chat
|
|
83
|
+
const errorMessage: Message = {
|
|
84
|
+
id: crypto.randomUUID(),
|
|
85
|
+
role: 'assistant',
|
|
86
|
+
content: `Error: ${err.message}`,
|
|
87
|
+
timestamp: Date.now()
|
|
88
|
+
}
|
|
89
|
+
setMessages(prev => [...prev, errorMessage])
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const sendMessage = useCallback(() => {
|
|
94
|
+
if (!input.trim() || sendMutation.isPending) return
|
|
95
|
+
sendMutation.mutate(input)
|
|
96
|
+
}, [input, sendMutation])
|
|
97
|
+
|
|
98
|
+
const clearChat = useCallback(() => {
|
|
99
|
+
setMessages([])
|
|
100
|
+
setSessionId(undefined)
|
|
101
|
+
setError(null)
|
|
102
|
+
setInput('')
|
|
103
|
+
}, [])
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
messages,
|
|
107
|
+
input,
|
|
108
|
+
setInput,
|
|
109
|
+
error,
|
|
110
|
+
isLoading: sendMutation.isPending,
|
|
111
|
+
sendMessage,
|
|
112
|
+
clearChat
|
|
113
|
+
}
|
|
114
|
+
}
|