@messenger-box/tailwind-ui-inbox 10.0.3-alpha.71 → 10.0.3-alpha.72

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 (148) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/lib/components/AIAgent/AIAgent.d.ts +14 -0
  3. package/lib/components/AIAgent/AIAgent.d.ts.map +1 -0
  4. package/lib/components/AIAgent/AIAgent.js +1148 -0
  5. package/lib/components/AIAgent/AIAgent.js.map +1 -0
  6. package/lib/components/AIAgent/index.d.ts +2 -0
  7. package/lib/components/AIAgent/index.d.ts.map +1 -0
  8. package/lib/components/InboxMessage/InputComponent.d.ts +9 -0
  9. package/lib/components/InboxMessage/InputComponent.d.ts.map +1 -0
  10. package/lib/components/InboxMessage/InputComponent.js +210 -0
  11. package/lib/components/InboxMessage/InputComponent.js.map +1 -0
  12. package/lib/components/InboxMessage/MessageInput.d.ts.map +1 -1
  13. package/lib/components/InboxMessage/MessageInput.js +14 -10
  14. package/lib/components/InboxMessage/MessageInput.js.map +1 -1
  15. package/lib/components/InboxMessage/MessageInputComponent.d.ts +9 -0
  16. package/lib/components/InboxMessage/MessageInputComponent.d.ts.map +1 -0
  17. package/lib/components/InboxMessage/MessageInputComponent.js +173 -0
  18. package/lib/components/InboxMessage/MessageInputComponent.js.map +1 -0
  19. package/lib/components/InboxMessage/Messages.d.ts.map +1 -1
  20. package/lib/components/InboxMessage/Messages.js +4 -54
  21. package/lib/components/InboxMessage/Messages.js.map +1 -1
  22. package/lib/components/InboxMessage/MessagesBuilderUi.d.ts +17 -0
  23. package/lib/components/InboxMessage/MessagesBuilderUi.d.ts.map +1 -0
  24. package/lib/components/InboxMessage/MessagesBuilderUi.js +162 -0
  25. package/lib/components/InboxMessage/MessagesBuilderUi.js.map +1 -0
  26. package/lib/components/InboxMessage/UploadImageButton.d.ts +1 -0
  27. package/lib/components/InboxMessage/UploadImageButton.d.ts.map +1 -1
  28. package/lib/components/InboxMessage/UploadImageButton.js +3 -3
  29. package/lib/components/InboxMessage/UploadImageButton.js.map +1 -1
  30. package/lib/components/InboxMessage/index.d.ts +3 -0
  31. package/lib/components/InboxMessage/index.d.ts.map +1 -1
  32. package/lib/components/InboxMessage/message-widgets/CommonMessage.d.ts.map +1 -1
  33. package/lib/components/InboxMessage/message-widgets/CommonMessage.js +11 -6
  34. package/lib/components/InboxMessage/message-widgets/CommonMessage.js.map +1 -1
  35. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts +14 -0
  36. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts.map +1 -0
  37. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js +1525 -0
  38. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js.map +1 -0
  39. package/lib/components/InboxMessage/message-widgets/PlainMessage.d.ts.map +1 -1
  40. package/lib/components/InboxMessage/message-widgets/PlainMessage.js +6 -3
  41. package/lib/components/InboxMessage/message-widgets/PlainMessage.js.map +1 -1
  42. package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.d.ts.map +1 -1
  43. package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.js +207 -12
  44. package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.js.map +1 -1
  45. package/lib/components/InboxMessage/message-widgets/index.d.ts +1 -0
  46. package/lib/components/InboxMessage/message-widgets/index.d.ts.map +1 -1
  47. package/lib/components/index.d.ts +2 -1
  48. package/lib/components/index.d.ts.map +1 -1
  49. package/lib/compute.d.ts.map +1 -1
  50. package/lib/compute.js +79 -3
  51. package/lib/compute.js.map +1 -1
  52. package/lib/config/env-config.d.ts +6 -0
  53. package/lib/config/env-config.d.ts.map +1 -1
  54. package/lib/config/env-config.js +19 -1
  55. package/lib/config/env-config.js.map +1 -1
  56. package/lib/container/AiInbox.d.ts +15 -0
  57. package/lib/container/AiInbox.d.ts.map +1 -0
  58. package/lib/container/AiInbox.js +1520 -0
  59. package/lib/container/AiInbox.js.map +1 -0
  60. package/lib/container/AiInboxWithLoader.d.ts +36 -0
  61. package/lib/container/AiInboxWithLoader.d.ts.map +1 -0
  62. package/lib/container/AiInboxWithLoader.js +300 -0
  63. package/lib/container/AiInboxWithLoader.js.map +1 -0
  64. package/lib/container/AiLandingInput.d.ts +4 -0
  65. package/lib/container/AiLandingInput.d.ts.map +1 -0
  66. package/lib/container/AiLandingInput.js +164 -0
  67. package/lib/container/AiLandingInput.js.map +1 -0
  68. package/lib/container/Inbox.d.ts.map +1 -1
  69. package/lib/container/Inbox.js +6 -4
  70. package/lib/container/Inbox.js.map +1 -1
  71. package/lib/container/InboxAiMessagesLoader.d.ts +36 -0
  72. package/lib/container/InboxAiMessagesLoader.d.ts.map +1 -0
  73. package/lib/container/InboxAiMessagesLoader.js +47 -0
  74. package/lib/container/InboxAiMessagesLoader.js.map +1 -0
  75. package/lib/container/InboxContainer.d.ts +12 -0
  76. package/lib/container/InboxContainer.d.ts.map +1 -0
  77. package/lib/container/InboxContainer.js +31 -0
  78. package/lib/container/InboxContainer.js.map +1 -0
  79. package/lib/container/InboxTemplate1.d.ts +15 -0
  80. package/lib/container/InboxTemplate1.d.ts.map +1 -0
  81. package/lib/container/InboxTemplate1.js +1375 -0
  82. package/lib/container/InboxTemplate1.js.map +1 -0
  83. package/lib/container/InboxTemplate1WithLoader.d.ts +36 -0
  84. package/lib/container/InboxTemplate1WithLoader.d.ts.map +1 -0
  85. package/lib/container/InboxTemplate2.d.ts +15 -0
  86. package/lib/container/InboxTemplate2.d.ts.map +1 -0
  87. package/lib/container/InboxTemplate2.js +1426 -0
  88. package/lib/container/InboxTemplate2.js.map +1 -0
  89. package/lib/container/InboxWithAiLoader.d.ts +15 -0
  90. package/lib/container/InboxWithAiLoader.d.ts.map +1 -0
  91. package/lib/container/InboxWithAiLoader.js +56 -0
  92. package/lib/container/InboxWithAiLoader.js.map +1 -0
  93. package/lib/container/ServiceInbox.js +1 -1
  94. package/lib/container/ServiceInbox.js.map +1 -1
  95. package/lib/container/ThreadMessages.js +1 -1
  96. package/lib/container/ThreadMessages.js.map +1 -1
  97. package/lib/container/ThreadMessagesInbox.js +1 -1
  98. package/lib/container/ThreadMessagesInbox.js.map +1 -1
  99. package/lib/container/Threads.js +1 -1
  100. package/lib/container/Threads.js.map +1 -1
  101. package/lib/container/index.d.ts +4 -1
  102. package/lib/container/index.d.ts.map +1 -1
  103. package/lib/index.js +1 -1
  104. package/lib/machines/aiAgentMachine.d.ts +3 -0
  105. package/lib/machines/aiAgentMachine.d.ts.map +1 -0
  106. package/lib/machines/aiAgentMachine.js +1040 -0
  107. package/lib/machines/aiAgentMachine.js.map +1 -0
  108. package/lib/machines/types.d.ts +77 -0
  109. package/lib/machines/types.d.ts.map +1 -0
  110. package/lib/routes.json +40 -0
  111. package/lib/templates/InboxWithAi.d.ts +15 -0
  112. package/lib/templates/InboxWithAi.d.ts.map +1 -0
  113. package/lib/templates/InboxWithAi.js +405 -0
  114. package/lib/templates/InboxWithAi.js.map +1 -0
  115. package/lib/templates/InboxWithAi.tsx +502 -0
  116. package/package.json +7 -5
  117. package/src/components/AIAgent/AIAgent.tsx +1351 -0
  118. package/src/components/AIAgent/README.md +82 -0
  119. package/src/components/AIAgent/index.ts +1 -0
  120. package/src/components/InboxMessage/InputComponent.tsx +263 -0
  121. package/src/components/InboxMessage/MessageInput.tsx +73 -66
  122. package/src/components/InboxMessage/MessageInputComponent.tsx +245 -0
  123. package/src/components/InboxMessage/Messages.tsx +2 -56
  124. package/src/components/InboxMessage/MessagesBuilderUi.tsx +205 -0
  125. package/src/components/InboxMessage/UploadImageButton.tsx +3 -2
  126. package/src/components/InboxMessage/index.ts +3 -0
  127. package/src/components/InboxMessage/message-widgets/CommonMessage.tsx +39 -21
  128. package/src/components/InboxMessage/message-widgets/ModernMessageGroup.tsx +1968 -0
  129. package/src/components/InboxMessage/message-widgets/PlainMessage.tsx +6 -2
  130. package/src/components/InboxMessage/message-widgets/SlackLikeMessageGroup.tsx +306 -54
  131. package/src/components/InboxMessage/message-widgets/index.ts +1 -0
  132. package/src/components/index.ts +4 -0
  133. package/src/compute.ts +83 -2
  134. package/src/config/env-config.ts +6 -0
  135. package/src/container/AiInbox.tsx +1796 -0
  136. package/src/container/AiInboxWithLoader.tsx +356 -0
  137. package/src/container/AiLandingInput.tsx +168 -0
  138. package/src/container/Inbox.tsx +8 -5
  139. package/src/container/InboxAiMessagesLoader.tsx +68 -0
  140. package/src/container/InboxContainer.tsx +35 -0
  141. package/src/container/InboxTemplate1.tsx +1542 -0
  142. package/src/container/InboxTemplate1WithLoader.tsx +338 -0
  143. package/src/container/InboxTemplate2.tsx +1606 -0
  144. package/src/container/InboxWithAiLoader.tsx +76 -0
  145. package/src/container/index.ts +15 -1
  146. package/src/machines/aiAgentMachine.ts +1248 -0
  147. package/src/machines/types.ts +59 -0
  148. package/src/templates/InboxWithAi.tsx +502 -0
@@ -0,0 +1,1351 @@
1
+ import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react';
2
+ import { useActor, useMachine } from '@xstate/react';
3
+ import { aiAgentMachine } from '../../machines/aiAgentMachine';
4
+ import { Message } from '../../machines/types';
5
+ import { InputComponent } from '../InboxMessage/InputComponent';
6
+ import { format, isToday, isYesterday, formatDistanceToNow } from 'date-fns';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { ModernMessageGroupComponent } from '../InboxMessage/message-widgets/ModernMessageGroup';
9
+ import { useUploadFiles } from '@messenger-box/platform-client';
10
+ import { IFileInfo, PostTypeEnum } from 'common';
11
+ import { useSelector, shallowEqual } from 'react-redux';
12
+ import { Store, userSelector } from '@adminide-stack/user-auth0-client';
13
+ import { IUserState } from '@adminide-stack/core';
14
+ import { objectId } from '@messenger-box/core';
15
+ import { useApolloClient } from '@apollo/client';
16
+ import {
17
+ useSendMessagesMutation,
18
+ MessagesDocument,
19
+ useMessagesQuery,
20
+ OnChatMessageAddedDocument as CHAT_MESSAGE_ADDED,
21
+ } from 'common/graphql';
22
+ import { config } from '../../config';
23
+ import { orderBy, uniqBy } from 'lodash-es';
24
+ import { useParams } from '@remix-run/react';
25
+ import { RoomType } from 'common';
26
+ import { SubscriptionHandler } from '../InboxMessage/SubscriptionHandler';
27
+
28
+ const { MESSAGES_PER_PAGE } = config;
29
+
30
+ interface AIAgentProps {
31
+ channelId?: string;
32
+ onSendMessage?: (message: string, files?: File[]) => Promise<void>;
33
+ placeholder?: string;
34
+ className?: string;
35
+ currentUser?: any;
36
+ channelName?: string;
37
+ isDesktopView?: boolean;
38
+ isSmallScreen?: boolean;
39
+ }
40
+
41
+ export const AIAgent: React.FC<AIAgentProps> = ({
42
+ channelId,
43
+ onSendMessage,
44
+ placeholder = 'Ask me anything...',
45
+ className = '',
46
+ currentUser,
47
+ isDesktopView = false,
48
+ isSmallScreen = false,
49
+ }) => {
50
+ const [state, send] = useMachine(aiAgentMachine);
51
+ const apolloClient = useApolloClient();
52
+ const { startUpload } = useUploadFiles();
53
+ const [sendMsg] = useSendMessagesMutation();
54
+ const auth: any = useSelector<Store.Auth, IUserState>(userSelector, shallowEqual);
55
+ const { id: pathChannelId } = useParams();
56
+
57
+ // Get channelId from props or path params
58
+ const actualChannelId = channelId || pathChannelId;
59
+
60
+ // Direct message query from InboxWithAiLoader.tsx
61
+ const messagesQuery = useMessagesQuery({
62
+ variables: {
63
+ channelId: actualChannelId?.toString(),
64
+ parentId: null,
65
+ limit: MESSAGES_PER_PAGE,
66
+ },
67
+ skip: !actualChannelId,
68
+ fetchPolicy: 'cache-and-network',
69
+ errorPolicy: 'all',
70
+ notifyOnNetworkStatusChange: true,
71
+ returnPartialData: true,
72
+ pollInterval: 0,
73
+ context: {
74
+ cacheKey: 'messages-list',
75
+ },
76
+ });
77
+
78
+ const { messages: aiMessages, error, isTyping } = state.context;
79
+ const { t } = useTranslation('translations');
80
+
81
+ const [isOpen, setIsOpen] = useState(false);
82
+ const [selectedElement, setSelectedElement] = useState<any>(null);
83
+ const [currentProcessingMessage, setCurrentProcessingMessage] = useState<number | null>(null);
84
+ const [processedMessageIds, setProcessedMessageIds] = useState<Set<string>>(new Set());
85
+ const [activeTab, setActiveTab] = useState<'chat' | 'history'>('chat');
86
+ const bottomRef = useRef<HTMLDivElement | null>(null);
87
+
88
+ // New state variables for message display control
89
+ const [displayedMessageCount, setDisplayedMessageCount] = useState(1); // Initially show only 1 message
90
+ const [hasMoreMessages, setHasMoreMessages] = useState(false);
91
+
92
+ // Get regular messages from direct query
93
+ const {
94
+ data: messagesData,
95
+ loading: messageLoading,
96
+ fetchMore: fetchMoreMessages,
97
+ subscribeToMore,
98
+ } = messagesQuery;
99
+
100
+ const regularMessages = useMemo(() => {
101
+ if (!messagesData?.messages?.data) return [];
102
+ return orderBy(uniqBy(messagesData.messages.data || [], 'id'), ['createdAt'], ['asc']);
103
+ }, [messagesData?.messages?.data]);
104
+
105
+ const onOpen = (element?: any) => {
106
+ setSelectedElement(element);
107
+ setIsOpen(true);
108
+ };
109
+
110
+ const onClose = () => {
111
+ setIsOpen(false);
112
+ setSelectedElement(null);
113
+ };
114
+
115
+ // Enhanced scroll to bottom function
116
+ const scrollToBottom = useCallback((behavior: ScrollBehavior = 'smooth', delay: number = 0) => {
117
+ setTimeout(() => {
118
+ if (bottomRef.current) {
119
+ bottomRef.current.scrollIntoView({
120
+ behavior,
121
+ block: 'end',
122
+ inline: 'nearest',
123
+ });
124
+ }
125
+ }, delay);
126
+ }, []);
127
+
128
+ // Updated handleSend function from InboxWithAi.tsx
129
+ const handleSend = useCallback(
130
+ async (message: string, files: any[] = []) => {
131
+ // Allow sending if there's either a message or files
132
+ if ((!message || !message.trim()) && (!files || files.length === 0)) return;
133
+ if (!actualChannelId) return;
134
+
135
+ try {
136
+ const postId = objectId();
137
+ const currentDate = new Date();
138
+
139
+ const createOptimisticMessage = (files?: any[]) => ({
140
+ __typename: 'Post' as const,
141
+ id: postId,
142
+ message,
143
+ createdAt: currentDate.toISOString(),
144
+ updatedAt: currentDate.toISOString(),
145
+ author: {
146
+ __typename: 'UserAccount' as const,
147
+ id: currentUser?.id,
148
+ givenName: currentUser?.profile?.given_name || '',
149
+ familyName: currentUser?.profile?.family_name || '',
150
+ email: currentUser?.profile?.email || '',
151
+ username: currentUser?.profile?.nickname || '',
152
+ fullName: currentUser?.profile?.name || '',
153
+ picture: currentUser?.profile?.picture || '',
154
+ alias: [currentUser?.authUserId ?? ''],
155
+ tokens: [],
156
+ },
157
+ isDelivered: false, // Will be true once confirmed by server
158
+ isRead: false,
159
+ type: 'Simple' as any,
160
+ parentId: null,
161
+ fromServer: false,
162
+ channel: {
163
+ __typename: 'Channel' as const,
164
+ id: actualChannelId,
165
+ },
166
+ propsConfiguration: {
167
+ __typename: 'MachineConfiguration' as const,
168
+ id: null,
169
+ resource: '' as any,
170
+ contents: null,
171
+ keys: null,
172
+ target: null,
173
+ overrides: null,
174
+ },
175
+ props: {},
176
+ files: {
177
+ __typename: 'FilesInfo' as const,
178
+ data: files || [],
179
+ totalCount: files?.length || 0,
180
+ },
181
+ replies: {
182
+ __typename: 'Messages' as const,
183
+ data: [],
184
+ totalCount: 0,
185
+ },
186
+ });
187
+
188
+ const optimisticMessage = createOptimisticMessage(files);
189
+
190
+ if (files?.length > 0) {
191
+ const uploadResponse = await startUpload({
192
+ file: files,
193
+ saveUploadedFile: { variables: { postId } },
194
+ createUploadLink: { variables: { postId } },
195
+ });
196
+
197
+ const uploadedFiles = uploadResponse.data as unknown as IFileInfo[];
198
+ if (uploadedFiles) {
199
+ const fileIds = uploadedFiles.map((f: any) => f.id);
200
+ await sendMsg({
201
+ variables: { postId, channelId: actualChannelId, content: message, files: fileIds },
202
+ optimisticResponse: {
203
+ __typename: 'Mutation',
204
+ sendMessage: createOptimisticMessage(uploadedFiles),
205
+ },
206
+ update: (cache, { data: mutationData }) => {
207
+ if (!mutationData?.sendMessage) return;
208
+
209
+ // Update messages cache optimistically
210
+ const messagesQuery = {
211
+ query: MessagesDocument,
212
+ variables: {
213
+ channelId: actualChannelId.toString(),
214
+ parentId: null,
215
+ limit: MESSAGES_PER_PAGE,
216
+ },
217
+ };
218
+
219
+ try {
220
+ const existingData = cache.readQuery(messagesQuery) as any;
221
+ if (existingData?.messages) {
222
+ cache.writeQuery({
223
+ ...messagesQuery,
224
+ data: {
225
+ messages: {
226
+ ...existingData.messages,
227
+ data: [
228
+ ...(existingData.messages.data || []),
229
+ mutationData.sendMessage,
230
+ ],
231
+ totalCount: (existingData.messages.totalCount || 0) + 1,
232
+ },
233
+ },
234
+ });
235
+ }
236
+ } catch (error) {
237
+ console.debug('Cache update failed (expected on first message):', error);
238
+ }
239
+ },
240
+ });
241
+ }
242
+ } else {
243
+ await sendMsg({
244
+ variables: { channelId: actualChannelId, content: message },
245
+ optimisticResponse: {
246
+ __typename: 'Mutation',
247
+ sendMessage: optimisticMessage,
248
+ },
249
+ update: (cache, { data: mutationData }) => {
250
+ if (!mutationData?.sendMessage) return;
251
+
252
+ // Update messages cache optimistically
253
+ const messagesQuery = {
254
+ query: MessagesDocument,
255
+ variables: {
256
+ channelId: actualChannelId.toString(),
257
+ parentId: null,
258
+ limit: MESSAGES_PER_PAGE,
259
+ },
260
+ };
261
+
262
+ try {
263
+ const existingData = cache.readQuery(messagesQuery) as any;
264
+ if (existingData?.messages) {
265
+ cache.writeQuery({
266
+ ...messagesQuery,
267
+ data: {
268
+ messages: {
269
+ ...existingData.messages,
270
+ data: [...(existingData.messages.data || []), mutationData.sendMessage],
271
+ totalCount: (existingData.messages.totalCount || 0) + 1,
272
+ },
273
+ },
274
+ });
275
+ }
276
+ } catch (error) {
277
+ console.debug('Cache update failed (expected on first message):', error);
278
+ }
279
+ },
280
+ });
281
+ }
282
+
283
+ // Send message to AI agent machine
284
+ send({ type: 'SEND_MESSAGE', message: message.trim() });
285
+
286
+ // Ensure we scroll to the newest message immediately
287
+ scrollToBottom('smooth', 0);
288
+
289
+ // When sending new messages, increment the displayed count to show the new message
290
+ // This ensures we show both the existing last message and the new message
291
+ setDisplayedMessageCount((prev) => {
292
+ const newCount = Math.max(prev + 1, 2);
293
+ console.log('🔄 Message sent: Updating displayedMessageCount', {
294
+ previous: prev,
295
+ new: newCount,
296
+ reason: 'New message sent',
297
+ });
298
+ return newCount;
299
+ });
300
+
301
+ // Call the optional onSendMessage prop if provided
302
+ if (onSendMessage) {
303
+ await onSendMessage(message, files);
304
+ }
305
+ } catch (error) {
306
+ console.error('Error sending message:', error);
307
+ }
308
+ },
309
+ [actualChannelId, currentUser, startUpload, sendMsg, send, onSendMessage],
310
+ );
311
+
312
+ // Debug effect to track AI messages changes
313
+ useEffect(() => {
314
+ console.log('🤖 AI messages state changed:', aiMessages.length);
315
+ if (aiMessages.length > 0) {
316
+ console.log('🤖 Latest AI message:', aiMessages[aiMessages.length - 1]);
317
+
318
+ // Clear processing message if we have new AI responses
319
+ if (currentProcessingMessage !== null) {
320
+ console.log('🤖 AI response received, clearing processing message');
321
+ setCurrentProcessingMessage(null);
322
+ }
323
+
324
+ // Auto-scroll to bottom when new AI response appears
325
+ scrollToBottom('smooth', 200);
326
+ } else {
327
+ console.log('🤖 No AI messages in state - this might indicate an issue');
328
+ }
329
+ }, [aiMessages, currentProcessingMessage]);
330
+
331
+ // Debug effect to track machine state changes
332
+ useEffect(() => {
333
+ console.log('🤖 Machine state changed:', state.value);
334
+ console.log('🤖 Machine context:', {
335
+ messagesCount: state.context.messages.length,
336
+ hasMessageToRespondTo: !!state.context.messageToRespondTo,
337
+ messageToRespondTo: state.context.messageToRespondTo,
338
+ });
339
+
340
+ // Check if we're in processing state but no AI response is being generated
341
+ if (state.value === 'processing' && !state.context.messageToRespondTo) {
342
+ console.log('🤖 ⚠️ WARNING: In processing state but no messageToRespondTo!');
343
+ }
344
+
345
+ // Auto-scroll when AI starts processing
346
+ if (state.value === 'processing') {
347
+ scrollToBottom('smooth', 100);
348
+ }
349
+ }, [state.value, state.context, scrollToBottom]);
350
+
351
+ // Send regular messages to AI agent machine for context
352
+ useEffect(() => {
353
+ if (regularMessages && regularMessages.length > 0) {
354
+ send({ type: 'UPDATE_REGULAR_MESSAGES', messages: regularMessages });
355
+ }
356
+ }, [regularMessages, send]);
357
+
358
+ // Reset processed messages when regular messages change completely
359
+ useEffect(() => {
360
+ if (regularMessages && regularMessages.length > 0) {
361
+ // Check if we have completely new messages (different IDs)
362
+ const currentMessageIds = new Set(regularMessages.map((msg: any) => msg.id));
363
+ const hasNewMessages = !regularMessages.every((msg: any) => processedMessageIds.has(msg.id));
364
+
365
+ if (hasNewMessages) {
366
+ console.log('🤖 New messages detected, resetting processed state');
367
+ setProcessedMessageIds(new Set());
368
+ setCurrentProcessingMessage(null);
369
+ }
370
+ }
371
+ }, [regularMessages]);
372
+
373
+ // Sequential auto-response logic - respond to each message one at a time
374
+ useEffect(() => {
375
+ if (regularMessages && regularMessages.length > 0) {
376
+ console.log('🤖 Regular messages detected:', regularMessages.length);
377
+ console.log('🤖 Current AI messages:', aiMessages.length);
378
+ console.log('🤖 Already processed message IDs:', Array.from(processedMessageIds));
379
+
380
+ // Find messages that don't have AI responses yet and haven't been processed
381
+ const messagesWithoutAIResponses = regularMessages.filter((msg: any, index: number) => {
382
+ // Check if this message already has an AI response
383
+ const hasAIResponse = aiMessages.length > index;
384
+ // Check if this message has already been processed
385
+ const alreadyProcessed = processedMessageIds.has(msg.id);
386
+ return !hasAIResponse && !alreadyProcessed;
387
+ });
388
+
389
+ if (messagesWithoutAIResponses.length > 0) {
390
+ console.log(`🤖 Found ${messagesWithoutAIResponses.length} messages to process`);
391
+
392
+ // Simplified: Just process the first message and let the machine handle the rest
393
+ const firstMsg = messagesWithoutAIResponses[0];
394
+ console.log(`🤖 Starting with first message: ${firstMsg.message?.substring(0, 50)}...`);
395
+
396
+ // Mark this message as being processed
397
+ setProcessedMessageIds((prev) => new Set([...prev, firstMsg.id]));
398
+ setCurrentProcessingMessage(0);
399
+
400
+ // Send the first message to AI
401
+ send({ type: 'AUTO_RESPOND_TO_MESSAGE', message: firstMsg.message, isAutoResponse: true });
402
+ } else {
403
+ console.log('🤖 All messages already have AI responses or are being processed');
404
+ setCurrentProcessingMessage(null);
405
+ }
406
+ }
407
+ }, [regularMessages.length, aiMessages.length, processedMessageIds.size]); // Only depend on lengths and processed count
408
+
409
+ // Process next message when machine becomes idle (after completing AI response)
410
+ useEffect(() => {
411
+ // Only run when machine is idle and we have messages to process
412
+ if (state.matches('idle') && regularMessages && regularMessages.length > 0) {
413
+ console.log('🤖 Machine idle, checking for next message to process...');
414
+ console.log(
415
+ '🤖 Current state - AI messages:',
416
+ aiMessages.length,
417
+ 'Processed IDs:',
418
+ processedMessageIds.size,
419
+ );
420
+
421
+ // Find the next message that needs an AI response
422
+ const nextMessageIndex = regularMessages.findIndex((msg: any, index: number) => {
423
+ const hasAIResponse = aiMessages.length > index;
424
+ const alreadyProcessed = processedMessageIds.has(msg.id);
425
+ const needsResponse = !hasAIResponse && !alreadyProcessed;
426
+
427
+ console.log(
428
+ `🤖 Message ${index + 1} "${msg.message?.substring(
429
+ 0,
430
+ 30,
431
+ )}..." - hasAIResponse: ${hasAIResponse}, alreadyProcessed: ${alreadyProcessed}, needsResponse: ${needsResponse}`,
432
+ );
433
+
434
+ return needsResponse;
435
+ });
436
+
437
+ if (nextMessageIndex !== -1) {
438
+ console.log(
439
+ `🤖 Found next message to process: ${nextMessageIndex + 1}: ${regularMessages[
440
+ nextMessageIndex
441
+ ].message?.substring(0, 50)}...`,
442
+ );
443
+
444
+ // Check if this message is a duplicate of a previous one
445
+ const currentMessage = regularMessages[nextMessageIndex];
446
+ const previousMessageIndex = regularMessages.findIndex(
447
+ (prevMsg: any, prevIndex: number) =>
448
+ prevIndex < nextMessageIndex && prevMsg.message === currentMessage.message,
449
+ );
450
+
451
+ if (previousMessageIndex !== -1 && aiMessages.length > previousMessageIndex) {
452
+ // This is a duplicate message, mark it as processed without sending to AI
453
+ console.log(
454
+ `🤖 Duplicate message detected: "${
455
+ currentMessage.message
456
+ }" - reusing AI response from message ${previousMessageIndex + 1}`,
457
+ );
458
+ setProcessedMessageIds((prev) => new Set([...prev, currentMessage.id]));
459
+ setCurrentProcessingMessage(null);
460
+
461
+ // Continue with next message immediately
462
+ setTimeout(() => {
463
+ send({ type: 'CONTINUE_PROCESSING' });
464
+ }, 100);
465
+ } else {
466
+ // Mark this message as being processed
467
+ setProcessedMessageIds((prev) => new Set([...prev, currentMessage.id]));
468
+ setCurrentProcessingMessage(nextMessageIndex);
469
+
470
+ // Send to AI
471
+ console.log(`🤖 Sending message to AI: "${currentMessage.message}"`);
472
+ send({ type: 'AUTO_RESPOND_TO_MESSAGE', message: currentMessage.message, isAutoResponse: true });
473
+ }
474
+ } else {
475
+ // Check if we actually have all responses
476
+ const totalMessagesNeedingResponses = regularMessages.length;
477
+ const messagesWithResponses = aiMessages.length;
478
+ const messagesProcessed = processedMessageIds.size;
479
+
480
+ console.log(`🤖 No more messages to process. Summary:`, {
481
+ totalMessages: totalMessagesNeedingResponses,
482
+ messagesWithResponses,
483
+ messagesProcessed,
484
+ allHaveResponses: messagesWithResponses >= totalMessagesNeedingResponses,
485
+ });
486
+
487
+ if (messagesWithResponses >= totalMessagesNeedingResponses) {
488
+ console.log('🤖 All messages have been processed successfully');
489
+ setCurrentProcessingMessage(null);
490
+ } else {
491
+ console.log('🤖 ⚠️ Discrepancy detected: Some messages may not have proper AI responses');
492
+ // Force a reset to try again
493
+ setProcessedMessageIds(new Set());
494
+ setCurrentProcessingMessage(null);
495
+ }
496
+ }
497
+ }
498
+ }, [state.value, regularMessages, aiMessages.length, processedMessageIds.size, send]);
499
+
500
+ // Reset processing state when regularMessages change significantly
501
+ useEffect(() => {
502
+ if (regularMessages && regularMessages.length > 0) {
503
+ // Reset processing state when we get new messages
504
+ setProcessedMessageIds(new Set());
505
+ setCurrentProcessingMessage(null);
506
+ console.log('🤖 Reset processing state for new messages');
507
+ }
508
+ }, [regularMessages.length]); // Only depend on length to avoid unnecessary resets
509
+
510
+ // Only reset displayed message count on initial load, not when new messages arrive
511
+ useEffect(() => {
512
+ if (regularMessages && regularMessages.length > 0) {
513
+ // Only reset to show last message if this is the first time we're getting messages
514
+ // or if we're currently showing more messages than we have
515
+ if (displayedMessageCount === 1 || displayedMessageCount > regularMessages.length) {
516
+ console.log('🔄 Resetting displayedMessageCount to 1:', {
517
+ reason: 'Initial load or count mismatch',
518
+ currentDisplayed: displayedMessageCount,
519
+ regularMessagesLength: regularMessages.length,
520
+ });
521
+ setDisplayedMessageCount(1);
522
+ }
523
+ }
524
+ }, [regularMessages.length, displayedMessageCount]);
525
+
526
+ const handleRetry = useCallback(() => {
527
+ send({ type: 'RETRY' });
528
+ }, [send]);
529
+
530
+ const handleClearError = useCallback(() => {
531
+ send({ type: 'CLEAR_ERROR' });
532
+ }, [send]);
533
+
534
+ // Merge AI agent messages with regular messages from query
535
+ const allMessages = useMemo(() => {
536
+ if (!regularMessages) return [] as any[];
537
+
538
+ console.log(
539
+ '🔄 Merging messages - AI messages:',
540
+ aiMessages.length,
541
+ 'Regular messages:',
542
+ regularMessages.length,
543
+ );
544
+ console.log(
545
+ '🔄 AI messages content:',
546
+ aiMessages.map((m) => ({ id: m.id, content: m.content.substring(0, 50) + '...', sender: m.sender })),
547
+ );
548
+
549
+ const regularMessagesFormatted = (regularMessages || []).map((m, idx) => ({
550
+ id: m.id || `regular-${idx}`,
551
+ message: (m as any).message || (m as any).content || '',
552
+ createdAt: new Date((m as any).createdAt || Date.now()),
553
+ sender: 'user',
554
+ author: (m as any).author,
555
+ isUserMessage: true, // Explicitly mark as user message
556
+ }));
557
+
558
+ const aiMessagesFormatted = (aiMessages || []).map((m) => ({
559
+ id: m.id,
560
+ message: m.content,
561
+ createdAt: new Date(m.timestamp || Date.now()),
562
+ sender: 'ai',
563
+ isUserMessage: false, // Explicitly mark as AI message
564
+ }));
565
+
566
+ // Show ALL user messages immediately, interleave AI responses as they arrive
567
+ const interleavedMessages: any[] = [];
568
+
569
+ console.log('🔄 Starting message interleaving...');
570
+ console.log('🔄 Regular messages count:', regularMessagesFormatted.length);
571
+ console.log('🔄 AI messages count:', aiMessagesFormatted.length);
572
+
573
+ for (let i = 0; i < regularMessagesFormatted.length; i++) {
574
+ const userMsg = regularMessagesFormatted[i];
575
+ console.log(`🔄 Processing user message ${i + 1}:`, {
576
+ id: userMsg.id,
577
+ message: userMsg.message.substring(0, 50),
578
+ sender: userMsg.sender,
579
+ isUserMessage: userMsg.isUserMessage,
580
+ author: userMsg.author,
581
+ });
582
+
583
+ interleavedMessages.push(userMsg);
584
+ console.log(`🔄 Added user message ${i + 1}: ${userMsg.message.substring(0, 50)}...`);
585
+
586
+ // Check if this message is a duplicate of a previous one
587
+ const isDuplicate =
588
+ i > 0 && regularMessagesFormatted.slice(0, i).some((prevMsg) => prevMsg.message === userMsg.message);
589
+
590
+ if (isDuplicate) {
591
+ // Find the first occurrence of this message to get its AI response
592
+ const firstOccurrenceIndex = regularMessagesFormatted.findIndex(
593
+ (msg) => msg.message === userMsg.message,
594
+ );
595
+ const hasAIResponseForFirst = firstOccurrenceIndex < aiMessagesFormatted.length;
596
+
597
+ if (hasAIResponseForFirst) {
598
+ // Reuse the AI response from the first occurrence
599
+ const aiResponse = aiMessagesFormatted[firstOccurrenceIndex];
600
+ const adjustedCreatedAt = new Date(userMsg.createdAt.getTime() + 1);
601
+ const finalAiResponse = {
602
+ ...aiResponse,
603
+ createdAt: adjustedCreatedAt,
604
+ id: `duplicate-${aiResponse.id}-${i}`,
605
+ isDuplicate: true,
606
+ };
607
+ interleavedMessages.push(finalAiResponse);
608
+ console.log(
609
+ `🔄 Added duplicate AI response for message ${i + 1} (reusing from message ${
610
+ firstOccurrenceIndex + 1
611
+ }):`,
612
+ {
613
+ id: finalAiResponse.id,
614
+ message: finalAiResponse.message.substring(0, 50),
615
+ sender: finalAiResponse.sender,
616
+ isUserMessage: finalAiResponse.isUserMessage,
617
+ isDuplicate: finalAiResponse.isDuplicate,
618
+ },
619
+ );
620
+ } else {
621
+ // First occurrence doesn't have AI response yet, show loading
622
+ const loadingMessage = {
623
+ id: `loading-duplicate-${i}`,
624
+ message: 'Loading...', // Shorter text since we show visual loader
625
+ createdAt: new Date(userMsg.createdAt.getTime() + 1),
626
+ sender: 'ai',
627
+ isLoading: true,
628
+ isProcessing: false,
629
+ isUserMessage: false,
630
+ isDuplicate: true,
631
+ };
632
+ interleavedMessages.push(loadingMessage);
633
+ console.log(`🔄 Added loading indicator for duplicate message ${i + 1}:`, {
634
+ id: loadingMessage.id,
635
+ message: loadingMessage.message,
636
+ isDuplicate: loadingMessage.isDuplicate,
637
+ });
638
+ }
639
+ } else {
640
+ // This is not a duplicate, handle normally
641
+ if (i < aiMessagesFormatted.length) {
642
+ const aiResponse = aiMessagesFormatted[i];
643
+ // Ensure AI response sorts after corresponding user message
644
+ const adjustedCreatedAt = new Date(userMsg.createdAt.getTime() + 1);
645
+ const finalAiResponse = { ...aiResponse, createdAt: adjustedCreatedAt };
646
+ interleavedMessages.push(finalAiResponse);
647
+ console.log(`🔄 Added AI response for message ${i + 1}:`, {
648
+ id: finalAiResponse.id,
649
+ message: finalAiResponse.message.substring(0, 50),
650
+ sender: finalAiResponse.sender,
651
+ isUserMessage: finalAiResponse.isUserMessage,
652
+ });
653
+ } else {
654
+ // Add a loading indicator for messages waiting for AI responses
655
+ const loadingMessage = {
656
+ id: `loading-${i}`,
657
+ message: 'Loading...', // Shorter text since we show visual loader
658
+ createdAt: new Date(userMsg.createdAt.getTime() + 1),
659
+ sender: 'ai',
660
+ isLoading: true,
661
+ isProcessing: currentProcessingMessage === i,
662
+ isUserMessage: false, // Explicitly mark as not a user message
663
+ };
664
+ interleavedMessages.push(loadingMessage);
665
+ console.log(`🔄 Added loading indicator for message ${i + 1}:`, {
666
+ id: loadingMessage.id,
667
+ message: loadingMessage.message,
668
+ sender: loadingMessage.sender,
669
+ isUserMessage: loadingMessage.isUserMessage,
670
+ isProcessing: loadingMessage.isProcessing,
671
+ });
672
+ }
673
+ }
674
+ }
675
+
676
+ console.log('🔄 Final interleaved messages:', interleavedMessages.length);
677
+ console.log(
678
+ '🔄 Message structure:',
679
+ interleavedMessages.map((m) => ({
680
+ sender: m.sender,
681
+ isUserMessage: m.isUserMessage,
682
+ message: m.message.substring(0, 30) + '...',
683
+ })),
684
+ );
685
+
686
+ return interleavedMessages;
687
+ }, [aiMessages, regularMessages, currentUser, currentProcessingMessage]);
688
+
689
+ // Update hasMoreMessages when allMessages changes
690
+ useEffect(() => {
691
+ if (allMessages && allMessages.length > 0) {
692
+ // Count complete conversation pairs instead of individual messages
693
+ const conversationPairs = Math.ceil(allMessages.length / 2);
694
+ console.log('🔄 Conversation pairs calculation:', {
695
+ totalMessages: allMessages.length,
696
+ conversationPairs,
697
+ displayedMessageCount,
698
+ hasMore: conversationPairs > displayedMessageCount,
699
+ });
700
+
701
+ // Show "Load Past Messages" whenever there are more conversation pairs
702
+ // than currently displayed (including initial view)
703
+ const hasMore = conversationPairs > displayedMessageCount;
704
+ setHasMoreMessages(hasMore);
705
+
706
+ // Ensure we always show at least the last conversation
707
+ if (displayedMessageCount === 0) {
708
+ setDisplayedMessageCount(1);
709
+ }
710
+ } else {
711
+ setHasMoreMessages(false);
712
+ }
713
+ }, [allMessages, displayedMessageCount]);
714
+
715
+ // Function to load more past messages
716
+ const loadMoreMessages = useCallback(() => {
717
+ if (allMessages && allMessages.length > 0) {
718
+ // Load more conversation pairs (5 pairs = 10 messages)
719
+ const conversationPairs = Math.ceil(allMessages.length / 2);
720
+ setDisplayedMessageCount((prev) => Math.min(prev + 5, conversationPairs));
721
+ }
722
+ }, [allMessages]);
723
+
724
+ // Function to reset to showing only last message
725
+ const resetToLastMessage = useCallback(() => {
726
+ setDisplayedMessageCount(1);
727
+ }, []);
728
+
729
+ // Build list with date separators similar to MessagesBuilderUi
730
+ const messageListWithDates = useMemo(() => {
731
+ let currentDate = '';
732
+ const res: any[] = [];
733
+
734
+ allMessages?.forEach((msg: any) => {
735
+ const date = new Date(msg.createdAt);
736
+ let msgDate: string;
737
+ if (isToday(date)) msgDate = t('tailwind_ui_inbox.today');
738
+ else if (isYesterday(date)) msgDate = t('tailwind_ui_inbox.yesterday');
739
+ else msgDate = format(date, 'eee, do MMMM');
740
+
741
+ if (msgDate !== currentDate) {
742
+ res.push({ type: 'date', content: msgDate });
743
+ currentDate = msgDate;
744
+ }
745
+ res.push(msg);
746
+ });
747
+
748
+ if (allMessages && allMessages.length > 0) {
749
+ const todayLabel = t('tailwind_ui_inbox.today');
750
+ if (currentDate !== todayLabel) {
751
+ res.push({ type: 'date', content: todayLabel });
752
+ }
753
+ }
754
+
755
+ return res;
756
+ }, [allMessages, t]);
757
+
758
+ // Group messages by date sections for Slack-like rendering
759
+ const messagesByDate = useMemo(() => {
760
+ const sections: { date: string | null; messages: any[] }[] = [];
761
+ let currentSection: { date: string | null; messages: any[] } = { date: null, messages: [] };
762
+
763
+ messageListWithDates.forEach((item: any) => {
764
+ if (item?.type === 'date') {
765
+ if (currentSection.messages.length > 0) {
766
+ sections.push(currentSection);
767
+ }
768
+ currentSection = { date: item.content, messages: [] };
769
+ } else {
770
+ currentSection.messages.push(item);
771
+ }
772
+ });
773
+
774
+ if (currentSection.messages.length > 0) {
775
+ sections.push(currentSection);
776
+ }
777
+
778
+ return sections;
779
+ }, [messageListWithDates]);
780
+
781
+ // Limit messages to display based on displayedMessageCount (conversation pairs)
782
+ const limitedMessagesByDate = useMemo(() => {
783
+ if (!messagesByDate || messagesByDate.length === 0) return [];
784
+
785
+ // Calculate how many conversation pairs we should show
786
+ let conversationPairs = 0;
787
+ const limitedSections: { date: string | null; messages: any[] }[] = [];
788
+
789
+ // Start from the end (most recent messages) and work backwards
790
+ for (let i = messagesByDate.length - 1; i >= 0; i--) {
791
+ const section = messagesByDate[i];
792
+ const sectionMessages = [...section.messages];
793
+
794
+ // Count conversation pairs in this section
795
+ let sectionPairs = 0;
796
+ for (let j = 0; j < sectionMessages.length; j++) {
797
+ const currentMsg = sectionMessages[j];
798
+ if (currentMsg.sender === 'user' || currentMsg.isUserMessage) {
799
+ // Check if there's an AI response following this user message
800
+ if (j + 1 < sectionMessages.length) {
801
+ const nextMsg = sectionMessages[j + 1];
802
+ if (nextMsg.sender === 'ai' || !nextMsg.isUserMessage) {
803
+ sectionPairs++;
804
+ j++; // Skip the AI response since we counted it as a pair
805
+ }
806
+ } else {
807
+ sectionPairs++; // User message without AI response still counts
808
+ }
809
+ } else if (currentMsg.sender === 'ai' || !currentMsg.isUserMessage) {
810
+ // AI message without preceding user message counts as half a pair
811
+ sectionPairs += 0.5;
812
+ }
813
+ }
814
+
815
+ // If this section would exceed our limit, only take what we can fit
816
+ if (conversationPairs + sectionPairs > displayedMessageCount) {
817
+ const remainingPairs = displayedMessageCount - conversationPairs;
818
+ if (remainingPairs > 0) {
819
+ // Take only the last N conversation pairs from this section
820
+ const limitedMessages = sectionMessages.slice(-Math.floor(remainingPairs * 2));
821
+ limitedSections.unshift({ ...section, messages: limitedMessages });
822
+ conversationPairs += remainingPairs;
823
+ }
824
+ break;
825
+ } else {
826
+ // We can fit this entire section
827
+ limitedSections.unshift(section);
828
+ conversationPairs += sectionPairs;
829
+ }
830
+ }
831
+
832
+ return limitedSections;
833
+ }, [messagesByDate, displayedMessageCount]);
834
+
835
+ // Ensure we show complete conversation pairs (user message + AI response)
836
+ const completeConversationMessages = useMemo(() => {
837
+ if (!limitedMessagesByDate || limitedMessagesByDate.length === 0) return [];
838
+
839
+ const completeSections: { date: string | null; messages: any[] }[] = [];
840
+
841
+ limitedMessagesByDate.forEach((section) => {
842
+ const completeMessages: any[] = [];
843
+ const sectionMessages = [...section.messages];
844
+
845
+ console.log('🔄 Processing section for complete conversations:', {
846
+ sectionDate: section.date,
847
+ totalMessages: sectionMessages.length,
848
+ messages: sectionMessages.map((m) => ({
849
+ sender: m.sender,
850
+ isUserMessage: m.isUserMessage,
851
+ message: m.message?.substring(0, 30) + '...',
852
+ })),
853
+ });
854
+
855
+ // Group messages into conversation pairs
856
+ for (let i = 0; i < sectionMessages.length; i++) {
857
+ const currentMsg = sectionMessages[i];
858
+
859
+ // If this is a user message, look for its AI response
860
+ if (currentMsg.sender === 'user' || currentMsg.isUserMessage) {
861
+ completeMessages.push(currentMsg);
862
+
863
+ // Look for the corresponding AI response (next message should be AI)
864
+ if (i + 1 < sectionMessages.length) {
865
+ const nextMsg = sectionMessages[i + 1];
866
+ if (nextMsg.sender === 'ai' || !nextMsg.isUserMessage) {
867
+ completeMessages.push(nextMsg);
868
+ i++; // Skip the next message since we've already added it
869
+ }
870
+ }
871
+ } else if (currentMsg.sender === 'ai' || !currentMsg.isUserMessage) {
872
+ // If this is an AI message without a preceding user message, still show it
873
+ completeMessages.push(currentMsg);
874
+ }
875
+ }
876
+
877
+ console.log('🔄 Complete messages for section:', {
878
+ sectionDate: section.date,
879
+ completeMessages: completeMessages.map((m) => ({
880
+ sender: m.sender,
881
+ isUserMessage: m.isUserMessage,
882
+ message: m.message?.substring(0, 30) + '...',
883
+ })),
884
+ });
885
+
886
+ if (completeMessages.length > 0) {
887
+ completeSections.push({ ...section, messages: completeMessages });
888
+ }
889
+ });
890
+
891
+ return completeSections;
892
+ }, [limitedMessagesByDate]);
893
+
894
+ // Auto scroll to bottom when messages, typing status, or AI responses change
895
+ useEffect(() => {
896
+ scrollToBottom('smooth', 100);
897
+ }, [completeConversationMessages?.length, isTyping, aiMessages.length, scrollToBottom]);
898
+
899
+ // Auto scroll when processing message changes (shows loading indicators)
900
+ useEffect(() => {
901
+ if (currentProcessingMessage !== null) {
902
+ scrollToBottom('smooth', 150);
903
+ }
904
+ }, [currentProcessingMessage, scrollToBottom]);
905
+
906
+ return (
907
+ <div className={`flex flex-col h-full ${className}`}>
908
+ {/* Header with Tabs */}
909
+ <div className="border-b border-gray-200 bg-white">
910
+ <div className="flex items-center justify-between px-4 py-3">
911
+ {/* Tabs */}
912
+ <div className="flex space-x-6">
913
+ <button
914
+ onClick={() => setActiveTab('chat')}
915
+ className={`relative ${
916
+ activeTab === 'chat' ? 'text-blue-600 font-medium' : 'text-gray-500 hover:text-gray-700'
917
+ } text-sm transition-colors`}
918
+ >
919
+ <span>Chat</span>
920
+ {activeTab === 'chat' && (
921
+ <div className="absolute bottom-0 left-0 w-full h-0.5 bg-blue-600"></div>
922
+ )}
923
+ </button>
924
+ <button
925
+ onClick={() => setActiveTab('history')}
926
+ className={`relative ${
927
+ activeTab === 'history'
928
+ ? 'text-blue-600 font-medium'
929
+ : 'text-gray-500 hover:text-gray-700'
930
+ } text-sm transition-colors`}
931
+ >
932
+ <span>History</span>
933
+ {activeTab === 'history' && (
934
+ <div className="absolute bottom-0 left-0 w-full h-0.5 bg-blue-600"></div>
935
+ )}
936
+ </button>
937
+ </div>
938
+
939
+ {/* New Chat Button */}
940
+ <button
941
+ onClick={() => {
942
+ // Reset to show only the last message for new chat
943
+ setDisplayedMessageCount(1);
944
+ setHasMoreMessages(false);
945
+ // Clear processing state
946
+ setProcessedMessageIds(new Set());
947
+ setCurrentProcessingMessage(null);
948
+ // Clear AI messages by sending update event to the machine
949
+ send({ type: 'UPDATE', value: { messages: [] } });
950
+ }}
951
+ className="px-4 py-2 text-sm text-gray-700 border border-gray-300 rounded-lg hover:bg-gray-200 transition-colors"
952
+ style={{ borderRadius: '10px' }}
953
+ >
954
+ New Chat
955
+ </button>
956
+ </div>
957
+ </div>
958
+
959
+ {/* Tab Content */}
960
+ {activeTab === 'history' ? (
961
+ /* History Tab Content */
962
+ <div className="flex-1 overflow-y-auto bg-white">
963
+ {/* Search Bar */}
964
+ <div className="px-4 py-3 border-b border-gray-200">
965
+ <div className="relative">
966
+ <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
967
+ <svg
968
+ className="h-5 w-5 text-gray-400"
969
+ fill="none"
970
+ viewBox="0 0 24 24"
971
+ stroke="currentColor"
972
+ >
973
+ <path
974
+ strokeLinecap="round"
975
+ strokeLinejoin="round"
976
+ strokeWidth={2}
977
+ d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
978
+ />
979
+ </svg>
980
+ </div>
981
+ <input
982
+ type="text"
983
+ placeholder="Search messages..."
984
+ className="block w-full pl-10 pr-12 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
985
+ />
986
+ <div className="absolute inset-y-0 right-0 pr-3 flex items-center">
987
+ <button className="text-gray-400 hover:text-gray-600">
988
+ <svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
989
+ <path
990
+ strokeLinecap="round"
991
+ strokeLinejoin="round"
992
+ strokeWidth={2}
993
+ d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"
994
+ />
995
+ </svg>
996
+ </button>
997
+ </div>
998
+ </div>
999
+ </div>
1000
+
1001
+ {/* Message History List */}
1002
+ <div className="px-4 py-2">
1003
+ {regularMessages && regularMessages.length > 0 ? (
1004
+ regularMessages.map((msg: any, index: number) => {
1005
+ const messageTime = new Date(msg.createdAt);
1006
+
1007
+ return (
1008
+ <div key={msg.id || index} className="mb-4">
1009
+ {/* Restore to this point button */}
1010
+ <div className="flex items-center justify-end mb-2">
1011
+ <button className="text-blue-600 text-sm underline hover:text-blue-800 flex items-center">
1012
+ <svg
1013
+ className="w-4 h-4 mr-1"
1014
+ fill="none"
1015
+ viewBox="0 0 24 24"
1016
+ stroke="currentColor"
1017
+ >
1018
+ <path
1019
+ strokeLinecap="round"
1020
+ strokeLinejoin="round"
1021
+ strokeWidth={2}
1022
+ d="M15 19l-7-7 7-7"
1023
+ />
1024
+ </svg>
1025
+ Restore to this point
1026
+ </button>
1027
+ </div>
1028
+
1029
+ {/* Message using same container as Chat tab */}
1030
+ <ModernMessageGroupComponent
1031
+ messages={[
1032
+ {
1033
+ id: msg.id,
1034
+ message: msg.message || msg.content || 'No message content',
1035
+ author: msg.author || {
1036
+ id: 'user',
1037
+ givenName: 'User',
1038
+ familyName: '',
1039
+ fullName: 'User',
1040
+ username: 'user',
1041
+ email: '',
1042
+ picture: null,
1043
+ alias: [],
1044
+ tokens: [],
1045
+ },
1046
+ createdAt: msg.createdAt,
1047
+ type: 'Simple' as any,
1048
+ isDelivered: false,
1049
+ isRead: false,
1050
+ parentId: null,
1051
+ fromServer: null,
1052
+ updatedAt: msg.createdAt,
1053
+ propsConfiguration: null,
1054
+ props: null,
1055
+ files: null,
1056
+ replies: null,
1057
+ channel: null,
1058
+ isPinned: false,
1059
+ },
1060
+ ]}
1061
+ currentUser={currentUser as any}
1062
+ onOpen={onOpen}
1063
+ onMessageClick={() => {}}
1064
+ isDesktopView={isDesktopView}
1065
+ isSmallScreen={isSmallScreen}
1066
+ />
1067
+ </div>
1068
+ );
1069
+ })
1070
+ ) : (
1071
+ // Fallback entries when no messages
1072
+ <>
1073
+ {/* History Entry 1 */}
1074
+ <div className="mb-4">
1075
+ <div className="flex items-center justify-end mb-2">
1076
+ <button className="text-blue-600 text-sm underline hover:text-blue-800 flex items-center">
1077
+ <svg
1078
+ className="w-4 h-4 mr-1"
1079
+ fill="none"
1080
+ viewBox="0 0 24 24"
1081
+ stroke="currentColor"
1082
+ >
1083
+ <path
1084
+ strokeLinecap="round"
1085
+ strokeLinejoin="round"
1086
+ strokeWidth={2}
1087
+ d="M15 19l-7-7 7-7"
1088
+ />
1089
+ </svg>
1090
+ Restore to this point
1091
+ </button>
1092
+ </div>
1093
+ <div className="bg-gray-100 rounded-lg p-3">
1094
+ <p className="text-sm text-gray-900">Sample message for demonstration</p>
1095
+ </div>
1096
+ </div>
1097
+ </>
1098
+ )}
1099
+ </div>
1100
+ </div>
1101
+ ) : (
1102
+ /* Chat Tab Content */
1103
+ <div className="flex-1 overflow-y-auto bg-white">
1104
+ {/* Messages Display - Styled like MessagesBuilderUi */}
1105
+ <div
1106
+ className={`w-full pb-4 pt-2 ${
1107
+ isDesktopView ? 'space-y-3 max-w-full mx-auto' : isSmallScreen ? 'space-y-2' : 'space-y-2'
1108
+ }`}
1109
+ >
1110
+ {/* Load Past Messages Button */}
1111
+ {hasMoreMessages && (
1112
+ <div className="px-4 py-3 text-center">
1113
+ <button
1114
+ onClick={loadMoreMessages}
1115
+ className="px-4 py-2 text-sm text-gray-600 bg-gray-100 border border-gray-300 rounded-lg hover:bg-gray-200 transition-colors"
1116
+ >
1117
+ Load Past Messages
1118
+ </button>
1119
+ </div>
1120
+ )}
1121
+
1122
+ {/* Show placeholder when no messages */}
1123
+ {(!completeConversationMessages || completeConversationMessages.length === 0) && (
1124
+ <div className="px-4 py-8 text-center">
1125
+ <div className="text-gray-500 text-lg font-medium mb-2">
1126
+ Describe your idea and I'll help you create it step by step
1127
+ </div>
1128
+ <div className="text-gray-400 text-sm">
1129
+ Start a new conversation by typing your message below
1130
+ </div>
1131
+ </div>
1132
+ )}
1133
+
1134
+ {completeConversationMessages?.map((section, sectionIndex) => (
1135
+ <div key={`section-${sectionIndex}`} className="w-full px-4">
1136
+ {section.date && (
1137
+ <div className="flex items-center justify-center my-3">
1138
+ <div className="flex-grow border-t border-gray-200"></div>
1139
+ <div className="mx-4 px-3 py-1 bg-white border border-gray-200 rounded-full text-xs font-medium text-gray-600">
1140
+ {section.date}
1141
+ </div>
1142
+ <div className="flex-grow border-t border-gray-200"></div>
1143
+ </div>
1144
+ )}
1145
+
1146
+ <div className={`${isDesktopView ? 'mb-2' : 'mb-1'}`}>
1147
+ {section.messages.map((msg: any, msgIndex: number) => (
1148
+ <div key={msg.id || msgIndex} className="mb-3">
1149
+ {/* AI Message Indicator */}
1150
+ {msg.isAIMessage && (
1151
+ <div className="flex items-center mb-2">
1152
+ <div className="w-2 h-2 bg-blue-500 rounded-full mr-2"></div>
1153
+ <span className="text-xs text-blue-600 font-medium">
1154
+ AI Assistant
1155
+ </span>
1156
+ </div>
1157
+ )}
1158
+
1159
+ {msg.isLoading ? (
1160
+ // Show loader for loading messages
1161
+ <div className="flex items-center space-x-2 bg-gray-50 border border-gray-200 px-4 py-3 rounded-lg">
1162
+ <div className="w-4 h-4 border-2 border-gray-300 border-t-gray-500 rounded-full animate-spin"></div>
1163
+ <span className="text-sm text-gray-600">
1164
+ {msg.isProcessing ? 'AI is thinking...' : 'Thinking...'}
1165
+ </span>
1166
+ </div>
1167
+ ) : (
1168
+ // Show normal message for non-loading messages
1169
+ <ModernMessageGroupComponent
1170
+ messages={[
1171
+ {
1172
+ id: msg.id,
1173
+ message: msg.message,
1174
+ author:
1175
+ msg.sender === 'user'
1176
+ ? msg.author
1177
+ : {
1178
+ id: 'ai-assistant',
1179
+ givenName: 'AI',
1180
+ familyName: 'Assistant',
1181
+ fullName: 'AI Assistant',
1182
+ username: 'ai-assistant',
1183
+ email: 'ai@assistant.com',
1184
+ picture: null,
1185
+ alias: [],
1186
+ tokens: [],
1187
+ },
1188
+ createdAt: msg.createdAt,
1189
+ type: 'Simple' as any,
1190
+ isDelivered: false,
1191
+ isRead: false,
1192
+ parentId: null,
1193
+ fromServer: null,
1194
+ updatedAt: msg.createdAt,
1195
+ propsConfiguration: null,
1196
+ props: null,
1197
+ files: null,
1198
+ replies: null,
1199
+ channel: null,
1200
+ isPinned: false,
1201
+ },
1202
+ ]}
1203
+ currentUser={currentUser as any}
1204
+ onOpen={onOpen}
1205
+ onMessageClick={() => {}}
1206
+ isDesktopView={isDesktopView}
1207
+ isSmallScreen={isSmallScreen}
1208
+ />
1209
+ )}
1210
+ </div>
1211
+ ))}
1212
+ </div>
1213
+ </div>
1214
+ ))}
1215
+
1216
+ {/* Typing indicator */}
1217
+ {isTyping && (
1218
+ <div className="px-4">
1219
+ <div className="flex justify-start">
1220
+ <div className="bg-gray-50 border border-gray-200 px-4 py-3 rounded-lg">
1221
+ <div className="flex items-center space-x-2">
1222
+ <div className="w-4 h-4 border-2 border-gray-300 border-t-gray-500 rounded-full animate-spin"></div>
1223
+ <span className="text-sm text-gray-600">AI is thinking...</span>
1224
+ </div>
1225
+ </div>
1226
+ </div>
1227
+ </div>
1228
+ )}
1229
+
1230
+ <div ref={bottomRef} />
1231
+ </div>
1232
+
1233
+ {/* Subscription Handler for real-time message updates */}
1234
+ <SubscriptionHandler
1235
+ subscribeToMore={subscribeToMore}
1236
+ document={CHAT_MESSAGE_ADDED}
1237
+ variables={{ channelId: actualChannelId?.toString() }}
1238
+ enabled={!!actualChannelId && !!subscribeToMore}
1239
+ updateQuery={(prev: any, { subscriptionData }: any) => {
1240
+ console.log('Subscription updateQuery called:', { prev, subscriptionData });
1241
+ if (!subscriptionData.data) {
1242
+ console.log('No subscription data, returning prev');
1243
+ return prev;
1244
+ }
1245
+ const newMessage = subscriptionData.data.chatMessageAdded;
1246
+ console.log('New message received via subscription:', newMessage);
1247
+
1248
+ return {
1249
+ ...prev,
1250
+ messages: {
1251
+ ...prev?.messages,
1252
+ data: uniqBy([...(prev?.messages?.data || []), newMessage], 'id'),
1253
+ totalCount: (prev?.messages?.totalCount || 0) + 1,
1254
+ },
1255
+ };
1256
+ }}
1257
+ onError={(error) => {
1258
+ console.error('Subscription error:', error);
1259
+ }}
1260
+ />
1261
+ </div>
1262
+ )}
1263
+
1264
+ {/* Error Display */}
1265
+ {error && (
1266
+ <div className="mx-4 mb-4 p-3 bg-red-50 border border-red-200 rounded-lg">
1267
+ <div className="flex items-center justify-between">
1268
+ <div className="text-red-800 text-sm">{error}</div>
1269
+ <div className="flex space-x-2">
1270
+ <button
1271
+ onClick={handleRetry}
1272
+ className="px-2 py-1 text-xs bg-red-100 text-red-700 rounded hover:bg-red-200 transition-colors"
1273
+ >
1274
+ Retry
1275
+ </button>
1276
+ <button
1277
+ onClick={handleClearError}
1278
+ className="px-2 py-1 text-xs bg-gray-100 text-gray-700 rounded hover:bg-gray-200 transition-colors"
1279
+ >
1280
+ Dismiss
1281
+ </button>
1282
+ </div>
1283
+ </div>
1284
+ </div>
1285
+ )}
1286
+
1287
+ {/* Input Area - Only show for Chat tab */}
1288
+ {activeTab === 'chat' && (
1289
+ <div className="border-t border-gray-200 bg-white">
1290
+ {/* Processing Status Indicator */}
1291
+ {currentProcessingMessage !== null && (
1292
+ <div className="px-4 py-2 border-b border-blue-100 bg-blue-50">
1293
+ <div className="flex items-center space-x-2 text-sm text-blue-700">
1294
+ <div className="flex space-x-1">
1295
+ <div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce"></div>
1296
+ <div
1297
+ className="w-2 h-2 bg-blue-500 rounded-full animate-bounce"
1298
+ style={{ animationDelay: '0.1s' }}
1299
+ ></div>
1300
+ <div
1301
+ className="w-2 h-2 bg-blue-500 rounded-full animate-bounce"
1302
+ style={{ animationDelay: '0.2s' }}
1303
+ ></div>
1304
+ </div>
1305
+ <span>
1306
+ AI is processing message {currentProcessingMessage + 1} of {regularMessages.length}
1307
+ </span>
1308
+ </div>
1309
+ </div>
1310
+ )}
1311
+
1312
+ {/* Removed temporary test button */}
1313
+ <InputComponent channelId={actualChannelId} handleSend={handleSend} placeholder={placeholder} />
1314
+ </div>
1315
+ )}
1316
+
1317
+ {/* Optional Modal for user content like MessagesBuilderUi */}
1318
+ {isOpen ? (
1319
+ <div>
1320
+ <div className="fixed inset-0 bg-black bg-opacity-50 z-40" onClick={onClose} />
1321
+ <div className="fixed z-50 left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2">
1322
+ <div
1323
+ className="bg-white w-[1036px] h-[700px] rounded-lg shadow-xl"
1324
+ style={{ boxShadow: '0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)' }}
1325
+ >
1326
+ <div className="flex justify-between border-b border-gray-300 pb-4 pt-4">
1327
+ <button
1328
+ onClick={onClose}
1329
+ className="w-8 ml-3 text-black hover:text-black focus:outline-none"
1330
+ >
1331
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1332
+ <path
1333
+ strokeLinecap="round"
1334
+ strokeLinejoin="round"
1335
+ strokeWidth={2}
1336
+ d="M6 18L18 6M6 6l12 12"
1337
+ />
1338
+ </svg>
1339
+ </button>
1340
+ </div>
1341
+ <div className="p-4">
1342
+ {/* Keep minimal until full UserModalContent is needed */}
1343
+ <div className="text-sm text-gray-700">{selectedElement?.author?.username}</div>
1344
+ </div>
1345
+ </div>
1346
+ </div>
1347
+ </div>
1348
+ ) : null}
1349
+ </div>
1350
+ );
1351
+ };