@messenger-box/tailwind-ui-inbox 10.0.3-alpha.73 → 10.0.3-alpha.77

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