@messenger-box/tailwind-ui-inbox 10.0.3-alpha.73 → 10.0.3-alpha.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/lib/components/AIAgent/AIAgent.d.ts +7 -0
- package/lib/components/AIAgent/AIAgent.d.ts.map +1 -1
- package/lib/components/AIAgent/AIAgent.js +362 -615
- package/lib/components/AIAgent/AIAgent.js.map +1 -1
- package/lib/components/InboxMessage/InputComponent.d.ts.map +1 -1
- package/lib/components/InboxMessage/InputComponent.js +143 -140
- package/lib/components/InboxMessage/InputComponent.js.map +1 -1
- package/lib/components/InboxMessage/RightSidebarAi.d.ts +23 -0
- package/lib/components/InboxMessage/RightSidebarAi.d.ts.map +1 -0
- package/lib/components/InboxMessage/RightSidebarAi.js +9 -0
- package/lib/components/InboxMessage/RightSidebarAi.js.map +1 -0
- package/lib/components/InboxMessage/index.d.ts +1 -0
- package/lib/components/InboxMessage/index.d.ts.map +1 -1
- package/lib/components/InboxMessage/message-widgets/ErrorFixCard.d.ts +11 -0
- package/lib/components/InboxMessage/message-widgets/ErrorFixCard.d.ts.map +1 -0
- package/lib/components/InboxMessage/message-widgets/ErrorFixCard.js +194 -0
- package/lib/components/InboxMessage/message-widgets/ErrorFixCard.js.map +1 -0
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts +5 -1
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts.map +1 -1
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js +308 -857
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js.map +1 -1
- package/lib/components/ModelConfigPanel.d.ts +12 -0
- package/lib/components/ModelConfigPanel.d.ts.map +1 -0
- package/lib/components/ModelConfigPanel.js +304 -0
- package/lib/components/ModelConfigPanel.js.map +1 -0
- package/lib/components/filler-components/RightSiderBar.d.ts +24 -0
- package/lib/components/filler-components/RightSiderBar.d.ts.map +1 -0
- package/lib/components/filler-components/RightSiderBar.js +335 -0
- package/lib/components/filler-components/RightSiderBar.js.map +1 -0
- package/lib/components/index.d.ts +4 -2
- package/lib/components/index.d.ts.map +1 -1
- package/lib/components/live-code-editor/hybrid-live-editor.d.ts +20 -0
- package/lib/components/live-code-editor/hybrid-live-editor.d.ts.map +1 -0
- package/lib/components/live-code-editor/hybrid-live-editor.js +68 -0
- package/lib/components/live-code-editor/hybrid-live-editor.js.map +1 -0
- package/lib/components/live-code-editor/index.d.ts +4 -0
- package/lib/components/live-code-editor/index.d.ts.map +1 -0
- package/lib/components/live-code-editor/live-code-editor.d.ts +14 -0
- package/lib/components/live-code-editor/live-code-editor.d.ts.map +1 -0
- package/lib/components/live-code-editor/live-code-editor.js +207 -0
- package/lib/components/live-code-editor/live-code-editor.js.map +1 -0
- package/lib/components/slot-fill/chat-message-filler.js +1 -1
- package/lib/components/slot-fill/chat-message-filler.js.map +1 -1
- package/lib/components/slot-fill/index.d.ts +1 -0
- package/lib/components/slot-fill/index.d.ts.map +1 -1
- package/lib/components/slot-fill/right-sidebar-filler.d.ts +4 -0
- package/lib/components/slot-fill/right-sidebar-filler.d.ts.map +1 -0
- package/lib/components/slot-fill/right-sidebar-filler.js +13 -0
- package/lib/components/slot-fill/right-sidebar-filler.js.map +1 -0
- package/lib/components/ui/button.d.ts +9 -0
- package/lib/components/ui/button.d.ts.map +1 -0
- package/lib/compute.js +1 -2
- package/lib/container/AiInbox.d.ts.map +1 -1
- package/lib/container/AiLandingInput.d.ts.map +1 -1
- package/lib/container/AiLandingInput.js +46 -119
- package/lib/container/AiLandingInput.js.map +1 -1
- package/lib/container/Inbox.js +1 -1
- package/lib/container/Inbox.js.map +1 -1
- package/lib/container/InboxAiMessagesLoader.d.ts +0 -21
- package/lib/container/InboxAiMessagesLoader.d.ts.map +1 -1
- package/lib/container/InboxAiMessagesLoader.js +18 -32
- package/lib/container/InboxAiMessagesLoader.js.map +1 -1
- package/lib/container/ServiceInbox.js +1 -1
- package/lib/container/ServiceInbox.js.map +1 -1
- package/lib/container/ThreadMessages.js +1 -1
- package/lib/container/ThreadMessages.js.map +1 -1
- package/lib/container/ThreadMessagesInbox.js +1 -1
- package/lib/container/ThreadMessagesInbox.js.map +1 -1
- package/lib/container/Threads.js +1 -1
- package/lib/container/Threads.js.map +1 -1
- package/lib/container/index.d.ts +2 -1
- package/lib/container/index.d.ts.map +1 -1
- package/lib/enums/messenger-slot-fill-name-enum.d.ts +2 -1
- package/lib/enums/messenger-slot-fill-name-enum.d.ts.map +1 -1
- package/lib/enums/messenger-slot-fill-name-enum.js +1 -0
- package/lib/enums/messenger-slot-fill-name-enum.js.map +1 -1
- package/lib/hooks/index.d.ts +3 -0
- package/lib/hooks/index.d.ts.map +1 -0
- package/lib/hooks/use-file-sync.d.ts +16 -0
- package/lib/hooks/use-file-sync.d.ts.map +1 -0
- package/lib/hooks/use-file-sync.js +63 -0
- package/lib/hooks/use-file-sync.js.map +1 -0
- package/lib/hooks/usePersistentModelConfig.d.ts +15 -0
- package/lib/hooks/usePersistentModelConfig.d.ts.map +1 -0
- package/lib/hooks/usePersistentModelConfig.js +46 -0
- package/lib/hooks/usePersistentModelConfig.js.map +1 -0
- package/lib/index.d.ts +4 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/machines/aiAgentMachine.d.ts.map +1 -1
- package/lib/machines/aiAgentMachine.js +64 -21
- package/lib/machines/aiAgentMachine.js.map +1 -1
- package/lib/machines/aiAgentMachine.simple.d.ts +3 -0
- package/lib/machines/aiAgentMachine.simple.d.ts.map +1 -0
- package/lib/machines/aiAgentMachine.simple.js +108 -0
- package/lib/machines/aiAgentMachine.simple.js.map +1 -0
- package/lib/machines/index.d.ts +3 -0
- package/lib/machines/index.d.ts.map +1 -0
- package/lib/module.d.ts +2 -1
- package/lib/module.d.ts.map +1 -1
- package/lib/module.js +11 -3
- package/lib/module.js.map +1 -1
- package/lib/routes.json +1 -2
- package/lib/templates/InboxWithAi.d.ts.map +1 -1
- package/lib/templates/InboxWithAi.js +129 -70
- package/lib/templates/InboxWithAi.js.map +1 -1
- package/lib/templates/InboxWithAi.tsx +151 -90
- package/lib/utils/utils.d.ts +2 -0
- package/lib/utils/utils.d.ts.map +1 -0
- package/lib/utils/utils.js +3 -0
- package/lib/utils/utils.js.map +1 -0
- package/package.json +8 -5
- package/src/components/AIAgent/AIAgent.tsx +469 -731
- package/src/components/AIAgent/AIAgent.tsx.bk +1365 -0
- package/src/components/InboxMessage/InputComponent.tsx +2 -1
- package/src/components/InboxMessage/RightSidebarAi.tsx +37 -0
- package/src/components/InboxMessage/index.ts +1 -0
- package/src/components/InboxMessage/message-widgets/ErrorFixCard.tsx +240 -0
- package/src/components/InboxMessage/message-widgets/ModernMessageGroup.tsx +337 -1116
- package/src/components/ModelConfigPanel.tsx +334 -0
- package/src/components/filler-components/RightSiderBar.tsx +408 -0
- package/src/components/index.ts +4 -1
- package/src/components/live-code-editor/hybrid-live-editor.tsx +105 -0
- package/src/components/live-code-editor/index.ts +3 -0
- package/src/components/live-code-editor/live-code-editor.tsx +257 -0
- package/src/components/slot-fill/index.ts +1 -0
- package/src/components/slot-fill/right-sidebar-filler.tsx +39 -0
- package/src/components/ui/button.tsx +32 -0
- package/src/container/AiInbox.tsx +26 -3
- package/src/container/AiLandingInput.tsx +48 -22
- package/src/container/InboxAiMessagesLoader.tsx +17 -31
- package/src/container/index.ts +2 -0
- package/src/enums/messenger-slot-fill-name-enum.ts +1 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-file-sync.ts +91 -0
- package/src/hooks/usePersistentModelConfig.ts +63 -0
- package/src/index.ts +7 -0
- package/src/machines/aiAgentMachine.simple.ts +89 -0
- package/src/machines/aiAgentMachine.ts +67 -19
- package/src/machines/aiAgentMachine.ts.bk +1296 -0
- package/src/machines/index.ts +2 -0
- package/src/module.tsx +10 -1
- package/src/templates/InboxWithAi.tsx +151 -90
- 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
|
+
};
|