@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.
Files changed (145) hide show
  1. package/CHANGELOG.md +4 -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
@@ -1,13 +1,12 @@
1
1
  import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react';
2
2
  import { useActor, useMachine } from '@xstate/react';
3
- import { aiAgentMachine } from '../../machines/aiAgentMachine';
4
- import { Message } from '../../machines/types';
3
+ import { aiAgentMachine } from '../../machines/aiAgentMachine.simple';
5
4
  import { InputComponent } from '../InboxMessage/InputComponent';
6
5
  import { format, isToday, isYesterday, formatDistanceToNow } from 'date-fns';
7
6
  import { useTranslation } from 'react-i18next';
8
7
  import { ModernMessageGroupComponent } from '../InboxMessage/message-widgets/ModernMessageGroup';
9
8
  import { useUploadFiles } from '@messenger-box/platform-client';
10
- import { IFileInfo, PostTypeEnum } from 'common';
9
+ import { IFileInfo, PostTypeEnum, ISandboxError } from 'common';
11
10
  import { useSelector, shallowEqual } from 'react-redux';
12
11
  import { Store, userSelector } from '@adminide-stack/user-auth0-client';
13
12
  import { IUserState } from '@adminide-stack/core';
@@ -18,12 +17,17 @@ import {
18
17
  MessagesDocument,
19
18
  useMessagesQuery,
20
19
  OnChatMessageAddedDocument as CHAT_MESSAGE_ADDED,
20
+ useGenerateAiCodeMutation,
21
+ useOnChatMessageAddedSubscription,
22
+ useSandboxErrorSubscription,
23
+ useRecreateSandboxMutation,
21
24
  } from 'common/graphql';
22
25
  import { config } from '../../config';
23
26
  import { orderBy, uniqBy } from 'lodash-es';
24
- import { useParams } from '@remix-run/react';
27
+ import { useParams, useNavigate } from '@remix-run/react';
25
28
  import { RoomType } from 'common';
26
29
  import { SubscriptionHandler } from '../InboxMessage/SubscriptionHandler';
30
+ import { usePersistentModelConfig } from '../../hooks/usePersistentModelConfig';
27
31
 
28
32
  const { MESSAGES_PER_PAGE } = config;
29
33
 
@@ -36,6 +40,13 @@ interface AIAgentProps {
36
40
  channelName?: string;
37
41
  isDesktopView?: boolean;
38
42
  isSmallScreen?: boolean;
43
+ messages?: any[];
44
+ setMessages?: (messages: any[]) => void;
45
+ selectedPost?: any;
46
+ setSelectedPost?: (selectedPost: any) => void;
47
+ setIsLoading?: (isLoading: boolean) => void;
48
+ isLoading?: boolean;
49
+ [key: string]: any; // Allow other props to be passed through to Inbox
39
50
  }
40
51
 
41
52
  export const AIAgent: React.FC<AIAgentProps> = ({
@@ -46,6 +57,12 @@ export const AIAgent: React.FC<AIAgentProps> = ({
46
57
  currentUser,
47
58
  isDesktopView = false,
48
59
  isSmallScreen = false,
60
+ setMessages,
61
+ setSelectedPost,
62
+ messages,
63
+ selectedPost,
64
+ setIsLoading,
65
+ isLoading,
49
66
  }) => {
50
67
  const [state, send] = useMachine(aiAgentMachine);
51
68
  const apolloClient = useApolloClient();
@@ -53,14 +70,31 @@ export const AIAgent: React.FC<AIAgentProps> = ({
53
70
  const [sendMsg] = useSendMessagesMutation();
54
71
  const auth: any = useSelector<Store.Auth, IUserState>(userSelector, shallowEqual);
55
72
  const { id: pathChannelId } = useParams();
56
-
73
+ const navigate = useNavigate();
74
+ const { modelConfig, getValidatedConfig, hasApiKey } = usePersistentModelConfig();
75
+ // const [isLoading, setIsLoading] = useState(false);
76
+ const [errorData, setError] = useState<string | null>(null);
57
77
  // Get channelId from props or path params
58
78
  const actualChannelId = channelId || pathChannelId;
79
+ const [generateAiCode] = useGenerateAiCodeMutation();
80
+ const [recreateSandbox] = useRecreateSandboxMutation();
81
+ const { data: chatMessageAddedData } = useOnChatMessageAddedSubscription({
82
+ variables: { channelId: actualChannelId?.toString() || '' },
83
+ skip: !actualChannelId,
84
+ });
85
+
86
+ // Subscribe to sandbox errors if projectId is provided
87
+ const { data: sandboxErrorData } = useSandboxErrorSubscription({
88
+ variables: { projectId: actualChannelId?.toString() || '' },
89
+ skip: !actualChannelId,
90
+ });
59
91
 
60
92
  // Direct message query from InboxWithAiLoader.tsx
61
93
  const messagesQuery = useMessagesQuery({
62
94
  variables: {
63
- channelId: actualChannelId?.toString(),
95
+ props: {
96
+ projectId: actualChannelId?.toString(),
97
+ },
64
98
  parentId: null,
65
99
  limit: MESSAGES_PER_PAGE,
66
100
  },
@@ -80,28 +114,147 @@ export const AIAgent: React.FC<AIAgentProps> = ({
80
114
 
81
115
  const [isOpen, setIsOpen] = useState(false);
82
116
  const [selectedElement, setSelectedElement] = useState<any>(null);
83
- const [currentProcessingMessage, setCurrentProcessingMessage] = useState<number | null>(null);
84
- const [processedMessageIds, setProcessedMessageIds] = useState<Set<string>>(new Set());
85
117
  const [activeTab, setActiveTab] = useState<'chat' | 'history'>('chat');
86
118
  const bottomRef = useRef<HTMLDivElement | null>(null);
87
-
119
+ const [sandboxErrors, setSandboxErrors] = useState<ISandboxError[]>([]);
120
+ const [currentFiles, setCurrentFiles] = useState<Record<string, string>>({});
121
+ const [canvasLayers, setCanvasLayers] = useState<any[]>([]);
88
122
  // New state variables for message display control
89
- const [displayedMessageCount, setDisplayedMessageCount] = useState(1); // Initially show only 1 message
90
- const [hasMoreMessages, setHasMoreMessages] = useState(false);
123
+ const [isSuccessThinking, setIsSuccessThinking] = useState(false);
124
+ const successThinkingTimeoutRef = useRef<any>(null);
91
125
 
92
126
  // Get regular messages from direct query
93
127
  const {
94
128
  data: messagesData,
95
129
  loading: messageLoading,
130
+ refetch: refetchMessages,
96
131
  fetchMore: fetchMoreMessages,
97
132
  subscribeToMore,
98
133
  } = messagesQuery;
99
134
 
135
+ useEffect(() => {
136
+ if (actualChannelId) {
137
+ refetchMessages({
138
+ limit: MESSAGES_PER_PAGE,
139
+ props: {
140
+ projectId: actualChannelId?.toString(),
141
+ },
142
+ });
143
+ }
144
+ }, [actualChannelId, refetchMessages]);
145
+
146
+ useEffect(() => {
147
+ if (chatMessageAddedData?.chatMessageAdded) {
148
+ console.log(
149
+ 'chatMessageAddedData?.chatMessageAdded',
150
+ JSON.stringify(chatMessageAddedData?.chatMessageAdded, null, 2),
151
+ );
152
+ // handleGenerateAiCode(chatMessageAddedData.chatMessageAdded.id);
153
+ // Stop the success thinking loader once a new message arrives
154
+ if (isSuccessThinking) {
155
+ setIsSuccessThinking(false);
156
+ if (successThinkingTimeoutRef.current) {
157
+ clearTimeout(successThinkingTimeoutRef.current);
158
+ successThinkingTimeoutRef.current = null;
159
+ }
160
+ }
161
+ }
162
+ }, [chatMessageAddedData?.chatMessageAdded]);
163
+
164
+ useEffect(() => {
165
+ if (messagesData?.messages?.data?.length == 1) {
166
+ handleGenerateAiCode(messagesData.messages.data[0].id);
167
+ }
168
+ // console.log('messagesData?.messages?.data',JSON.stringify(messagesData?.messages?.data,null,2))
169
+ }, [messagesData?.messages?.data]);
170
+
171
+ // Handle sandbox error subscription updates
172
+ useEffect(() => {
173
+ if (sandboxErrorData?.sandboxError) {
174
+ const errorData = sandboxErrorData.sandboxError;
175
+ console.log('Sandbox error received:', errorData);
176
+ setSandboxErrors((prev) => [...prev, errorData.error]);
177
+ }
178
+ }, [sandboxErrorData]);
179
+
100
180
  const regularMessages = useMemo(() => {
101
181
  if (!messagesData?.messages?.data) return [];
102
182
  return orderBy(uniqBy(messagesData.messages.data || [], 'id'), ['createdAt'], ['asc']);
103
183
  }, [messagesData?.messages?.data]);
104
184
 
185
+ // Send regular messages to AI agent machine for context
186
+ useEffect(() => {
187
+ if (regularMessages && regularMessages.length > 0) {
188
+ send({ type: 'UPDATE_REGULAR_MESSAGES', messages: regularMessages });
189
+ setMessages(regularMessages);
190
+ const currentMessage = regularMessages?.[regularMessages.length - 1];
191
+ setSelectedPost(regularMessages[regularMessages.length - 1]);
192
+ setCurrentFiles(currentMessage?.propsConfiguration?.contents?.fragment?.files ?? {});
193
+ let canvasLayers = currentMessage?.propsConfiguration?.contents?.fragment?.canvasLayers ?? [];
194
+ const summary = currentMessage?.propsConfiguration?.contents?.fragment?.summary ?? null;
195
+ // If not in direct field, try to extract from summary
196
+ if (!canvasLayers && summary) {
197
+ const canvasLayersMatch = summary?.match(/<canvas_layers>([\s\S]*?)<\/canvas_layers>/);
198
+ if (canvasLayersMatch) {
199
+ try {
200
+ canvasLayers = JSON.parse(canvasLayersMatch[1]);
201
+ } catch (error) {
202
+ console.error('Failed to parse canvas layers from summary:', error);
203
+ }
204
+ }
205
+ }
206
+ // Update canvas layers if available
207
+ if (canvasLayers && Array.isArray(canvasLayers)) {
208
+ setCanvasLayers(canvasLayers);
209
+ }
210
+
211
+ console.log('currenmessage', currentMessage);
212
+ if (currentMessage?.propsConfiguration?.contents?.role === 'ASSISTANT') {
213
+ //setIsLoading(false);
214
+ }
215
+
216
+ scrollToBottom('smooth', 0);
217
+ }
218
+ }, [regularMessages, send]);
219
+
220
+ const handleGenerateAiCode = useCallback(
221
+ (messageId: string) => {
222
+ generateAiCode({
223
+ variables: {
224
+ messageId: messageId,
225
+ modelConfig: modelConfig,
226
+ },
227
+ onCompleted: (res) => {
228
+ if (res.generateAiCode.success) {
229
+ console.log('generated_result', res);
230
+ // Show AI is thinking loader on success
231
+ if (successThinkingTimeoutRef.current) {
232
+ clearTimeout(successThinkingTimeoutRef.current);
233
+ successThinkingTimeoutRef.current = null;
234
+ }
235
+ setIsSuccessThinking(true);
236
+ // Fallback auto-hide after 15s in case no subsequent event arrives
237
+ successThinkingTimeoutRef.current = setTimeout(() => {
238
+ setIsSuccessThinking(false);
239
+ // setIsLoading(false);
240
+ successThinkingTimeoutRef.current = null;
241
+ }, 15000);
242
+ }
243
+ },
244
+ onError: (err) => {
245
+ console.log('err', JSON.stringify(err, null, 2));
246
+ // Also ensure loader is hidden on error
247
+ if (successThinkingTimeoutRef.current) {
248
+ clearTimeout(successThinkingTimeoutRef.current);
249
+ successThinkingTimeoutRef.current = null;
250
+ }
251
+ setIsSuccessThinking(false);
252
+ },
253
+ });
254
+ },
255
+ [generateAiCode, modelConfig],
256
+ );
257
+
105
258
  const onOpen = (element?: any) => {
106
259
  setSelectedElement(element);
107
260
  setIsOpen(true);
@@ -125,403 +278,205 @@ export const AIAgent: React.FC<AIAgentProps> = ({
125
278
  }, delay);
126
279
  }, []);
127
280
 
281
+ const recreateSandboxForFragment = useCallback(
282
+ async (messageId: string) => {
283
+ if (!actualChannelId) {
284
+ console.error('No project ID available for sandbox recreation');
285
+ return;
286
+ }
287
+
288
+ try {
289
+ setIsLoading(true);
290
+ setError(null);
291
+
292
+ const response = await recreateSandbox({
293
+ variables: {
294
+ projectId: actualChannelId,
295
+ messageId,
296
+ },
297
+ });
298
+
299
+ if (response.data?.recreateSandbox?.success) {
300
+ console.log('Sandbox recreation initiated successfully');
301
+ // The subscription will handle updating the UI with the new sandbox URL
302
+ } else {
303
+ const errorMsg = response.data?.recreateSandbox?.message || 'Failed to recreate sandbox';
304
+ throw new Error(errorMsg);
305
+ }
306
+ } catch (err) {
307
+ console.error('Error recreating sandbox:', err);
308
+ setError(err instanceof Error ? err.message : 'Failed to recreate sandbox');
309
+ setIsLoading(false);
310
+ }
311
+ },
312
+ [recreateSandbox, actualChannelId],
313
+ );
314
+
128
315
  // Updated handleSend function from InboxWithAi.tsx
129
316
  const handleSend = useCallback(
130
317
  async (message: string, files: any[] = []) => {
131
318
  // Allow sending if there's either a message or files
132
319
  if ((!message || !message.trim()) && (!files || files.length === 0)) return;
133
- if (!actualChannelId) return;
134
-
135
- try {
136
- const postId = objectId();
137
- const currentDate = new Date();
138
-
139
- const createOptimisticMessage = (files?: any[]) => ({
140
- __typename: 'Post' as const,
141
- id: postId,
142
- message,
143
- createdAt: currentDate.toISOString(),
144
- updatedAt: currentDate.toISOString(),
145
- author: {
146
- __typename: 'UserAccount' as const,
147
- id: currentUser?.id,
148
- givenName: currentUser?.profile?.given_name || '',
149
- familyName: currentUser?.profile?.family_name || '',
150
- email: currentUser?.profile?.email || '',
151
- username: currentUser?.profile?.nickname || '',
152
- fullName: currentUser?.profile?.name || '',
153
- picture: currentUser?.profile?.picture || '',
154
- alias: [currentUser?.authUserId ?? ''],
155
- tokens: [],
156
- },
157
- isDelivered: false, // Will be true once confirmed by server
158
- isRead: false,
159
- type: 'Simple' as any,
160
- parentId: null,
161
- fromServer: false,
162
- channel: {
163
- __typename: 'Channel' as const,
164
- id: actualChannelId,
165
- },
166
- propsConfiguration: {
167
- __typename: 'MachineConfiguration' as const,
168
- id: null,
169
- resource: '' as any,
170
- contents: null,
171
- keys: null,
172
- target: null,
173
- overrides: null,
174
- },
175
- props: {},
176
- files: {
177
- __typename: 'FilesInfo' as const,
178
- data: files || [],
179
- totalCount: files?.length || 0,
180
- },
181
- replies: {
182
- __typename: 'Messages' as const,
183
- data: [],
184
- totalCount: 0,
185
- },
186
- });
187
320
 
188
- const optimisticMessage = createOptimisticMessage(files);
321
+ const validated = getValidatedConfig();
322
+ if (!validated && !hasApiKey) {
323
+ // No API key/config; do nothing (AiLandingInput would prompt config UI)
324
+ return;
325
+ }
189
326
 
190
- if (files?.length > 0) {
191
- const uploadResponse = await startUpload({
192
- file: files,
193
- saveUploadedFile: { variables: { postId } },
194
- createUploadLink: { variables: { postId } },
327
+ // Start the AI thinking loader immediately on send
328
+ if (successThinkingTimeoutRef.current) {
329
+ clearTimeout(successThinkingTimeoutRef.current);
330
+ successThinkingTimeoutRef.current = null;
331
+ }
332
+ setIsSuccessThinking(true);
333
+ setIsLoading(true);
334
+ // Safety auto-stop in case no event arrives
335
+ successThinkingTimeoutRef.current = setTimeout(() => {
336
+ setIsSuccessThinking(false);
337
+ successThinkingTimeoutRef.current = null;
338
+ }, 15000);
339
+
340
+ // If we already have a channel, send message (with optional file upload) similar to Inbox
341
+ if (actualChannelId) {
342
+ try {
343
+ const postId = objectId();
344
+ const channelId = objectId();
345
+ const currentDate = new Date();
346
+
347
+ const createOptimisticMessage = (uploadedFiles?: any[]) => ({
348
+ __typename: 'Post' as const,
349
+ id: postId,
350
+ message,
351
+ createdAt: currentDate.toISOString(),
352
+ updatedAt: currentDate.toISOString(),
353
+ author: {
354
+ __typename: 'UserAccount' as const,
355
+ id: auth?.id,
356
+ givenName: auth?.profile?.given_name || '',
357
+ familyName: auth?.profile?.family_name || '',
358
+ email: auth?.profile?.email || '',
359
+ username: auth?.profile?.nickname || '',
360
+ fullName: auth?.profile?.name || '',
361
+ picture: auth?.profile?.picture || '',
362
+ alias: [auth?.authUserId ?? ''],
363
+ tokens: [],
364
+ },
365
+ isDelivered: false,
366
+ isRead: false,
367
+ type: 'TEXT' as PostTypeEnum,
368
+ parentId: null,
369
+ fromServer: false,
370
+ channel: {
371
+ __typename: 'Channel' as const,
372
+ id: actualChannelId,
373
+ },
374
+ propsConfiguration: {
375
+ __typename: 'MachineConfiguration' as const,
376
+ id: null,
377
+ resource: '' as any,
378
+ contents: null,
379
+ keys: null,
380
+ target: null,
381
+ overrides: null,
382
+ },
383
+ props: {},
384
+ files: {
385
+ __typename: 'FilesInfo' as const,
386
+ data: uploadedFiles || [],
387
+ totalCount: uploadedFiles?.length || 0,
388
+ },
389
+ replies: {
390
+ __typename: 'Messages' as const,
391
+ data: [],
392
+ totalCount: 0,
393
+ },
195
394
  });
196
395
 
197
- const uploadedFiles = uploadResponse.data as unknown as IFileInfo[];
198
- if (uploadedFiles) {
199
- const fileIds = uploadedFiles.map((f: any) => f.id);
396
+ const cacheMessagesQuery = {
397
+ query: MessagesDocument,
398
+ variables: {
399
+ // Match variables used in useMessagesQuery above
400
+ props: { projectId: actualChannelId.toString() },
401
+ parentId: null,
402
+ limit: MESSAGES_PER_PAGE,
403
+ },
404
+ } as const;
405
+
406
+ const extraProps = {
407
+ projectId: actualChannelId,
408
+ template: modelConfig?.template || 'vite-react',
409
+ role: 'USER',
410
+ sendNotificationWithProjectId: true,
411
+ } as any;
412
+
413
+ if (files?.length > 0) {
414
+ const uploadResponse = await startUpload({
415
+ file: files,
416
+ saveUploadedFile: { variables: { postId } },
417
+ createUploadLink: { variables: { postId } },
418
+ });
419
+
420
+ const uploadedFiles = uploadResponse.data as unknown as IFileInfo[];
421
+ const fileIds = (uploadedFiles || []).map((f: any) => f.id);
422
+
200
423
  await sendMsg({
201
- variables: { postId, channelId: actualChannelId, content: message, files: fileIds },
424
+ variables: {
425
+ postId,
426
+ channelId: actualChannelId,
427
+ content: message,
428
+ files: fileIds,
429
+ extraProps,
430
+ },
202
431
  optimisticResponse: {
203
432
  __typename: 'Mutation',
204
433
  sendMessage: createOptimisticMessage(uploadedFiles),
205
434
  },
206
435
  update: (cache, { data: mutationData }) => {
207
436
  if (!mutationData?.sendMessage) return;
208
-
209
- // Update messages cache optimistically
210
- const messagesQuery = {
211
- query: MessagesDocument,
212
- variables: {
213
- channelId: actualChannelId.toString(),
214
- parentId: null,
215
- limit: MESSAGES_PER_PAGE,
216
- },
217
- };
218
-
219
- try {
220
- const existingData = cache.readQuery(messagesQuery) as any;
221
- if (existingData?.messages) {
222
- cache.writeQuery({
223
- ...messagesQuery,
224
- data: {
225
- messages: {
226
- ...existingData.messages,
227
- data: [
228
- ...(existingData.messages.data || []),
229
- mutationData.sendMessage,
230
- ],
231
- totalCount: (existingData.messages.totalCount || 0) + 1,
232
- },
233
- },
234
- });
235
- }
236
- } catch (error) {
237
- console.debug('Cache update failed (expected on first message):', error);
437
+ if (mutationData?.sendMessage?.id) {
438
+ handleGenerateAiCode(mutationData.sendMessage.id);
439
+ }
440
+ },
441
+ });
442
+ } else {
443
+ await sendMsg({
444
+ variables: { channelId: actualChannelId, content: message, extraProps },
445
+ optimisticResponse: {
446
+ __typename: 'Mutation',
447
+ sendMessage: createOptimisticMessage(),
448
+ },
449
+ update: (cache, { data: mutationData }) => {
450
+ if (!mutationData?.sendMessage) return;
451
+ console.log('mutationData', JSON.stringify(mutationData, null, 2));
452
+ if (mutationData?.sendMessage?.id) {
453
+ handleGenerateAiCode(mutationData.sendMessage.id);
238
454
  }
239
455
  },
240
456
  });
241
457
  }
242
- } else {
243
- await sendMsg({
244
- variables: { channelId: actualChannelId, content: message },
245
- optimisticResponse: {
246
- __typename: 'Mutation',
247
- sendMessage: optimisticMessage,
248
- },
249
- update: (cache, { data: mutationData }) => {
250
- if (!mutationData?.sendMessage) return;
251
-
252
- // Update messages cache optimistically
253
- const messagesQuery = {
254
- query: MessagesDocument,
255
- variables: {
256
- channelId: actualChannelId.toString(),
257
- parentId: null,
258
- limit: MESSAGES_PER_PAGE,
259
- },
260
- };
261
458
 
262
- try {
263
- const existingData = cache.readQuery(messagesQuery) as any;
264
- if (existingData?.messages) {
265
- cache.writeQuery({
266
- ...messagesQuery,
267
- data: {
268
- messages: {
269
- ...existingData.messages,
270
- data: [...(existingData.messages.data || []), mutationData.sendMessage],
271
- totalCount: (existingData.messages.totalCount || 0) + 1,
272
- },
273
- },
274
- });
275
- }
276
- } catch (error) {
277
- console.debug('Cache update failed (expected on first message):', error);
278
- }
279
- },
280
- });
459
+ // Scroll to bottom after send
460
+ scrollToBottom('smooth', 0);
461
+ } catch (error) {
462
+ console.error('Error sending message from AIAgent:', error);
463
+ setIsLoading(false);
281
464
  }
282
465
 
283
- // Send message to AI agent machine
284
- send({ type: 'SEND_MESSAGE', message: message.trim() });
285
-
286
- // Ensure we scroll to the newest message immediately
287
- scrollToBottom('smooth', 0);
288
-
289
- // When sending new messages, increment the displayed count to show the new message
290
- // This ensures we show both the existing last message and the new message
291
- setDisplayedMessageCount((prev) => {
292
- const newCount = Math.max(prev + 1, 2);
293
- console.log('🔄 Message sent: Updating displayedMessageCount', {
294
- previous: prev,
295
- new: newCount,
296
- reason: 'New message sent',
297
- });
298
- return newCount;
299
- });
300
-
301
- // Call the optional onSendMessage prop if provided
302
- if (onSendMessage) {
303
- await onSendMessage(message, files);
304
- }
305
- } catch (error) {
306
- console.error('Error sending message:', error);
466
+ return;
307
467
  }
468
+ // If no channelId, we skip sending here.
308
469
  },
309
- [actualChannelId, currentUser, startUpload, sendMsg, send, onSendMessage],
470
+ [actualChannelId, auth, startUpload, sendMsg, scrollToBottom, getValidatedConfig, hasApiKey, modelConfig],
310
471
  );
311
472
 
312
- // Debug effect to track AI messages changes
313
- useEffect(() => {
314
- console.log('🤖 AI messages state changed:', aiMessages.length);
315
- if (aiMessages.length > 0) {
316
- console.log('🤖 Latest AI message:', aiMessages[aiMessages.length - 1]);
317
-
318
- // Clear processing message if we have new AI responses
319
- if (currentProcessingMessage !== null) {
320
- console.log('🤖 AI response received, clearing processing message');
321
- setCurrentProcessingMessage(null);
322
- }
323
-
324
- // Auto-scroll to bottom when new AI response appears
325
- scrollToBottom('smooth', 200);
326
- } else {
327
- console.log('🤖 No AI messages in state - this might indicate an issue');
328
- }
329
- }, [aiMessages, currentProcessingMessage]);
330
-
331
- // Debug effect to track machine state changes
332
- useEffect(() => {
333
- console.log('🤖 Machine state changed:', state.value);
334
- console.log('🤖 Machine context:', {
335
- messagesCount: state.context.messages.length,
336
- hasMessageToRespondTo: !!state.context.messageToRespondTo,
337
- messageToRespondTo: state.context.messageToRespondTo,
338
- });
339
-
340
- // Check if we're in processing state but no AI response is being generated
341
- if (state.value === 'processing' && !state.context.messageToRespondTo) {
342
- console.log('🤖 ⚠️ WARNING: In processing state but no messageToRespondTo!');
343
- }
344
-
345
- // Auto-scroll when AI starts processing
346
- if (state.value === 'processing') {
347
- scrollToBottom('smooth', 100);
348
- }
349
- }, [state.value, state.context, scrollToBottom]);
350
-
351
- // Send regular messages to AI agent machine for context
352
- useEffect(() => {
353
- if (regularMessages && regularMessages.length > 0) {
354
- send({ type: 'UPDATE_REGULAR_MESSAGES', messages: regularMessages });
355
- }
356
- }, [regularMessages, send]);
357
-
358
- // Reset processed messages when regular messages change completely
359
- useEffect(() => {
360
- if (regularMessages && regularMessages.length > 0) {
361
- // Check if we have completely new messages (different IDs)
362
- const currentMessageIds = new Set(regularMessages.map((msg: any) => msg.id));
363
- const hasNewMessages = !regularMessages.every((msg: any) => processedMessageIds.has(msg.id));
364
-
365
- if (hasNewMessages) {
366
- console.log('🤖 New messages detected, resetting processed state');
367
- setProcessedMessageIds(new Set());
368
- setCurrentProcessingMessage(null);
369
- }
370
- }
371
- }, [regularMessages]);
372
-
373
- // Sequential auto-response logic - respond to each message one at a time
374
- useEffect(() => {
375
- if (regularMessages && regularMessages.length > 0) {
376
- console.log('🤖 Regular messages detected:', regularMessages.length);
377
- console.log('🤖 Current AI messages:', aiMessages.length);
378
- console.log('🤖 Already processed message IDs:', Array.from(processedMessageIds));
379
-
380
- // Find messages that don't have AI responses yet and haven't been processed
381
- const messagesWithoutAIResponses = regularMessages.filter((msg: any, index: number) => {
382
- // Check if this message already has an AI response
383
- const hasAIResponse = aiMessages.length > index;
384
- // Check if this message has already been processed
385
- const alreadyProcessed = processedMessageIds.has(msg.id);
386
- return !hasAIResponse && !alreadyProcessed;
387
- });
388
-
389
- if (messagesWithoutAIResponses.length > 0) {
390
- console.log(`🤖 Found ${messagesWithoutAIResponses.length} messages to process`);
391
-
392
- // Simplified: Just process the first message and let the machine handle the rest
393
- const firstMsg = messagesWithoutAIResponses[0];
394
- console.log(`🤖 Starting with first message: ${firstMsg.message?.substring(0, 50)}...`);
395
-
396
- // Mark this message as being processed
397
- setProcessedMessageIds((prev) => new Set([...prev, firstMsg.id]));
398
- setCurrentProcessingMessage(0);
399
-
400
- // Send the first message to AI
401
- send({ type: 'AUTO_RESPOND_TO_MESSAGE', message: firstMsg.message, isAutoResponse: true });
402
- } else {
403
- console.log('🤖 All messages already have AI responses or are being processed');
404
- setCurrentProcessingMessage(null);
405
- }
406
- }
407
- }, [regularMessages.length, aiMessages.length, processedMessageIds.size]); // Only depend on lengths and processed count
408
-
409
- // Process next message when machine becomes idle (after completing AI response)
410
- useEffect(() => {
411
- // Only run when machine is idle and we have messages to process
412
- if (state.matches('idle') && regularMessages && regularMessages.length > 0) {
413
- console.log('🤖 Machine idle, checking for next message to process...');
414
- console.log(
415
- '🤖 Current state - AI messages:',
416
- aiMessages.length,
417
- 'Processed IDs:',
418
- processedMessageIds.size,
419
- );
420
-
421
- // Find the next message that needs an AI response
422
- const nextMessageIndex = regularMessages.findIndex((msg: any, index: number) => {
423
- const hasAIResponse = aiMessages.length > index;
424
- const alreadyProcessed = processedMessageIds.has(msg.id);
425
- const needsResponse = !hasAIResponse && !alreadyProcessed;
426
-
427
- console.log(
428
- `🤖 Message ${index + 1} "${msg.message?.substring(
429
- 0,
430
- 30,
431
- )}..." - hasAIResponse: ${hasAIResponse}, alreadyProcessed: ${alreadyProcessed}, needsResponse: ${needsResponse}`,
432
- );
433
-
434
- return needsResponse;
435
- });
436
-
437
- if (nextMessageIndex !== -1) {
438
- console.log(
439
- `🤖 Found next message to process: ${nextMessageIndex + 1}: ${regularMessages[
440
- nextMessageIndex
441
- ].message?.substring(0, 50)}...`,
442
- );
443
-
444
- // Check if this message is a duplicate of a previous one
445
- const currentMessage = regularMessages[nextMessageIndex];
446
- const previousMessageIndex = regularMessages.findIndex(
447
- (prevMsg: any, prevIndex: number) =>
448
- prevIndex < nextMessageIndex && prevMsg.message === currentMessage.message,
449
- );
450
-
451
- if (previousMessageIndex !== -1 && aiMessages.length > previousMessageIndex) {
452
- // This is a duplicate message, mark it as processed without sending to AI
453
- console.log(
454
- `🤖 Duplicate message detected: "${
455
- currentMessage.message
456
- }" - reusing AI response from message ${previousMessageIndex + 1}`,
457
- );
458
- setProcessedMessageIds((prev) => new Set([...prev, currentMessage.id]));
459
- setCurrentProcessingMessage(null);
460
-
461
- // Continue with next message immediately
462
- setTimeout(() => {
463
- send({ type: 'CONTINUE_PROCESSING' });
464
- }, 100);
465
- } else {
466
- // Mark this message as being processed
467
- setProcessedMessageIds((prev) => new Set([...prev, currentMessage.id]));
468
- setCurrentProcessingMessage(nextMessageIndex);
469
-
470
- // Send to AI
471
- console.log(`🤖 Sending message to AI: "${currentMessage.message}"`);
472
- send({ type: 'AUTO_RESPOND_TO_MESSAGE', message: currentMessage.message, isAutoResponse: true });
473
- }
474
- } else {
475
- // Check if we actually have all responses
476
- const totalMessagesNeedingResponses = regularMessages.length;
477
- const messagesWithResponses = aiMessages.length;
478
- const messagesProcessed = processedMessageIds.size;
479
-
480
- console.log(`🤖 No more messages to process. Summary:`, {
481
- totalMessages: totalMessagesNeedingResponses,
482
- messagesWithResponses,
483
- messagesProcessed,
484
- allHaveResponses: messagesWithResponses >= totalMessagesNeedingResponses,
485
- });
486
-
487
- if (messagesWithResponses >= totalMessagesNeedingResponses) {
488
- console.log('🤖 All messages have been processed successfully');
489
- setCurrentProcessingMessage(null);
490
- } else {
491
- console.log('🤖 ⚠️ Discrepancy detected: Some messages may not have proper AI responses');
492
- // Force a reset to try again
493
- setProcessedMessageIds(new Set());
494
- setCurrentProcessingMessage(null);
495
- }
496
- }
497
- }
498
- }, [state.value, regularMessages, aiMessages.length, processedMessageIds.size, send]);
499
-
500
- // Reset processing state when regularMessages change significantly
501
- useEffect(() => {
502
- if (regularMessages && regularMessages.length > 0) {
503
- // Reset processing state when we get new messages
504
- setProcessedMessageIds(new Set());
505
- setCurrentProcessingMessage(null);
506
- console.log('🤖 Reset processing state for new messages');
507
- }
508
- }, [regularMessages.length]); // Only depend on length to avoid unnecessary resets
509
-
510
- // Only reset displayed message count on initial load, not when new messages arrive
511
- useEffect(() => {
512
- if (regularMessages && regularMessages.length > 0) {
513
- // Only reset to show last message if this is the first time we're getting messages
514
- // or if we're currently showing more messages than we have
515
- if (displayedMessageCount === 1 || displayedMessageCount > regularMessages.length) {
516
- console.log('🔄 Resetting displayedMessageCount to 1:', {
517
- reason: 'Initial load or count mismatch',
518
- currentDisplayed: displayedMessageCount,
519
- regularMessagesLength: regularMessages.length,
520
- });
521
- setDisplayedMessageCount(1);
522
- }
523
- }
524
- }, [regularMessages.length, displayedMessageCount]);
473
+ const fixError = useCallback(
474
+ (errorMessage: string) => {
475
+ // Use sendMessage to fix the error, which will use createProject flow
476
+ return handleSend(errorMessage);
477
+ },
478
+ [handleSend],
479
+ );
525
480
 
526
481
  const handleRetry = useCallback(() => {
527
482
  send({ type: 'RETRY' });
@@ -531,200 +486,24 @@ export const AIAgent: React.FC<AIAgentProps> = ({
531
486
  send({ type: 'CLEAR_ERROR' });
532
487
  }, [send]);
533
488
 
534
- // Merge AI agent messages with regular messages from query
489
+ // Use only default/regular messages and ignore AI messages
535
490
  const allMessages = useMemo(() => {
536
491
  if (!regularMessages) return [] as any[];
537
492
 
538
- console.log(
539
- '🔄 Merging messages - AI messages:',
540
- aiMessages.length,
541
- 'Regular messages:',
542
- regularMessages.length,
543
- );
544
- console.log(
545
- '🔄 AI messages content:',
546
- aiMessages.map((m) => ({ id: m.id, content: m.content.substring(0, 50) + '...', sender: m.sender })),
547
- );
548
-
549
493
  const regularMessagesFormatted = (regularMessages || []).map((m, idx) => ({
550
494
  id: m.id || `regular-${idx}`,
551
495
  message: (m as any).message || (m as any).content || '',
552
496
  createdAt: new Date((m as any).createdAt || Date.now()),
497
+ propsConfiguration: (m as any).propsConfiguration,
498
+ props: (m as any).props,
499
+ files: (m as any).files,
553
500
  sender: 'user',
554
501
  author: (m as any).author,
555
- isUserMessage: true, // Explicitly mark as user message
556
- }));
557
-
558
- const aiMessagesFormatted = (aiMessages || []).map((m) => ({
559
- id: m.id,
560
- message: m.content,
561
- createdAt: new Date(m.timestamp || Date.now()),
562
- sender: 'ai',
563
- isUserMessage: false, // Explicitly mark as AI message
502
+ isUserMessage: true,
564
503
  }));
565
504
 
566
- // Show ALL user messages immediately, interleave AI responses as they arrive
567
- const interleavedMessages: any[] = [];
568
-
569
- console.log('🔄 Starting message interleaving...');
570
- console.log('🔄 Regular messages count:', regularMessagesFormatted.length);
571
- console.log('🔄 AI messages count:', aiMessagesFormatted.length);
572
-
573
- for (let i = 0; i < regularMessagesFormatted.length; i++) {
574
- const userMsg = regularMessagesFormatted[i];
575
- console.log(`🔄 Processing user message ${i + 1}:`, {
576
- id: userMsg.id,
577
- message: userMsg.message.substring(0, 50),
578
- sender: userMsg.sender,
579
- isUserMessage: userMsg.isUserMessage,
580
- author: userMsg.author,
581
- });
582
-
583
- interleavedMessages.push(userMsg);
584
- console.log(`🔄 Added user message ${i + 1}: ${userMsg.message.substring(0, 50)}...`);
585
-
586
- // Check if this message is a duplicate of a previous one
587
- const isDuplicate =
588
- i > 0 && regularMessagesFormatted.slice(0, i).some((prevMsg) => prevMsg.message === userMsg.message);
589
-
590
- if (isDuplicate) {
591
- // Find the first occurrence of this message to get its AI response
592
- const firstOccurrenceIndex = regularMessagesFormatted.findIndex(
593
- (msg) => msg.message === userMsg.message,
594
- );
595
- const hasAIResponseForFirst = firstOccurrenceIndex < aiMessagesFormatted.length;
596
-
597
- if (hasAIResponseForFirst) {
598
- // Reuse the AI response from the first occurrence
599
- const aiResponse = aiMessagesFormatted[firstOccurrenceIndex];
600
- const adjustedCreatedAt = new Date(userMsg.createdAt.getTime() + 1);
601
- const finalAiResponse = {
602
- ...aiResponse,
603
- createdAt: adjustedCreatedAt,
604
- id: `duplicate-${aiResponse.id}-${i}`,
605
- isDuplicate: true,
606
- };
607
- interleavedMessages.push(finalAiResponse);
608
- console.log(
609
- `🔄 Added duplicate AI response for message ${i + 1} (reusing from message ${
610
- firstOccurrenceIndex + 1
611
- }):`,
612
- {
613
- id: finalAiResponse.id,
614
- message: finalAiResponse.message.substring(0, 50),
615
- sender: finalAiResponse.sender,
616
- isUserMessage: finalAiResponse.isUserMessage,
617
- isDuplicate: finalAiResponse.isDuplicate,
618
- },
619
- );
620
- } else {
621
- // First occurrence doesn't have AI response yet, show loading
622
- const loadingMessage = {
623
- id: `loading-duplicate-${i}`,
624
- message: 'Loading...', // Shorter text since we show visual loader
625
- createdAt: new Date(userMsg.createdAt.getTime() + 1),
626
- sender: 'ai',
627
- isLoading: true,
628
- isProcessing: false,
629
- isUserMessage: false,
630
- isDuplicate: true,
631
- };
632
- interleavedMessages.push(loadingMessage);
633
- console.log(`🔄 Added loading indicator for duplicate message ${i + 1}:`, {
634
- id: loadingMessage.id,
635
- message: loadingMessage.message,
636
- isDuplicate: loadingMessage.isDuplicate,
637
- });
638
- }
639
- } else {
640
- // This is not a duplicate, handle normally
641
- if (i < aiMessagesFormatted.length) {
642
- const aiResponse = aiMessagesFormatted[i];
643
- // Ensure AI response sorts after corresponding user message
644
- const adjustedCreatedAt = new Date(userMsg.createdAt.getTime() + 1);
645
- const finalAiResponse = { ...aiResponse, createdAt: adjustedCreatedAt };
646
- interleavedMessages.push(finalAiResponse);
647
- console.log(`🔄 Added AI response for message ${i + 1}:`, {
648
- id: finalAiResponse.id,
649
- message: finalAiResponse.message.substring(0, 50),
650
- sender: finalAiResponse.sender,
651
- isUserMessage: finalAiResponse.isUserMessage,
652
- });
653
- } else {
654
- // Add a loading indicator for messages waiting for AI responses
655
- const loadingMessage = {
656
- id: `loading-${i}`,
657
- message: 'Loading...', // Shorter text since we show visual loader
658
- createdAt: new Date(userMsg.createdAt.getTime() + 1),
659
- sender: 'ai',
660
- isLoading: true,
661
- isProcessing: currentProcessingMessage === i,
662
- isUserMessage: false, // Explicitly mark as not a user message
663
- };
664
- interleavedMessages.push(loadingMessage);
665
- console.log(`🔄 Added loading indicator for message ${i + 1}:`, {
666
- id: loadingMessage.id,
667
- message: loadingMessage.message,
668
- sender: loadingMessage.sender,
669
- isUserMessage: loadingMessage.isUserMessage,
670
- isProcessing: loadingMessage.isProcessing,
671
- });
672
- }
673
- }
674
- }
675
-
676
- console.log('🔄 Final interleaved messages:', interleavedMessages.length);
677
- console.log(
678
- '🔄 Message structure:',
679
- interleavedMessages.map((m) => ({
680
- sender: m.sender,
681
- isUserMessage: m.isUserMessage,
682
- message: m.message.substring(0, 30) + '...',
683
- })),
684
- );
685
-
686
- return interleavedMessages;
687
- }, [aiMessages, regularMessages, currentUser, currentProcessingMessage]);
688
-
689
- // Update hasMoreMessages when allMessages changes
690
- useEffect(() => {
691
- if (allMessages && allMessages.length > 0) {
692
- // Count complete conversation pairs instead of individual messages
693
- const conversationPairs = Math.ceil(allMessages.length / 2);
694
- console.log('🔄 Conversation pairs calculation:', {
695
- totalMessages: allMessages.length,
696
- conversationPairs,
697
- displayedMessageCount,
698
- hasMore: conversationPairs > displayedMessageCount,
699
- });
700
-
701
- // Show "Load Past Messages" whenever there are more conversation pairs
702
- // than currently displayed (including initial view)
703
- const hasMore = conversationPairs > displayedMessageCount;
704
- setHasMoreMessages(hasMore);
705
-
706
- // Ensure we always show at least the last conversation
707
- if (displayedMessageCount === 0) {
708
- setDisplayedMessageCount(1);
709
- }
710
- } else {
711
- setHasMoreMessages(false);
712
- }
713
- }, [allMessages, displayedMessageCount]);
714
-
715
- // Function to load more past messages
716
- const loadMoreMessages = useCallback(() => {
717
- if (allMessages && allMessages.length > 0) {
718
- // Load more conversation pairs (5 pairs = 10 messages)
719
- const conversationPairs = Math.ceil(allMessages.length / 2);
720
- setDisplayedMessageCount((prev) => Math.min(prev + 5, conversationPairs));
721
- }
722
- }, [allMessages]);
723
-
724
- // Function to reset to showing only last message
725
- const resetToLastMessage = useCallback(() => {
726
- setDisplayedMessageCount(1);
727
- }, []);
505
+ return regularMessagesFormatted;
506
+ }, [regularMessages, currentUser]);
728
507
 
729
508
  // Build list with date separators similar to MessagesBuilderUi
730
509
  const messageListWithDates = useMemo(() => {
@@ -778,59 +557,11 @@ export const AIAgent: React.FC<AIAgentProps> = ({
778
557
  return sections;
779
558
  }, [messageListWithDates]);
780
559
 
781
- // Limit messages to display based on displayedMessageCount (conversation pairs)
560
+ // Show all messages without limiting
782
561
  const limitedMessagesByDate = useMemo(() => {
783
562
  if (!messagesByDate || messagesByDate.length === 0) return [];
784
-
785
- // Calculate how many conversation pairs we should show
786
- let conversationPairs = 0;
787
- const limitedSections: { date: string | null; messages: any[] }[] = [];
788
-
789
- // Start from the end (most recent messages) and work backwards
790
- for (let i = messagesByDate.length - 1; i >= 0; i--) {
791
- const section = messagesByDate[i];
792
- const sectionMessages = [...section.messages];
793
-
794
- // Count conversation pairs in this section
795
- let sectionPairs = 0;
796
- for (let j = 0; j < sectionMessages.length; j++) {
797
- const currentMsg = sectionMessages[j];
798
- if (currentMsg.sender === 'user' || currentMsg.isUserMessage) {
799
- // Check if there's an AI response following this user message
800
- if (j + 1 < sectionMessages.length) {
801
- const nextMsg = sectionMessages[j + 1];
802
- if (nextMsg.sender === 'ai' || !nextMsg.isUserMessage) {
803
- sectionPairs++;
804
- j++; // Skip the AI response since we counted it as a pair
805
- }
806
- } else {
807
- sectionPairs++; // User message without AI response still counts
808
- }
809
- } else if (currentMsg.sender === 'ai' || !currentMsg.isUserMessage) {
810
- // AI message without preceding user message counts as half a pair
811
- sectionPairs += 0.5;
812
- }
813
- }
814
-
815
- // If this section would exceed our limit, only take what we can fit
816
- if (conversationPairs + sectionPairs > displayedMessageCount) {
817
- const remainingPairs = displayedMessageCount - conversationPairs;
818
- if (remainingPairs > 0) {
819
- // Take only the last N conversation pairs from this section
820
- const limitedMessages = sectionMessages.slice(-Math.floor(remainingPairs * 2));
821
- limitedSections.unshift({ ...section, messages: limitedMessages });
822
- conversationPairs += remainingPairs;
823
- }
824
- break;
825
- } else {
826
- // We can fit this entire section
827
- limitedSections.unshift(section);
828
- conversationPairs += sectionPairs;
829
- }
830
- }
831
-
832
- return limitedSections;
833
- }, [messagesByDate, displayedMessageCount]);
563
+ return messagesByDate;
564
+ }, [messagesByDate]);
834
565
 
835
566
  // Ensure we show complete conversation pairs (user message + AI response)
836
567
  const completeConversationMessages = useMemo(() => {
@@ -893,15 +624,31 @@ export const AIAgent: React.FC<AIAgentProps> = ({
893
624
 
894
625
  // Auto scroll to bottom when messages, typing status, or AI responses change
895
626
  useEffect(() => {
896
- scrollToBottom('smooth', 100);
627
+ // scrollToBottom('smooth', 100);
897
628
  }, [completeConversationMessages?.length, isTyping, aiMessages.length, scrollToBottom]);
898
629
 
899
- // Auto scroll when processing message changes (shows loading indicators)
630
+ // Cleanup any pending timeout on unmount
900
631
  useEffect(() => {
901
- if (currentProcessingMessage !== null) {
902
- scrollToBottom('smooth', 150);
632
+ return () => {
633
+ if (successThinkingTimeoutRef.current) {
634
+ clearTimeout(successThinkingTimeoutRef.current);
635
+ }
636
+ };
637
+ }, []);
638
+
639
+ const extractCleanContent = useCallback((summary: string): string => {
640
+ if (!summary) return 'Project generated successfully!';
641
+
642
+ // Extract content from <task_summary> tags
643
+ const taskSummaryMatch = summary.match(/<task_summary>([\s\S]*?)<\/task_summary>/);
644
+ if (taskSummaryMatch) {
645
+ return taskSummaryMatch[1].trim();
903
646
  }
904
- }, [currentProcessingMessage, scrollToBottom]);
647
+
648
+ // If no task_summary tags, remove canvas_layers and return the rest
649
+ const withoutCanvasLayers = summary.replace(/<canvas_layers>[\s\S]*?<\/canvas_layers>/g, '').trim();
650
+ return withoutCanvasLayers || 'Project generated successfully!';
651
+ }, []);
905
652
 
906
653
  return (
907
654
  <div className={`flex flex-col h-full ${className}`}>
@@ -937,14 +684,8 @@ export const AIAgent: React.FC<AIAgentProps> = ({
937
684
  </div>
938
685
 
939
686
  {/* New Chat Button */}
940
- <button
687
+ {/* <button
941
688
  onClick={() => {
942
- // Reset to show only the last message for new chat
943
- setDisplayedMessageCount(1);
944
- setHasMoreMessages(false);
945
- // Clear processing state
946
- setProcessedMessageIds(new Set());
947
- setCurrentProcessingMessage(null);
948
689
  // Clear AI messages by sending update event to the machine
949
690
  send({ type: 'UPDATE', value: { messages: [] } });
950
691
  }}
@@ -952,7 +693,7 @@ export const AIAgent: React.FC<AIAgentProps> = ({
952
693
  style={{ borderRadius: '10px' }}
953
694
  >
954
695
  New Chat
955
- </button>
696
+ </button> */}
956
697
  </div>
957
698
  </div>
958
699
 
@@ -1001,72 +742,84 @@ export const AIAgent: React.FC<AIAgentProps> = ({
1001
742
  {/* Message History List */}
1002
743
  <div className="px-4 py-2">
1003
744
  {regularMessages && regularMessages.length > 0 ? (
1004
- regularMessages.map((msg: any, index: number) => {
1005
- const messageTime = new Date(msg.createdAt);
1006
-
1007
- return (
1008
- <div key={msg.id || index} className="mb-4">
1009
- {/* Restore to this point button */}
1010
- <div className="flex items-center justify-end mb-2">
1011
- <button className="text-blue-600 text-sm underline hover:text-blue-800 flex items-center">
1012
- <svg
1013
- className="w-4 h-4 mr-1"
1014
- fill="none"
1015
- viewBox="0 0 24 24"
1016
- stroke="currentColor"
1017
- >
1018
- <path
1019
- strokeLinecap="round"
1020
- strokeLinejoin="round"
1021
- strokeWidth={2}
1022
- d="M15 19l-7-7 7-7"
1023
- />
1024
- </svg>
1025
- Restore to this point
1026
- </button>
1027
- </div>
1028
-
1029
- {/* Message using same container as Chat tab */}
1030
- <ModernMessageGroupComponent
1031
- messages={[
1032
- {
1033
- id: msg.id,
1034
- message: msg.message || msg.content || 'No message content',
1035
- author: msg.author || {
1036
- id: 'user',
1037
- givenName: 'User',
1038
- familyName: '',
1039
- fullName: 'User',
1040
- username: 'user',
1041
- email: '',
1042
- picture: null,
1043
- alias: [],
1044
- tokens: [],
745
+ regularMessages
746
+ .filter((msg: any) => (msg as any)?.propsConfiguration?.contents?.role === 'USER')
747
+ .map((msg: any, index: number) => {
748
+ const messageTime = new Date(msg.createdAt);
749
+
750
+ return (
751
+ <div key={msg.id || index} className="mb-4">
752
+ {/* Restore to this point button */}
753
+ <div className="flex items-center justify-end mb-2">
754
+ <button className="text-blue-600 text-sm underline hover:text-blue-800 flex items-center">
755
+ <svg
756
+ className="w-4 h-4 mr-1"
757
+ fill="none"
758
+ viewBox="0 0 24 24"
759
+ stroke="currentColor"
760
+ >
761
+ <path
762
+ strokeLinecap="round"
763
+ strokeLinejoin="round"
764
+ strokeWidth={2}
765
+ d="M15 19l-7-7 7-7"
766
+ />
767
+ </svg>
768
+ Restore to this point
769
+ </button>
770
+ </div>
771
+
772
+ {/* Message using same container as Chat tab */}
773
+ <ModernMessageGroupComponent
774
+ messages={[
775
+ {
776
+ id: msg.id,
777
+ //message: msg.message || msg.content || 'No message content',
778
+ message:
779
+ (msg as any)?.propsConfiguration?.contents?.role ===
780
+ 'ASSISTANT'
781
+ ? msg.propsConfiguration?.contents?.fragment?.summary
782
+ ? extractCleanContent(
783
+ msg.propsConfiguration?.contents?.fragment
784
+ ?.summary,
785
+ )
786
+ : msg.message
787
+ : msg.message,
788
+ author: msg.author || {
789
+ id: 'user',
790
+ givenName: 'User',
791
+ familyName: '',
792
+ fullName: 'User',
793
+ username: 'user',
794
+ email: '',
795
+ picture: null,
796
+ alias: [],
797
+ tokens: [],
798
+ },
799
+ createdAt: msg.createdAt,
800
+ type: 'Simple' as any,
801
+ isDelivered: false,
802
+ isRead: false,
803
+ parentId: null,
804
+ fromServer: null,
805
+ updatedAt: msg.createdAt,
806
+ propsConfiguration: msg.propsConfiguration,
807
+ props: msg.props,
808
+ files: msg.files,
809
+ replies: null,
810
+ channel: null,
811
+ isPinned: false,
1045
812
  },
1046
- createdAt: msg.createdAt,
1047
- type: 'Simple' as any,
1048
- isDelivered: false,
1049
- isRead: false,
1050
- parentId: null,
1051
- fromServer: null,
1052
- updatedAt: msg.createdAt,
1053
- propsConfiguration: null,
1054
- props: null,
1055
- files: null,
1056
- replies: null,
1057
- channel: null,
1058
- isPinned: false,
1059
- },
1060
- ]}
1061
- currentUser={currentUser as any}
1062
- onOpen={onOpen}
1063
- onMessageClick={() => {}}
1064
- isDesktopView={isDesktopView}
1065
- isSmallScreen={isSmallScreen}
1066
- />
1067
- </div>
1068
- );
1069
- })
813
+ ]}
814
+ currentUser={currentUser as any}
815
+ onOpen={onOpen}
816
+ onMessageClick={(msg) => setSelectedPost(msg)}
817
+ isDesktopView={isDesktopView}
818
+ isSmallScreen={isSmallScreen}
819
+ />
820
+ </div>
821
+ );
822
+ })
1070
823
  ) : (
1071
824
  // Fallback entries when no messages
1072
825
  <>
@@ -1108,16 +861,7 @@ export const AIAgent: React.FC<AIAgentProps> = ({
1108
861
  }`}
1109
862
  >
1110
863
  {/* Load Past Messages Button */}
1111
- {hasMoreMessages && (
1112
- <div className="px-4 py-3 text-center">
1113
- <button
1114
- onClick={loadMoreMessages}
1115
- className="px-4 py-2 text-sm text-gray-600 bg-gray-100 border border-gray-300 rounded-lg hover:bg-gray-200 transition-colors"
1116
- >
1117
- Load Past Messages
1118
- </button>
1119
- </div>
1120
- )}
864
+ {/* Load Past Messages disabled to show all messages */}
1121
865
 
1122
866
  {/* Show placeholder when no messages */}
1123
867
  {(!completeConversationMessages || completeConversationMessages.length === 0) && (
@@ -1170,7 +914,18 @@ export const AIAgent: React.FC<AIAgentProps> = ({
1170
914
  messages={[
1171
915
  {
1172
916
  id: msg.id,
1173
- message: msg.message,
917
+ // message: msg.message,
918
+ message:
919
+ (msg as any)?.propsConfiguration?.contents?.role ===
920
+ 'ASSISTANT'
921
+ ? msg.propsConfiguration?.contents?.fragment
922
+ ?.summary
923
+ ? extractCleanContent(
924
+ msg.propsConfiguration?.contents?.fragment
925
+ ?.summary,
926
+ )
927
+ : msg.message
928
+ : msg.message,
1174
929
  author:
1175
930
  msg.sender === 'user'
1176
931
  ? msg.author
@@ -1192,9 +947,9 @@ export const AIAgent: React.FC<AIAgentProps> = ({
1192
947
  parentId: null,
1193
948
  fromServer: null,
1194
949
  updatedAt: msg.createdAt,
1195
- propsConfiguration: null,
1196
- props: null,
1197
- files: null,
950
+ propsConfiguration: msg.propsConfiguration,
951
+ props: msg.props,
952
+ files: msg.files,
1198
953
  replies: null,
1199
954
  channel: null,
1200
955
  isPinned: false,
@@ -1202,9 +957,13 @@ export const AIAgent: React.FC<AIAgentProps> = ({
1202
957
  ]}
1203
958
  currentUser={currentUser as any}
1204
959
  onOpen={onOpen}
1205
- onMessageClick={() => {}}
960
+ onMessageClick={(msg) => setSelectedPost(msg)}
1206
961
  isDesktopView={isDesktopView}
1207
962
  isSmallScreen={isSmallScreen}
963
+ sandboxErrors={sandboxErrors}
964
+ currentFiles={currentFiles}
965
+ onFixError={fixError}
966
+ onRecreateSandbox={recreateSandboxForFragment}
1208
967
  />
1209
968
  )}
1210
969
  </div>
@@ -1214,7 +973,7 @@ export const AIAgent: React.FC<AIAgentProps> = ({
1214
973
  ))}
1215
974
 
1216
975
  {/* Typing indicator */}
1217
- {isTyping && (
976
+ {(isTyping || isSuccessThinking || isLoading) && (
1218
977
  <div className="px-4">
1219
978
  <div className="flex justify-start">
1220
979
  <div className="bg-gray-50 border border-gray-200 px-4 py-3 rounded-lg">
@@ -1243,6 +1002,7 @@ export const AIAgent: React.FC<AIAgentProps> = ({
1243
1002
  return prev;
1244
1003
  }
1245
1004
  const newMessage = subscriptionData.data.chatMessageAdded;
1005
+
1246
1006
  console.log('New message received via subscription:', newMessage);
1247
1007
 
1248
1008
  return {
@@ -1287,28 +1047,6 @@ export const AIAgent: React.FC<AIAgentProps> = ({
1287
1047
  {/* Input Area - Only show for Chat tab */}
1288
1048
  {activeTab === 'chat' && (
1289
1049
  <div className="border-t border-gray-200 bg-white">
1290
- {/* Processing Status Indicator */}
1291
- {currentProcessingMessage !== null && (
1292
- <div className="px-4 py-2 border-b border-blue-100 bg-blue-50">
1293
- <div className="flex items-center space-x-2 text-sm text-blue-700">
1294
- <div className="flex space-x-1">
1295
- <div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce"></div>
1296
- <div
1297
- className="w-2 h-2 bg-blue-500 rounded-full animate-bounce"
1298
- style={{ animationDelay: '0.1s' }}
1299
- ></div>
1300
- <div
1301
- className="w-2 h-2 bg-blue-500 rounded-full animate-bounce"
1302
- style={{ animationDelay: '0.2s' }}
1303
- ></div>
1304
- </div>
1305
- <span>
1306
- AI is processing message {currentProcessingMessage + 1} of {regularMessages.length}
1307
- </span>
1308
- </div>
1309
- </div>
1310
- )}
1311
-
1312
1050
  {/* Removed temporary test button */}
1313
1051
  <InputComponent channelId={actualChannelId} handleSend={handleSend} placeholder={placeholder} />
1314
1052
  </div>