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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/lib/components/AIAgent/AIAgent.d.ts +14 -0
  3. package/lib/components/AIAgent/AIAgent.d.ts.map +1 -0
  4. package/lib/components/AIAgent/AIAgent.js +1148 -0
  5. package/lib/components/AIAgent/AIAgent.js.map +1 -0
  6. package/lib/components/AIAgent/index.d.ts +2 -0
  7. package/lib/components/AIAgent/index.d.ts.map +1 -0
  8. package/lib/components/InboxMessage/InputComponent.d.ts +9 -0
  9. package/lib/components/InboxMessage/InputComponent.d.ts.map +1 -0
  10. package/lib/components/InboxMessage/InputComponent.js +210 -0
  11. package/lib/components/InboxMessage/InputComponent.js.map +1 -0
  12. package/lib/components/InboxMessage/MessageInput.d.ts.map +1 -1
  13. package/lib/components/InboxMessage/MessageInput.js +14 -10
  14. package/lib/components/InboxMessage/MessageInput.js.map +1 -1
  15. package/lib/components/InboxMessage/MessageInputComponent.d.ts +9 -0
  16. package/lib/components/InboxMessage/MessageInputComponent.d.ts.map +1 -0
  17. package/lib/components/InboxMessage/MessageInputComponent.js +173 -0
  18. package/lib/components/InboxMessage/MessageInputComponent.js.map +1 -0
  19. package/lib/components/InboxMessage/Messages.d.ts.map +1 -1
  20. package/lib/components/InboxMessage/Messages.js +4 -54
  21. package/lib/components/InboxMessage/Messages.js.map +1 -1
  22. package/lib/components/InboxMessage/MessagesBuilderUi.d.ts +17 -0
  23. package/lib/components/InboxMessage/MessagesBuilderUi.d.ts.map +1 -0
  24. package/lib/components/InboxMessage/MessagesBuilderUi.js +162 -0
  25. package/lib/components/InboxMessage/MessagesBuilderUi.js.map +1 -0
  26. package/lib/components/InboxMessage/UploadImageButton.d.ts +1 -0
  27. package/lib/components/InboxMessage/UploadImageButton.d.ts.map +1 -1
  28. package/lib/components/InboxMessage/UploadImageButton.js +3 -3
  29. package/lib/components/InboxMessage/UploadImageButton.js.map +1 -1
  30. package/lib/components/InboxMessage/index.d.ts +3 -0
  31. package/lib/components/InboxMessage/index.d.ts.map +1 -1
  32. package/lib/components/InboxMessage/message-widgets/CommonMessage.d.ts.map +1 -1
  33. package/lib/components/InboxMessage/message-widgets/CommonMessage.js +11 -6
  34. package/lib/components/InboxMessage/message-widgets/CommonMessage.js.map +1 -1
  35. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts +14 -0
  36. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts.map +1 -0
  37. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js +1525 -0
  38. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js.map +1 -0
  39. package/lib/components/InboxMessage/message-widgets/PlainMessage.d.ts.map +1 -1
  40. package/lib/components/InboxMessage/message-widgets/PlainMessage.js +6 -3
  41. package/lib/components/InboxMessage/message-widgets/PlainMessage.js.map +1 -1
  42. package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.d.ts.map +1 -1
  43. package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.js +207 -12
  44. package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.js.map +1 -1
  45. package/lib/components/InboxMessage/message-widgets/index.d.ts +1 -0
  46. package/lib/components/InboxMessage/message-widgets/index.d.ts.map +1 -1
  47. package/lib/components/index.d.ts +2 -1
  48. package/lib/components/index.d.ts.map +1 -1
  49. package/lib/compute.d.ts.map +1 -1
  50. package/lib/compute.js +79 -3
  51. package/lib/compute.js.map +1 -1
  52. package/lib/config/env-config.d.ts +6 -0
  53. package/lib/config/env-config.d.ts.map +1 -1
  54. package/lib/config/env-config.js +19 -1
  55. package/lib/config/env-config.js.map +1 -1
  56. package/lib/container/AiInbox.d.ts +15 -0
  57. package/lib/container/AiInbox.d.ts.map +1 -0
  58. package/lib/container/AiInbox.js +1520 -0
  59. package/lib/container/AiInbox.js.map +1 -0
  60. package/lib/container/AiInboxWithLoader.d.ts +36 -0
  61. package/lib/container/AiInboxWithLoader.d.ts.map +1 -0
  62. package/lib/container/AiInboxWithLoader.js +300 -0
  63. package/lib/container/AiInboxWithLoader.js.map +1 -0
  64. package/lib/container/AiLandingInput.d.ts +4 -0
  65. package/lib/container/AiLandingInput.d.ts.map +1 -0
  66. package/lib/container/AiLandingInput.js +164 -0
  67. package/lib/container/AiLandingInput.js.map +1 -0
  68. package/lib/container/Inbox.d.ts.map +1 -1
  69. package/lib/container/Inbox.js +6 -4
  70. package/lib/container/Inbox.js.map +1 -1
  71. package/lib/container/InboxAiMessagesLoader.d.ts +36 -0
  72. package/lib/container/InboxAiMessagesLoader.d.ts.map +1 -0
  73. package/lib/container/InboxAiMessagesLoader.js +47 -0
  74. package/lib/container/InboxAiMessagesLoader.js.map +1 -0
  75. package/lib/container/InboxContainer.d.ts +12 -0
  76. package/lib/container/InboxContainer.d.ts.map +1 -0
  77. package/lib/container/InboxContainer.js +31 -0
  78. package/lib/container/InboxContainer.js.map +1 -0
  79. package/lib/container/InboxTemplate1.d.ts +15 -0
  80. package/lib/container/InboxTemplate1.d.ts.map +1 -0
  81. package/lib/container/InboxTemplate1.js +1375 -0
  82. package/lib/container/InboxTemplate1.js.map +1 -0
  83. package/lib/container/InboxTemplate1WithLoader.d.ts +36 -0
  84. package/lib/container/InboxTemplate1WithLoader.d.ts.map +1 -0
  85. package/lib/container/InboxTemplate2.d.ts +15 -0
  86. package/lib/container/InboxTemplate2.d.ts.map +1 -0
  87. package/lib/container/InboxTemplate2.js +1426 -0
  88. package/lib/container/InboxTemplate2.js.map +1 -0
  89. package/lib/container/InboxWithAiLoader.d.ts +15 -0
  90. package/lib/container/InboxWithAiLoader.d.ts.map +1 -0
  91. package/lib/container/InboxWithAiLoader.js +56 -0
  92. package/lib/container/InboxWithAiLoader.js.map +1 -0
  93. package/lib/container/ServiceInbox.js +1 -1
  94. package/lib/container/ServiceInbox.js.map +1 -1
  95. package/lib/container/ThreadMessages.js +1 -1
  96. package/lib/container/ThreadMessages.js.map +1 -1
  97. package/lib/container/ThreadMessagesInbox.js +1 -1
  98. package/lib/container/ThreadMessagesInbox.js.map +1 -1
  99. package/lib/container/Threads.js +1 -1
  100. package/lib/container/Threads.js.map +1 -1
  101. package/lib/container/index.d.ts +4 -1
  102. package/lib/container/index.d.ts.map +1 -1
  103. package/lib/index.js +1 -1
  104. package/lib/machines/aiAgentMachine.d.ts +3 -0
  105. package/lib/machines/aiAgentMachine.d.ts.map +1 -0
  106. package/lib/machines/aiAgentMachine.js +1040 -0
  107. package/lib/machines/aiAgentMachine.js.map +1 -0
  108. package/lib/machines/types.d.ts +77 -0
  109. package/lib/machines/types.d.ts.map +1 -0
  110. package/lib/routes.json +40 -0
  111. package/lib/templates/InboxWithAi.d.ts +15 -0
  112. package/lib/templates/InboxWithAi.d.ts.map +1 -0
  113. package/lib/templates/InboxWithAi.js +405 -0
  114. package/lib/templates/InboxWithAi.js.map +1 -0
  115. package/lib/templates/InboxWithAi.tsx +502 -0
  116. package/package.json +7 -5
  117. package/src/components/AIAgent/AIAgent.tsx +1351 -0
  118. package/src/components/AIAgent/README.md +82 -0
  119. package/src/components/AIAgent/index.ts +1 -0
  120. package/src/components/InboxMessage/InputComponent.tsx +263 -0
  121. package/src/components/InboxMessage/MessageInput.tsx +73 -66
  122. package/src/components/InboxMessage/MessageInputComponent.tsx +245 -0
  123. package/src/components/InboxMessage/Messages.tsx +2 -56
  124. package/src/components/InboxMessage/MessagesBuilderUi.tsx +205 -0
  125. package/src/components/InboxMessage/UploadImageButton.tsx +3 -2
  126. package/src/components/InboxMessage/index.ts +3 -0
  127. package/src/components/InboxMessage/message-widgets/CommonMessage.tsx +39 -21
  128. package/src/components/InboxMessage/message-widgets/ModernMessageGroup.tsx +1968 -0
  129. package/src/components/InboxMessage/message-widgets/PlainMessage.tsx +6 -2
  130. package/src/components/InboxMessage/message-widgets/SlackLikeMessageGroup.tsx +306 -54
  131. package/src/components/InboxMessage/message-widgets/index.ts +1 -0
  132. package/src/components/index.ts +4 -0
  133. package/src/compute.ts +83 -2
  134. package/src/config/env-config.ts +6 -0
  135. package/src/container/AiInbox.tsx +1796 -0
  136. package/src/container/AiInboxWithLoader.tsx +356 -0
  137. package/src/container/AiLandingInput.tsx +168 -0
  138. package/src/container/Inbox.tsx +8 -5
  139. package/src/container/InboxAiMessagesLoader.tsx +68 -0
  140. package/src/container/InboxContainer.tsx +35 -0
  141. package/src/container/InboxTemplate1.tsx +1542 -0
  142. package/src/container/InboxTemplate1WithLoader.tsx +338 -0
  143. package/src/container/InboxTemplate2.tsx +1606 -0
  144. package/src/container/InboxWithAiLoader.tsx +76 -0
  145. package/src/container/index.ts +15 -1
  146. package/src/machines/aiAgentMachine.ts +1248 -0
  147. package/src/machines/types.ts +59 -0
  148. package/src/templates/InboxWithAi.tsx +502 -0
@@ -0,0 +1,1606 @@
1
+ import React, {
2
+ ReactNode,
3
+ useCallback,
4
+ useEffect,
5
+ useMemo,
6
+ useReducer,
7
+ useRef,
8
+ createContext,
9
+ useContext,
10
+ useState,
11
+ } from 'react';
12
+ import { orderBy, uniqBy } from 'lodash-es';
13
+ import {
14
+ OnChatMessageAddedDocument as CHAT_MESSAGE_ADDED,
15
+ useSendMessagesMutation,
16
+ GetChannelsByUserDocument,
17
+ MessagesDocument,
18
+ } from 'common/graphql';
19
+ import { useUploadFiles } from '@messenger-box/platform-client';
20
+ import { IFileInfo, RoomType, PostTypeEnum } from 'common';
21
+ import { useSelector, shallowEqual } from 'react-redux';
22
+ import { useNavigate, useParams } from '@remix-run/react';
23
+ import { LeftSidebar, MessagesBuilderUi, InputComponent, AIAgent } from '../components';
24
+ import { Store, userSelector } from '@adminide-stack/user-auth0-client';
25
+ import { IUserState } from '@adminide-stack/core';
26
+ import { config } from '../config';
27
+ import { applyFooterStyles } from './apply-footer-styles';
28
+ import { objectId } from '@messenger-box/core';
29
+ import { ThreadsInbox } from './ThreadsInbox';
30
+ import { ThreadMessagesInbox } from './ThreadMessagesInbox';
31
+ import { useApolloClient } from '@apollo/client';
32
+ import { SubscriptionHandler } from '../components/InboxMessage/SubscriptionHandler';
33
+
34
+ const { MESSAGES_PER_PAGE } = config;
35
+
36
+ // Context for sharing tab state between header and sidebar
37
+ const TabContext = createContext<{
38
+ activeTab: string;
39
+ setActiveTab: (tab: string) => void;
40
+ }>({
41
+ activeTab: 'design',
42
+ setActiveTab: () => {},
43
+ });
44
+
45
+ // Types
46
+ interface DrawerProps {
47
+ isOpen: boolean;
48
+ onClose: () => void;
49
+ children: ReactNode;
50
+ title?: string;
51
+ }
52
+
53
+ export interface InboxProps {
54
+ channelFilters?: Record<string, unknown>;
55
+ channelRole?: string;
56
+ supportServices?: boolean;
57
+ pathPrefix?: string;
58
+ data?: any;
59
+ orgName?: string;
60
+ }
61
+
62
+ interface MobilePreviewState {
63
+ mobilePreviewVisibility: boolean;
64
+ mobilePreviewText: string | ReactNode;
65
+ mobilePreviewCTAText: string | ReactNode;
66
+ }
67
+
68
+ // Static utility hooks and components
69
+ const useMediaQuery = (query: string) => {
70
+ const [matches, setMatches] = React.useState(false);
71
+
72
+ useEffect(() => {
73
+ if (typeof window === 'undefined') return;
74
+
75
+ const mediaQuery = window.matchMedia(query);
76
+ const updateMatches = () => setMatches(mediaQuery.matches);
77
+
78
+ updateMatches();
79
+ mediaQuery.addEventListener('change', updateMatches);
80
+ return () => mediaQuery.removeEventListener('change', updateMatches);
81
+ }, [query]);
82
+
83
+ return matches;
84
+ };
85
+
86
+ // Hook to get window dimensions
87
+ const useWindowDimensions = () => {
88
+ const [windowDimensions, setWindowDimensions] = React.useState({
89
+ width: typeof window !== 'undefined' ? window.innerWidth : 1024,
90
+ height: typeof window !== 'undefined' ? window.innerHeight : 768,
91
+ });
92
+
93
+ useEffect(() => {
94
+ if (typeof window === 'undefined') return;
95
+
96
+ const handleResize = () => {
97
+ setWindowDimensions({
98
+ width: window.innerWidth,
99
+ height: window.innerHeight,
100
+ });
101
+ };
102
+
103
+ window.addEventListener('resize', handleResize);
104
+ handleResize(); // Set initial dimensions
105
+
106
+ return () => window.removeEventListener('resize', handleResize);
107
+ }, []);
108
+
109
+ return windowDimensions;
110
+ };
111
+
112
+ // Static components
113
+ const Spinner = React.memo(({ className = '' }: { className?: string }) => (
114
+ <div className={`animate-spin rounded-full border-4 border-gray-200 border-t-blue-500 ${className}`}>
115
+ <span className="sr-only">Loading...</span>
116
+ </div>
117
+ ));
118
+
119
+ const Drawer = React.memo(({ isOpen, onClose, children, title }: DrawerProps) => {
120
+ if (!isOpen) return null;
121
+
122
+ return (
123
+ <div className="fixed inset-0 z-50 overflow-hidden">
124
+ <div className="absolute inset-0 bg-black bg-opacity-50" onClick={onClose} />
125
+ <div className="absolute bottom-0 left-0 right-0 bg-white rounded-t-lg shadow-lg max-h-[80vh] flex flex-col overflow-hidden">
126
+ <div className="flex items-center justify-between p-4 border-b border-gray-200 flex-shrink-0">
127
+ <h2 className="text-lg font-semibold truncate">{title}</h2>
128
+ <button
129
+ onClick={onClose}
130
+ className="p-1 hover:bg-gray-100 rounded-full transition-colors flex-shrink-0 ml-2"
131
+ >
132
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
133
+ <path
134
+ strokeLinecap="round"
135
+ strokeLinejoin="round"
136
+ strokeWidth={2}
137
+ d="M6 18L18 6M6 6l12 12"
138
+ />
139
+ </svg>
140
+ </button>
141
+ </div>
142
+ <div className="flex-1 p-4 overflow-y-auto" style={{ minHeight: 0 }}>
143
+ {children}
144
+ </div>
145
+ </div>
146
+ </div>
147
+ );
148
+ });
149
+
150
+ const EmptyState = React.memo(() => (
151
+ <div className="h-full flex items-center justify-center bg-gray-100 p-4 sm:p-6 overflow-hidden">
152
+ <div className="text-center max-w-sm mx-auto">
153
+ <div className="text-3xl sm:text-4xl text-gray-400 mb-4">💬</div>
154
+ <h3 className="text-lg sm:text-xl font-semibold text-gray-600 mb-2">Welcome to Messenger</h3>
155
+ <p className="text-sm sm:text-base text-gray-500 leading-relaxed">
156
+ Select a conversation from the sidebar to start messaging
157
+ </p>
158
+ </div>
159
+ </div>
160
+ ));
161
+
162
+ // Mobile preview reducer
163
+ const mobilePreviewReducer = (
164
+ state: MobilePreviewState,
165
+ action: { payload: Partial<MobilePreviewState>; type: string },
166
+ ) => {
167
+ if (action.type === 'update') {
168
+ return { ...state, ...action.payload };
169
+ }
170
+ return state;
171
+ };
172
+
173
+ const InboxTemplate1 = (props: InboxProps) => {
174
+ const [activeTab, setActiveTab] = React.useState('design');
175
+
176
+ return (
177
+ <TabContext.Provider value={{ activeTab, setActiveTab }}>
178
+ <InboxTemplate1Internal {...props} />
179
+ </TabContext.Provider>
180
+ );
181
+ };
182
+
183
+ const InboxTemplate1Internal = (props: InboxProps) => {
184
+ const { channelFilters: channelFilterProp, channelRole, supportServices, data, orgName, pathPrefix = null } = props;
185
+ const { id: pathChannelId, postId: pathPostId } = useParams();
186
+ const navigate = useNavigate();
187
+ const apolloClient = useApolloClient();
188
+
189
+ // Reduced state - only UI state remains, data comes from Apollo cache
190
+ const [isBottomDrawerOpen, setBottomDrawer] = React.useState(false);
191
+ const [mobilePreviewState, localDispatch] = useReducer(mobilePreviewReducer, {
192
+ mobilePreviewVisibility: false,
193
+ mobilePreviewText: false,
194
+ mobilePreviewCTAText: false,
195
+ });
196
+
197
+ // Hooks - improved responsive breakpoints with better granularity
198
+ const { width: windowWidth, height: windowHeight } = useWindowDimensions();
199
+ const isMobileView = useMediaQuery('(max-width: 640px)');
200
+ const isSmallTabletView = useMediaQuery('(min-width: 641px) and (max-width: 900px)');
201
+ const isTabletView = useMediaQuery('(min-width: 901px) and (max-width: 1024px)');
202
+ const isDesktopView = useMediaQuery('(min-width: 1025px)');
203
+ const isLargeDesktopView = useMediaQuery('(min-width: 1440px)');
204
+ const isSmallScreen = useMediaQuery('(max-width: 900px)');
205
+ // const auth = useSelector(userSelector);
206
+ const auth: any = useSelector<Store.Auth, IUserState>(userSelector, shallowEqual);
207
+ // const user = useSelector((state: any) => state.user, shallowEqual);
208
+
209
+ // Data destructuring from Apollo queries
210
+ const GetChannelsByUserQuery = data?.[0];
211
+ const {
212
+ data: userChannels,
213
+ loading: userChannelsLoading,
214
+ refetch: getChannelsRefetch,
215
+ } = GetChannelsByUserQuery || {};
216
+
217
+ // Get data directly from Apollo cache instead of local state
218
+ const channels = useMemo(() => {
219
+ if (!userChannels?.channelsByUser && !userChannels?.supportServiceChannels) return [];
220
+
221
+ return uniqBy([...(userChannels?.supportServiceChannels ?? []), ...(userChannels?.channelsByUser ?? [])], 'id');
222
+ }, [userChannels]);
223
+
224
+ // Memoize stable channel array to prevent unnecessary re-renders
225
+ const stableChannels = useMemo(() => {
226
+ return channels || [];
227
+ }, [channels]);
228
+
229
+ // Memoized values derived from Apollo cache data
230
+ const channelFilters = useMemo(() => {
231
+ const filters = { ...channelFilterProp };
232
+ const channelType = filters?.type ?? RoomType.Direct;
233
+ filters.type = supportServices ? [channelType, RoomType.Service] : channelType;
234
+ return filters;
235
+ }, [channelFilterProp, supportServices]);
236
+
237
+ const users = useMemo(() => {
238
+ return (
239
+ channels?.reduce((acc, curr) => {
240
+ const newMembers = curr.members?.filter(({ user }) => !acc.find(({ id }) => id === user.id)) || [];
241
+ return [...acc, ...newMembers.map(({ user }) => user)];
242
+ }, []) || []
243
+ );
244
+ }, [channels]);
245
+
246
+ // const currentUser = useMemo(
247
+ // () => users?.find((user) => user && user.alias?.includes(auth?.authUserId)),
248
+ // [users, auth?.authUserId],
249
+ // );
250
+ const currentUser = auth;
251
+
252
+ const channelName = useMemo(() => {
253
+ if (!channels || !pathChannelId) return '';
254
+
255
+ const currChannel = channels?.find((ch) => ch.id === pathChannelId);
256
+ if (!currChannel) return '';
257
+
258
+ const { members, title, type } = currChannel;
259
+
260
+ if (type === RoomType.Direct && members?.length >= 2) {
261
+ const otherUser = members.find((member) => member.user.id !== currentUser?.id);
262
+ if (otherUser?.user) {
263
+ const { givenName, familyName } = otherUser.user;
264
+ if (givenName && familyName) return `${givenName} ${familyName}`;
265
+ return givenName || familyName || title || 'Direct Message';
266
+ }
267
+ return title || 'Direct Message';
268
+ }
269
+
270
+ if (type === RoomType.Direct && members?.length === 1) {
271
+ if (members[0].user?.givenName && members[0]?.user?.familyName) {
272
+ return `${members[0].user?.givenName} ${members[0].user?.familyName}`;
273
+ }
274
+ return members[0].user?.givenName || members[0].user?.familyName || 'Direct Message';
275
+ }
276
+
277
+ return title || 'Channel';
278
+ }, [channels, pathChannelId, currentUser]);
279
+
280
+ // Effects
281
+ useEffect(() => {
282
+ applyFooterStyles();
283
+
284
+ // Optimistic refetch with cache update
285
+ const timeout = setTimeout(() => {
286
+ getChannelsRefetch?.({
287
+ role: channelRole,
288
+ criteria: orgName
289
+ ? { ...channelFilters, orgName: channelFilters?.orgName || orgName || '' }
290
+ : channelFilters,
291
+ supportServices: !!supportServices,
292
+ supportServiceCriteria: { type: RoomType.Service },
293
+ });
294
+ }, 0);
295
+ return () => clearTimeout(timeout);
296
+ }, [channelRole, channelFilters, supportServices, getChannelsRefetch]);
297
+
298
+ // Optimistic navigation with cache updates
299
+ const handleSelectChannel = useCallback(
300
+ async (channelId: string, pId: string | null = null) => {
301
+ // Optimistic UI update
302
+ const mainPath = orgName
303
+ ? pId
304
+ ? `/o/${orgName}/ai-messenger/${channelId}/${pId}`
305
+ : `/o/${orgName}/ai-messenger/${channelId}`
306
+ : pId
307
+ ? `/inbox/${channelId}/${pId}`
308
+ : `/inbox/${channelId}`;
309
+ const basePath = pathPrefix ? `${pathPrefix}${mainPath}` : mainPath;
310
+
311
+ const searchParams = new URLSearchParams();
312
+ // if (channelRole) searchParams.set('channelRole', channelRole);
313
+ // if (orgName) searchParams.set('orgName', orgName);
314
+
315
+ const newPath = searchParams.toString() ? `${basePath}?${searchParams.toString()}` : basePath;
316
+ navigate(newPath, { replace: true });
317
+
318
+ // Optimistically update Apollo cache for immediate UI feedback
319
+ try {
320
+ apolloClient.writeQuery({
321
+ query: MessagesDocument,
322
+ variables: {
323
+ channelId: channelId.toString(),
324
+ parentId: null,
325
+ limit: MESSAGES_PER_PAGE,
326
+ },
327
+ data: {
328
+ messages: {
329
+ __typename: 'Messages',
330
+ data: [],
331
+ totalCount: 0,
332
+ messagesRefId: channelId,
333
+ },
334
+ },
335
+ });
336
+ } catch (error) {
337
+ // Cache write might fail if query hasn't been executed yet, that's OK
338
+ console.debug('Cache write failed (expected on first load):', error);
339
+ }
340
+ },
341
+ [navigate, apolloClient, orgName, channelRole, pathPrefix],
342
+ );
343
+
344
+ const detailSidebarOptions = useMemo(
345
+ () => ({
346
+ isMobileView,
347
+ isSmallTabletView,
348
+ isTabletView,
349
+ isDesktopView,
350
+ isLargeDesktopView,
351
+ isSmallScreen,
352
+ setMobilePreviewCTAText: (v: string | ReactNode) =>
353
+ localDispatch({ payload: { mobilePreviewCTAText: v }, type: 'update' }),
354
+ setMobilePreviewText: (v: string | ReactNode) =>
355
+ localDispatch({ payload: { mobilePreviewText: v }, type: 'update' }),
356
+ setMobilePreviewVisibility: (v: boolean) =>
357
+ localDispatch({ payload: { mobilePreviewVisibility: v }, type: 'update' }),
358
+ }),
359
+ [isMobileView, isSmallTabletView, isTabletView, isDesktopView, isLargeDesktopView, isSmallScreen],
360
+ );
361
+
362
+ return (
363
+ <div
364
+ className="border-t border-gray-300 flex overflow-hidden"
365
+ style={{
366
+ height: `${windowHeight}px`,
367
+ maxHeight: '100vh',
368
+ }}
369
+ >
370
+ {/* Left Sidebar - Responsive Design */}
371
+ <div
372
+ className={`
373
+ flex-shrink-0 bg-gray-50 border-r border-gray-300 overflow-hidden transition-all duration-300 ease-in-out
374
+ ${isMobileView && pathChannelId ? 'hidden' : ''}
375
+ `}
376
+ style={{
377
+ width:
378
+ isMobileView && !pathChannelId
379
+ ? '100%'
380
+ : isMobileView && pathChannelId
381
+ ? '0px'
382
+ : isSmallTabletView
383
+ ? `${Math.min(288, windowWidth * 0.35)}px` // w-72 or 35% of window
384
+ : isTabletView
385
+ ? `${Math.min(320, windowWidth * 0.3)}px` // w-80 or 30% of window
386
+ : isLargeDesktopView
387
+ ? `${Math.min(384, windowWidth * 0.25)}px` // w-96 or 25% of window
388
+ : `${Math.min(320, windowWidth * 0.28)}px`, // w-80 or 28% of window
389
+ height: `${windowHeight}px`,
390
+ maxHeight: '100vh',
391
+ }}
392
+ >
393
+ <LeftSidebar
394
+ currentUser={currentUser}
395
+ userChannels={stableChannels}
396
+ userChannelsLoading={userChannelsLoading}
397
+ users={users}
398
+ handleSelectChannel={handleSelectChannel}
399
+ selectedChannelId={pathChannelId}
400
+ channelToTop={0}
401
+ getChannelsRefetch={getChannelsRefetch}
402
+ role={channelRole}
403
+ messagesQuery={data?.[1]}
404
+ windowHeight={windowHeight}
405
+ windowWidth={windowWidth}
406
+ />
407
+ </div>
408
+
409
+ {/* Main Content Area - Responsive */}
410
+ <div
411
+ className={`
412
+ flex-1 min-w-0 flex flex-col overflow-hidden transition-all duration-300 ease-in-out
413
+ ${isMobileView && !pathChannelId ? 'hidden' : 'flex'}
414
+ `}
415
+ style={{
416
+ minWidth: isSmallScreen ? '300px' : isDesktopView ? '500px' : '400px',
417
+ width: 'auto',
418
+ height: `${windowHeight}px`,
419
+ maxHeight: '100vh',
420
+ }}
421
+ >
422
+ {pathChannelId ? (
423
+ <ContentComponent
424
+ channelId={pathChannelId}
425
+ postId={pathPostId}
426
+ channelRole={channelRole}
427
+ pathPrefix={props.pathPrefix}
428
+ isMobileView={isMobileView}
429
+ isSmallTabletView={isSmallTabletView}
430
+ isTabletView={isTabletView}
431
+ isDesktopView={isDesktopView}
432
+ isLargeDesktopView={isLargeDesktopView}
433
+ isSmallScreen={isSmallScreen}
434
+ windowWidth={windowWidth}
435
+ windowHeight={windowHeight}
436
+ mobilePreviewState={mobilePreviewState}
437
+ detailSidebarOptions={detailSidebarOptions}
438
+ isBottomDrawerOpen={isBottomDrawerOpen}
439
+ setBottomDrawer={setBottomDrawer}
440
+ channelName={channelName}
441
+ loaderdata={data}
442
+ currentUser={currentUser}
443
+ />
444
+ ) : (
445
+ <EmptyState />
446
+ )}
447
+ </div>
448
+
449
+ {/* Right Sidebar - Desktop Only */}
450
+ {pathChannelId && data?.[1] && isDesktopView && (
451
+ <RightSidebarContent
452
+ MessagesLoaderQuery={data?.[1]}
453
+ selectedPost={null}
454
+ detailSidebarOptions={detailSidebarOptions}
455
+ windowWidth={windowWidth}
456
+ windowHeight={windowHeight}
457
+ />
458
+ )}
459
+ </div>
460
+ );
461
+ };
462
+
463
+ const HeaderWithTabs = React.memo((props: any) => {
464
+ const { channelName, isMobileView, isSmallTabletView, isSmallScreen, mobilePreviewState, setBottomDrawer } = props;
465
+
466
+ return (
467
+ <div
468
+ className={`border-b border-gray-200 bg-white flex-shrink-0 z-10 ${
469
+ isSmallScreen ? 'px-3 py-3' : 'px-4 sm:px-6 py-4'
470
+ }`}
471
+ >
472
+ <div className="flex items-center justify-between">
473
+ <div className="flex items-center space-x-2 min-w-0 flex-1">
474
+ {/* Mobile/Small Screen Back Button */}
475
+ {(isMobileView || isSmallTabletView) && (
476
+ <button
477
+ className="p-2 hover:bg-gray-100 rounded-full transition-colors flex-shrink-0"
478
+ onClick={() => window.history.back()}
479
+ aria-label="Go back"
480
+ >
481
+ <svg
482
+ className="w-5 h-5 text-gray-600"
483
+ fill="none"
484
+ stroke="currentColor"
485
+ viewBox="0 0 24 24"
486
+ >
487
+ <path
488
+ strokeLinecap="round"
489
+ strokeLinejoin="round"
490
+ strokeWidth={2}
491
+ d="M15 19l-7-7 7-7"
492
+ />
493
+ </svg>
494
+ </button>
495
+ )}
496
+ <h2 className={`font-semibold text-gray-800 truncate ${isSmallScreen ? 'text-base' : 'text-lg'}`}>
497
+ {channelName}
498
+ </h2>
499
+ </div>
500
+
501
+ {/* Mobile Preview Button */}
502
+ {(isMobileView || isSmallTabletView) && mobilePreviewState?.mobilePreviewVisibility && (
503
+ <button
504
+ className="text-sm px-3 py-1 bg-teal-500 hover:bg-teal-600 text-white rounded-md transition-colors flex-shrink-0"
505
+ onClick={() => setBottomDrawer(true)}
506
+ >
507
+ {mobilePreviewState?.mobilePreviewCTAText}
508
+ </button>
509
+ )}
510
+ </div>
511
+ </div>
512
+ );
513
+ });
514
+
515
+ const ContentComponent = React.memo((props: any) => {
516
+ const {
517
+ channelId,
518
+ channelRole,
519
+ pathPrefix,
520
+ postId,
521
+ isMobileView,
522
+ isSmallTabletView,
523
+ isTabletView,
524
+ isDesktopView,
525
+ isLargeDesktopView,
526
+ isSmallScreen,
527
+ windowWidth,
528
+ windowHeight,
529
+ mobilePreviewState,
530
+ detailSidebarOptions,
531
+ isBottomDrawerOpen,
532
+ setBottomDrawer,
533
+ channelName,
534
+ loaderdata,
535
+ currentUser,
536
+ } = props;
537
+
538
+ // AI Agent mode state
539
+ const [isAIAgentMode, setIsAIAgentMode] = useState(false);
540
+
541
+ const ViewChannelDetailLoaderQuery = loaderdata?.[2];
542
+ const MessagesLoaderQuery = loaderdata?.[1];
543
+
544
+ const [selectedPost, setSelectedPost] = React.useState(null);
545
+ const { data: channelData, loading: channelLoading } = ViewChannelDetailLoaderQuery || {};
546
+
547
+ const onMessageClick = useCallback((msg: any) => {
548
+ setSelectedPost(msg);
549
+ }, []);
550
+
551
+ const channelsDetail = useMemo(() => {
552
+ return channelData?.viewChannelDetail || null;
553
+ }, [channelData]);
554
+
555
+ return (
556
+ <div className="flex overflow-hidden" style={{ height: `${windowHeight}px`, maxHeight: '100vh' }}>
557
+ {/* Main Chat Content */}
558
+ <div className="flex-1 flex flex-col min-w-0 overflow-hidden">
559
+ {/* Channel Header */}
560
+ {channelId && (
561
+ <HeaderWithTabs
562
+ channelName={channelName}
563
+ isMobileView={isMobileView}
564
+ isSmallTabletView={isSmallTabletView}
565
+ isSmallScreen={isSmallScreen}
566
+ mobilePreviewState={mobilePreviewState}
567
+ setBottomDrawer={setBottomDrawer}
568
+ loaderdata={loaderdata}
569
+ />
570
+ )}
571
+
572
+ {/* AI Agent Toggle */}
573
+ <div className={`${isSmallScreen ? 'mx-3' : 'mx-4'} mt-4 mb-2`}>
574
+ <div className="flex items-center justify-between">
575
+ <div className="flex items-center space-x-2">
576
+ <span className="text-sm font-medium text-gray-700">AI Assistant</span>
577
+ <div className="relative inline-block w-10 mr-2 align-middle select-none">
578
+ <input
579
+ type="checkbox"
580
+ name="toggle"
581
+ id="ai-agent-toggle"
582
+ checked={isAIAgentMode}
583
+ onChange={(e) => setIsAIAgentMode(e.target.checked)}
584
+ className="sr-only"
585
+ />
586
+ <label
587
+ htmlFor="ai-agent-toggle"
588
+ className={`block w-10 h-6 rounded-full cursor-pointer transition-all duration-200 ease-in-out shadow-inner ${
589
+ isAIAgentMode ? 'bg-blue-500' : 'bg-gray-300'
590
+ }`}
591
+ >
592
+ <span
593
+ className={`block w-4 h-4 bg-white rounded-full transition-transform duration-200 ease-in-out transform shadow-md ${
594
+ isAIAgentMode ? 'translate-x-5' : 'translate-x-1'
595
+ }`}
596
+ style={{ marginTop: '2px' }}
597
+ ></span>
598
+ </label>
599
+ </div>
600
+ </div>
601
+ {isAIAgentMode && (
602
+ <div className="flex items-center space-x-2 text-xs text-blue-600">
603
+ <span className="w-2 h-2 bg-blue-500 rounded-full animate-pulse"></span>
604
+ <span>AI Mode Active</span>
605
+ </div>
606
+ )}
607
+ </div>
608
+ </div>
609
+
610
+ {/* Mobile Preview */}
611
+ {(isMobileView || isSmallTabletView) && channelId && mobilePreviewState?.mobilePreviewVisibility && (
612
+ <div className={`mt-4 ${isSmallScreen ? 'mx-3' : 'mx-4'}`}>
613
+ <div className="mb-2">
614
+ <div className="w-full flex justify-between items-center gap-2 mb-[5px]">
615
+ <span className="truncate flex-1 text-sm">{mobilePreviewState?.mobilePreviewText}</span>
616
+ <button
617
+ className="text-sm px-3 py-2 bg-teal-500 hover:bg-teal-600 text-white rounded-md transition-colors flex-shrink-0"
618
+ onClick={() => setBottomDrawer(true)}
619
+ >
620
+ {mobilePreviewState?.mobilePreviewCTAText}
621
+ </button>
622
+ </div>
623
+ </div>
624
+ <hr className="border-gray-200" />
625
+ </div>
626
+ )}
627
+
628
+ {/* Content based on postId */}
629
+ <div className="flex-1 flex flex-col min-h-0 overflow-hidden">
630
+ {channelId && (
631
+ <>
632
+ {postId ? (
633
+ postId === '1' ? (
634
+ <ThreadsInbox
635
+ channelId={channelId}
636
+ role={channelRole}
637
+ pathPrefix={pathPrefix}
638
+ setChannelId={() => {}}
639
+ setPostId={() => {}}
640
+ setGoBack={() => {}}
641
+ />
642
+ ) : (
643
+ <ThreadMessagesInbox
644
+ channelId={channelId}
645
+ postId={postId}
646
+ role={channelRole}
647
+ goBack={false}
648
+ pathPrefix={pathPrefix}
649
+ setPostId={() => {}}
650
+ setChannelId={() => {}}
651
+ onMessageClick={onMessageClick}
652
+ />
653
+ )
654
+ ) : (
655
+ <MessagesComponent
656
+ channelId={channelId}
657
+ MessagesLoaderQuery={MessagesLoaderQuery}
658
+ channelsDetail={channelsDetail}
659
+ channelLoading={channelLoading}
660
+ onMessageClick={onMessageClick}
661
+ isSmallScreen={isSmallScreen}
662
+ isDesktopView={isDesktopView}
663
+ windowHeight={windowHeight}
664
+ windowWidth={windowWidth}
665
+ isAIAgentMode={isAIAgentMode}
666
+ channelName={channelName}
667
+ currentUser={currentUser}
668
+ />
669
+ )}
670
+ </>
671
+ )}
672
+ </div>
673
+ </div>
674
+
675
+ {/* Mobile/Small Screen Drawer */}
676
+ {(isMobileView || isSmallTabletView) && (
677
+ <Drawer
678
+ isOpen={isBottomDrawerOpen}
679
+ onClose={() => setBottomDrawer(false)}
680
+ title={mobilePreviewState.mobilePreviewText as string}
681
+ >
682
+ <RightSidebarWrapper
683
+ MessagesLoaderQuery={MessagesLoaderQuery}
684
+ selectedPost={selectedPost}
685
+ detailSidebarOptions={detailSidebarOptions}
686
+ />
687
+ </Drawer>
688
+ )}
689
+ </div>
690
+ );
691
+ });
692
+
693
+ const RightSidebarWrapper = React.memo(({ MessagesLoaderQuery, selectedPost, detailSidebarOptions }: any) => {
694
+ const { data } = MessagesLoaderQuery || {};
695
+ const { activeTab, setActiveTab } = useContext(TabContext);
696
+
697
+ const sortedMessages = useMemo(() => {
698
+ const messages = data?.messages?.data || [];
699
+ return orderBy(uniqBy(messages, 'id'), ['createdAt'], ['asc']);
700
+ }, [data?.messages?.data]);
701
+
702
+ const handleRefreshSandbox = useCallback(() => {
703
+ console.log('Refreshing sandbox...');
704
+ }, []);
705
+
706
+ if (!sortedMessages.length) return null;
707
+
708
+ return (
709
+ <div className="h-full flex flex-col overflow-hidden bg-white">
710
+ {/* Header with tabs and refresh icon */}
711
+ <div className="flex-shrink-0 border-b border-gray-200">
712
+ <div className="flex items-center justify-between px-4 py-3">
713
+ <div className="flex items-center space-x-1">
714
+ <button
715
+ onClick={() => setActiveTab('design')}
716
+ className={`flex items-center space-x-2 px-3 py-2 text-sm font-medium rounded-lg transition-colors ${
717
+ activeTab === 'design'
718
+ ? 'bg-blue-500 text-white'
719
+ : 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'
720
+ }`}
721
+ >
722
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
723
+ <path
724
+ strokeLinecap="round"
725
+ strokeLinejoin="round"
726
+ strokeWidth={2}
727
+ d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"
728
+ />
729
+ </svg>
730
+ <span>Design</span>
731
+ </button>
732
+ <button
733
+ onClick={() => setActiveTab('interact')}
734
+ className={`flex items-center space-x-2 px-3 py-2 text-sm font-medium rounded-lg transition-colors ${
735
+ activeTab === 'interact'
736
+ ? 'bg-blue-500 text-white'
737
+ : 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'
738
+ }`}
739
+ >
740
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
741
+ <path
742
+ strokeLinecap="round"
743
+ strokeLinejoin="round"
744
+ strokeWidth={2}
745
+ d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
746
+ />
747
+ <path
748
+ strokeLinecap="round"
749
+ strokeLinejoin="round"
750
+ strokeWidth={2}
751
+ d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
752
+ />
753
+ </svg>
754
+ <span>Interact</span>
755
+ </button>
756
+ <button
757
+ onClick={() => setActiveTab('code')}
758
+ className={`flex items-center space-x-2 px-3 py-2 text-sm font-medium rounded-lg transition-colors ${
759
+ activeTab === 'code'
760
+ ? 'bg-blue-500 text-white'
761
+ : 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'
762
+ }`}
763
+ >
764
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
765
+ <path
766
+ strokeLinecap="round"
767
+ strokeLinejoin="round"
768
+ strokeWidth={2}
769
+ d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"
770
+ />
771
+ </svg>
772
+ <span>Code</span>
773
+ </button>
774
+ </div>
775
+
776
+ {/* Refresh icon */}
777
+ <button
778
+ onClick={handleRefreshSandbox}
779
+ className="p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-50 rounded-lg transition-colors"
780
+ title="Refresh Sandbox"
781
+ >
782
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
783
+ <path
784
+ strokeLinecap="round"
785
+ strokeLinejoin="round"
786
+ strokeWidth={2}
787
+ d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
788
+ />
789
+ </svg>
790
+ </button>
791
+ </div>
792
+ </div>
793
+
794
+ {/* Content Area */}
795
+ <div className="flex-1 overflow-hidden">
796
+ {activeTab === 'design' ? (
797
+ <div className="h-full flex items-center justify-center bg-gray-50">
798
+ <div className="text-center">
799
+ {/* Next.js Logo */}
800
+ <div className="mb-8">
801
+ <svg className="w-32 h-16 mx-auto text-black" viewBox="0 0 394 80" fill="currentColor">
802
+ <path d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.4zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.7h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z" />
803
+ <path d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.2 3.4 1 1.4 1.5 3 1.5 5h-5.8z" />
804
+ </svg>
805
+ </div>
806
+
807
+ <div className="space-y-4 text-gray-600">
808
+ <h3 className="text-lg font-semibold text-gray-900">Design View</h3>
809
+ <p className="font-mono text-sm bg-gray-100 px-3 py-1 rounded">
810
+ Design components and UI
811
+ </p>
812
+ <p>Customize and preview your design changes.</p>
813
+ </div>
814
+
815
+ <div className="mt-8 space-y-4">
816
+ <button className="bg-black text-white px-6 py-3 rounded-full hover:bg-gray-800 transition-colors flex items-center space-x-2 mx-auto">
817
+ <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
818
+ <path d="M12 2L2 7v10c0 5.55 3.84 9.739 9 11 5.16-1.261 9-5.45 9-11V7l-10-5z" />
819
+ </svg>
820
+ <span>Design now</span>
821
+ </button>
822
+ <p className="text-sm text-gray-500 underline cursor-pointer hover:text-gray-700">
823
+ View design docs
824
+ </p>
825
+ </div>
826
+ </div>
827
+ </div>
828
+ ) : activeTab === 'interact' ? (
829
+ <div className="h-full flex items-center justify-center bg-blue-50">
830
+ <div className="text-center">
831
+ {/* Interact Icon */}
832
+ <div className="mb-8">
833
+ <svg
834
+ className="w-32 h-32 mx-auto text-blue-500"
835
+ fill="none"
836
+ stroke="currentColor"
837
+ viewBox="0 0 24 24"
838
+ >
839
+ <path
840
+ strokeLinecap="round"
841
+ strokeLinejoin="round"
842
+ strokeWidth={1}
843
+ d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
844
+ />
845
+ <path
846
+ strokeLinecap="round"
847
+ strokeLinejoin="round"
848
+ strokeWidth={1}
849
+ d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
850
+ />
851
+ </svg>
852
+ </div>
853
+
854
+ <div className="space-y-4 text-gray-600">
855
+ <h3 className="text-lg font-semibold text-gray-900">Interactive Mode</h3>
856
+ <p className="font-mono text-sm bg-blue-100 px-3 py-1 rounded">
857
+ Live interaction and testing
858
+ </p>
859
+ <p>Test and interact with your components in real-time.</p>
860
+ </div>
861
+
862
+ <div className="mt-8 space-y-4">
863
+ <button className="bg-blue-500 text-white px-6 py-3 rounded-full hover:bg-blue-600 transition-colors flex items-center space-x-2 mx-auto">
864
+ <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
865
+ <path d="M8 5v14l11-7z" />
866
+ </svg>
867
+ <span>Start Interacting</span>
868
+ </button>
869
+ <p className="text-sm text-gray-500 underline cursor-pointer hover:text-gray-700">
870
+ Learn about interactions
871
+ </p>
872
+ </div>
873
+ </div>
874
+ </div>
875
+ ) : (
876
+ <div className="h-full bg-gray-900 text-gray-300 p-4 overflow-auto">
877
+ <div className="font-mono text-sm">
878
+ <div className="mb-4">
879
+ <div className="text-green-400">// app/page.tsx</div>
880
+ </div>
881
+ <div className="space-y-2">
882
+ <div>
883
+ <span className="text-purple-400">export</span>{' '}
884
+ <span className="text-purple-400">default</span>{' '}
885
+ <span className="text-blue-400">function</span>{' '}
886
+ <span className="text-yellow-400">Home</span>() {'{'}
887
+ </div>
888
+ <div className="ml-4">
889
+ <span className="text-purple-400">return</span> (
890
+ </div>
891
+ <div className="ml-8">
892
+ {'<'}
893
+ <span className="text-red-400">main</span>{' '}
894
+ <span className="text-green-400">className</span>=
895
+ <span className="text-yellow-400">
896
+ "flex min-h-screen flex-col items-center justify-between p-24"
897
+ </span>
898
+ {'>'}
899
+ </div>
900
+ <div className="ml-12">
901
+ {'<'}
902
+ <span className="text-red-400">div</span>{' '}
903
+ <span className="text-green-400">className</span>=
904
+ <span className="text-yellow-400">
905
+ "z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex"
906
+ </span>
907
+ {'>'}
908
+ </div>
909
+ <div className="ml-16">
910
+ {'<'}
911
+ <span className="text-red-400">p</span>{' '}
912
+ <span className="text-green-400">className</span>=
913
+ <span className="text-yellow-400">
914
+ "fixed left-0 top-0 flex w-full justify-center border-b border-gray-300
915
+ bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl
916
+ dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static
917
+ lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30"
918
+ </span>
919
+ {'>'}
920
+ </div>
921
+ <div className="ml-20 text-gray-400">Get started by editing&nbsp;</div>
922
+ <div className="ml-20">
923
+ {'<'}
924
+ <span className="text-red-400">code</span>{' '}
925
+ <span className="text-green-400">className</span>=
926
+ <span className="text-yellow-400">"font-mono font-bold"</span>
927
+ {'>'}app/page.tsx{'</'}
928
+ <span className="text-red-400">code</span>
929
+ {'>'}
930
+ </div>
931
+ <div className="ml-16">
932
+ {'</'}
933
+ <span className="text-red-400">p</span>
934
+ {'>'}
935
+ </div>
936
+ <div className="ml-12">
937
+ {'</'}
938
+ <span className="text-red-400">div</span>
939
+ {'>'}
940
+ </div>
941
+ <div className="ml-8">
942
+ {'</'}
943
+ <span className="text-red-400">main</span>
944
+ {'>'}
945
+ </div>
946
+ <div className="ml-4">);</div>
947
+ <div>{'}'}</div>
948
+ </div>
949
+ </div>
950
+ </div>
951
+ )}
952
+ </div>
953
+ </div>
954
+ );
955
+ });
956
+
957
+ const RightSidebarContent = React.memo(
958
+ ({ MessagesLoaderQuery, selectedPost, detailSidebarOptions, windowWidth, windowHeight }: any) => {
959
+ const { data } = MessagesLoaderQuery || {};
960
+
961
+ const sortedMessages = useMemo(() => {
962
+ const messages = data?.messages?.data || [];
963
+ return orderBy(uniqBy(messages, 'id'), ['createdAt'], ['asc']);
964
+ }, [data?.messages?.data]);
965
+
966
+ if (!sortedMessages.length) return null;
967
+
968
+ return (
969
+ <div
970
+ className="border-l border-gray-200 bg-white flex-shrink-0 overflow-hidden"
971
+ style={{
972
+ width: `${windowWidth * 0.35}px`, // 35% of window width
973
+ height: `${windowHeight}px`,
974
+ maxHeight: '100vh',
975
+ }}
976
+ >
977
+ <RightSidebarWrapper
978
+ MessagesLoaderQuery={MessagesLoaderQuery}
979
+ selectedPost={selectedPost}
980
+ detailSidebarOptions={detailSidebarOptions}
981
+ />
982
+ </div>
983
+ );
984
+ },
985
+ );
986
+
987
+ const MessagesComponent = React.memo((props: any) => {
988
+ const {
989
+ channelId,
990
+ MessagesLoaderQuery,
991
+ channelsDetail,
992
+ channelLoading,
993
+ onMessageClick,
994
+ isSmallScreen,
995
+ isDesktopView,
996
+ windowHeight = 768,
997
+ windowWidth = 1024,
998
+ isAIAgentMode,
999
+ channelName,
1000
+ currentUser,
1001
+ } = props;
1002
+
1003
+ const messageRootListRef = useRef(null);
1004
+ const messageListRef = useRef(null);
1005
+ const apolloClient = useApolloClient();
1006
+ const [isLoadingOlder, setIsLoadingOlder] = React.useState(false);
1007
+ const isLoadingOlderRef = useRef(false);
1008
+ const scrollTimeoutRef = useRef(null);
1009
+
1010
+ const auth = useSelector(userSelector);
1011
+ const { startUpload } = useUploadFiles();
1012
+ const [sendMsg] = useSendMessagesMutation();
1013
+
1014
+ const { data, loading: messageLoading, fetchMore: fetchMoreMessages, subscribeToMore } = MessagesLoaderQuery || {};
1015
+ // Add dummy messages with modern formatting to showcase the new UI
1016
+ const dummyMessages = [
1017
+ {
1018
+ id: 'dummy-1',
1019
+ message: `# Modern Next.js-Style Application
1020
+
1021
+ I'll help you build a modern Next.js-style application. Let me first examine the current homepage and then completely rebuild it to create a beautiful, modern "next app" experience.`,
1022
+ createdAt: new Date(Date.now() + 2000).toISOString(),
1023
+ author: {
1024
+ id: 'ai-assistant',
1025
+ username: 'AI Assistant',
1026
+ givenName: 'AI',
1027
+ familyName: 'Assistant',
1028
+ picture: '/default-avatar.svg',
1029
+ },
1030
+ files: { data: [], totalCount: 0 },
1031
+ },
1032
+ {
1033
+ id: 'dummy-2',
1034
+ message: `## Key Features:
1035
+
1036
+ - **Professional Navigation** - Clean header with logo and navigation links
1037
+ - **Hero Section** - Compelling headline with gradient text effects and code preview
1038
+ - **Feature Cards** - Showcase of Next.js capabilities with modern icons
1039
+ - **Call-to-Action** - Clear conversion paths for user engagement
1040
+ - **Footer** - Comprehensive site navigation and branding
1041
+
1042
+ ### Design Highlights:
1043
+
1044
+ - Modern gradient branding (blue to purple to pink)
1045
+ - Clean typography with proper hierarchy
1046
+ - Responsive grid layouts for all screen sizes
1047
+ - Subtle backdrop blur effects and modern cards
1048
+ - Smooth scroll behavior and enhanced visual effects
1049
+ - Production-ready color palette with proper light/dark mode support`,
1050
+ createdAt: new Date(Date.now() + 3000).toISOString(),
1051
+ author: {
1052
+ id: 'ai-assistant',
1053
+ username: 'AI Assistant',
1054
+ givenName: 'AI',
1055
+ familyName: 'Assistant',
1056
+ picture: '/default-avatar.svg',
1057
+ },
1058
+ files: { data: [], totalCount: 0 },
1059
+ },
1060
+ {
1061
+ id: 'dummy-3',
1062
+ message: `### Technical Implementation:
1063
+
1064
+ Uses existing **shadcn/ui components** (Button, Card, Badge)
1065
+ .`,
1066
+ createdAt: new Date(Date.now() + 4000).toISOString(),
1067
+ author: {
1068
+ id: 'ai-assistant',
1069
+ username: 'AI Assistant',
1070
+ givenName: 'AI',
1071
+ familyName: 'Assistant',
1072
+ picture: '/default-avatar.svg',
1073
+ },
1074
+ files: { data: [], totalCount: 0 },
1075
+ },
1076
+ {
1077
+ id: 'dummy-4',
1078
+ message: `\`\`\`typescript
1079
+ export default function Home() {
1080
+ return (
1081
+ <main className="flex min-h-screen flex-col items-center">
1082
+ <div className="z-10 max-w-5xl w-full items-center">
1083
+ <p className="fixed left-0 top-0 flex w-full justify-center">
1084
+ Get started by editing app/page.tsx
1085
+ </p>
1086
+ </div>
1087
+ </main>
1088
+ );
1089
+ }\`\`\`
1090
+ .`,
1091
+ createdAt: new Date(Date.now() + 4000).toISOString(),
1092
+ author: {
1093
+ id: 'ai-assistant',
1094
+ username: 'AI Assistant',
1095
+ givenName: 'AI',
1096
+ familyName: 'Assistant',
1097
+ picture: '/default-avatar.svg',
1098
+ },
1099
+ files: { data: [], totalCount: 0 },
1100
+ },
1101
+ {
1102
+ id: 'tool-status-1',
1103
+ message: `<div class="px-3 py-2 bg-gray-100 border border-gray-300 rounded-lg text-sm text-gray-700" style="border-radius: 5px !important;">🔧 Running tool</div>`,
1104
+ createdAt: new Date(Date.now() + 5000).toISOString(),
1105
+ author: {
1106
+ id: 'system-tool',
1107
+ username: 'System Tool',
1108
+ givenName: 'System',
1109
+ familyName: 'Tool',
1110
+ picture: '/default-avatar.svg',
1111
+ },
1112
+ files: { data: [], totalCount: 0 },
1113
+ },
1114
+ {
1115
+ id: 'tool-status-2',
1116
+ message: `<div class="px-3 py-2 bg-gray-100 border border-gray-300 rounded-lg text-sm text-gray-700" style="border-radius: 5px !important;">🔧 Running tool</div>`,
1117
+ createdAt: new Date(Date.now() + 5500).toISOString(),
1118
+ author: {
1119
+ id: 'system-tool',
1120
+ username: 'System Tool',
1121
+ givenName: 'System',
1122
+ familyName: 'Tool',
1123
+ picture: '/default-avatar.svg',
1124
+ },
1125
+ files: { data: [], totalCount: 0 },
1126
+ },
1127
+ {
1128
+ id: 'bullet-point-1',
1129
+ message: `<div>
1130
+ <h1 class="text-xl font-bold">Installation process</h1>
1131
+ <ul>
1132
+ <li>Install Node.js</li>
1133
+ <li>Install npm</li>
1134
+ <li>Install React</li>
1135
+ <li>Install Tailwind CSS</li>
1136
+ <li>Install shadcn/ui</li>
1137
+ <li>Install Next.js</li>
1138
+ <li>Install TypeScript</li>
1139
+ <li>Install ESLint</li>
1140
+ <li>Install Prettier</li>
1141
+ </ul> </div>`,
1142
+ createdAt: new Date(Date.now() + 5500).toISOString(),
1143
+ author: {
1144
+ id: 'bullet-tool',
1145
+ username: 'System Tool',
1146
+ givenName: 'System',
1147
+ familyName: 'Tool',
1148
+ picture: '/default-avatar.svg',
1149
+ },
1150
+ files: { data: [], totalCount: 0 },
1151
+ },
1152
+ ];
1153
+
1154
+ // Get messages directly from Apollo cache
1155
+ const messages = useMemo(() => {
1156
+ const messagesData = data?.messages?.data || [];
1157
+ dummyMessages.forEach((msg) => messagesData.push(msg));
1158
+ return orderBy(uniqBy(messagesData, 'id'), ['createdAt'], ['asc']);
1159
+ }, [data?.messages?.data]);
1160
+
1161
+ const totalCount = data?.messages?.totalCount || 0;
1162
+
1163
+ const scrollToBottom = useCallback(() => {
1164
+ if (messageRootListRef?.current) {
1165
+ messageRootListRef.current.scrollTop = messageRootListRef.current.scrollHeight;
1166
+ }
1167
+ }, []);
1168
+
1169
+ // Auto-scroll on new messages (but not when loading older messages)
1170
+ useEffect(() => {
1171
+ if (!isLoadingOlderRef.current) {
1172
+ const timer = setTimeout(() => scrollToBottom(), 100);
1173
+ return () => clearTimeout(timer);
1174
+ }
1175
+ }, [messages.length, scrollToBottom]);
1176
+
1177
+ const onFetchOld = useCallback(
1178
+ async (skip: number) => {
1179
+ if (channelId && fetchMoreMessages && !isLoadingOlder) {
1180
+ try {
1181
+ setIsLoadingOlder(true);
1182
+ isLoadingOlderRef.current = true;
1183
+ // Capture current scroll height before fetching
1184
+ const oldScrollHeight = messageRootListRef?.current?.scrollHeight || 0;
1185
+
1186
+ await fetchMoreMessages({
1187
+ variables: {
1188
+ channelId: channelId.toString(),
1189
+ parentId: null,
1190
+ skip,
1191
+ },
1192
+ updateQuery: (prev, { fetchMoreResult }) => {
1193
+ if (!fetchMoreResult) return prev;
1194
+
1195
+ const newMessages = fetchMoreResult.messages.data;
1196
+ const existingMessages = prev.messages?.data || [];
1197
+
1198
+ return {
1199
+ ...prev,
1200
+ messages: {
1201
+ ...fetchMoreResult.messages,
1202
+ data: uniqBy([...newMessages, ...existingMessages], 'id'),
1203
+ },
1204
+ };
1205
+ },
1206
+ });
1207
+
1208
+ // Maintain scroll position after loading older messages
1209
+ setTimeout(() => {
1210
+ if (messageRootListRef?.current) {
1211
+ const newScrollHeight = messageRootListRef.current.scrollHeight;
1212
+ const scrollDiff = newScrollHeight - oldScrollHeight;
1213
+ // For normal flex layout, maintain position by adjusting scroll offset
1214
+ messageRootListRef.current.scrollTop = scrollDiff;
1215
+ }
1216
+ // Reset the loading flag after position is maintained
1217
+ setTimeout(() => {
1218
+ isLoadingOlderRef.current = false;
1219
+ }, 50);
1220
+ }, 100);
1221
+ } catch (error) {
1222
+ console.error('Error fetching older messages:', error);
1223
+ isLoadingOlderRef.current = false;
1224
+ } finally {
1225
+ setIsLoadingOlder(false);
1226
+ }
1227
+ }
1228
+ },
1229
+ [channelId, fetchMoreMessages, isLoadingOlder],
1230
+ );
1231
+
1232
+ // Scroll to bottom when channel changes
1233
+ useEffect(() => {
1234
+ if (channelId && messages.length > 0) {
1235
+ isLoadingOlderRef.current = false; // Reset flag on channel change
1236
+ const timer = setTimeout(() => scrollToBottom(), 200);
1237
+ return () => clearTimeout(timer);
1238
+ }
1239
+ }, [channelId, scrollToBottom]);
1240
+
1241
+ // Alternative scroll detection for Firefox
1242
+ useEffect(() => {
1243
+ const element = messageRootListRef.current;
1244
+ if (!element) return;
1245
+
1246
+ // Firefox-specific scroll detection using passive listeners
1247
+ const handleScrollEnd = () => {
1248
+ if (!isLoadingOlder && element) {
1249
+ const { scrollTop } = element;
1250
+ const isAtTop = Math.round(scrollTop) <= 30;
1251
+ const hasMoreMessages = totalCount > messages.length;
1252
+
1253
+ if (isAtTop && hasMoreMessages) {
1254
+ console.log('ScrollEnd triggered load more (Firefox):', {
1255
+ scrollTop: Math.round(scrollTop),
1256
+ totalCount,
1257
+ messagesLength: messages.length,
1258
+ });
1259
+ onFetchOld(messages.length);
1260
+ }
1261
+ }
1262
+ };
1263
+
1264
+ // Use scrollend event if available (modern Firefox/Chrome)
1265
+ if ('onscrollend' in element) {
1266
+ element.addEventListener('scrollend', handleScrollEnd, { passive: true });
1267
+ return () => {
1268
+ element.removeEventListener('scrollend', handleScrollEnd);
1269
+ };
1270
+ }
1271
+ }, [totalCount, messages.length, onFetchOld, isLoadingOlder]);
1272
+
1273
+ // Cleanup scroll timeout on unmount
1274
+ useEffect(() => {
1275
+ return () => {
1276
+ if (scrollTimeoutRef.current) {
1277
+ clearTimeout(scrollTimeoutRef.current);
1278
+ }
1279
+ };
1280
+ }, []);
1281
+
1282
+ const onMessagesScroll = useCallback(
1283
+ async (e: any) => {
1284
+ // Throttle scroll events for better performance, especially in Firefox
1285
+ if (scrollTimeoutRef.current) {
1286
+ clearTimeout(scrollTimeoutRef.current);
1287
+ }
1288
+
1289
+ scrollTimeoutRef.current = setTimeout(async () => {
1290
+ if (messageRootListRef.current && !isLoadingOlder) {
1291
+ const element = messageRootListRef.current;
1292
+ const { clientHeight, scrollHeight, scrollTop } = element;
1293
+
1294
+ // Firefox-compatible scroll detection
1295
+ // Use Math.ceil to handle Firefox's fractional scrollTop values
1296
+ const isAtTop = Math.ceil(scrollTop) <= 25;
1297
+ const hasMoreMessages = totalCount > messages.length;
1298
+
1299
+ // Additional Firefox-specific check
1300
+ const isFirefox = navigator.userAgent.includes('Firefox');
1301
+ const firefoxAdjustedTop = isFirefox ? Math.round(scrollTop) <= 30 : isAtTop;
1302
+
1303
+ if ((isAtTop || firefoxAdjustedTop) && hasMoreMessages) {
1304
+ console.log('Triggering load more:', {
1305
+ scrollTop: Math.ceil(scrollTop),
1306
+ originalScrollTop: scrollTop,
1307
+ totalCount,
1308
+ messagesLength: messages.length,
1309
+ scrollHeight,
1310
+ clientHeight,
1311
+ browser: isFirefox ? 'Firefox' : 'Other',
1312
+ isAtTop,
1313
+ firefoxAdjustedTop,
1314
+ });
1315
+ await onFetchOld(messages.length);
1316
+ }
1317
+ }
1318
+ }, 100);
1319
+ },
1320
+ [totalCount, messages.length, onFetchOld, isLoadingOlder],
1321
+ );
1322
+
1323
+ // Optimistic message sending with Apollo cache updates
1324
+ const handleSend = useCallback(
1325
+ async (message: string, files: any[] = []) => {
1326
+ // Allow sending if there's either a message or files
1327
+ if ((!message || !message.trim()) && (!files || files.length === 0)) return;
1328
+ if (!channelId) return;
1329
+
1330
+ try {
1331
+ const postId = objectId();
1332
+ const currentDate = new Date();
1333
+
1334
+ const createOptimisticMessage = (files?: any[]) => ({
1335
+ __typename: 'Post' as const,
1336
+ id: postId,
1337
+ message,
1338
+ createdAt: currentDate.toISOString(),
1339
+ updatedAt: currentDate.toISOString(),
1340
+ author: {
1341
+ __typename: 'UserAccount' as const,
1342
+ id: currentUser?.id,
1343
+ givenName: currentUser?.profile?.given_name || '',
1344
+ familyName: currentUser?.profile?.family_name || '',
1345
+ email: currentUser?.profile?.email || '',
1346
+ username: currentUser?.profile?.nickname || '',
1347
+ fullName: currentUser?.profile?.name || '',
1348
+ picture: currentUser?.profile?.picture || '',
1349
+ alias: [currentUser?.authUserId ?? ''],
1350
+ tokens: [],
1351
+ },
1352
+ isDelivered: false, // Will be true once confirmed by server
1353
+ isRead: false,
1354
+ type: 'TEXT' as PostTypeEnum,
1355
+ parentId: null,
1356
+ fromServer: false,
1357
+ channel: {
1358
+ __typename: 'Channel' as const,
1359
+ id: channelId,
1360
+ },
1361
+ propsConfiguration: {
1362
+ __typename: 'MachineConfiguration' as const,
1363
+ id: null,
1364
+ resource: '' as any,
1365
+ contents: null,
1366
+ keys: null,
1367
+ target: null,
1368
+ overrides: null,
1369
+ },
1370
+ props: {},
1371
+ files: {
1372
+ __typename: 'FilesInfo' as const,
1373
+ data: files || [],
1374
+ totalCount: files?.length || 0,
1375
+ },
1376
+ replies: {
1377
+ __typename: 'Messages' as const,
1378
+ data: [],
1379
+ totalCount: 0,
1380
+ },
1381
+ });
1382
+
1383
+ const optimisticMessage = createOptimisticMessage(files);
1384
+
1385
+ if (files?.length > 0) {
1386
+ const uploadResponse = await startUpload({
1387
+ file: files,
1388
+ saveUploadedFile: { variables: { postId } },
1389
+ createUploadLink: { variables: { postId } },
1390
+ });
1391
+
1392
+ const uploadedFiles = uploadResponse.data as unknown as IFileInfo[];
1393
+ if (uploadedFiles) {
1394
+ const fileIds = uploadedFiles.map((f: any) => f.id);
1395
+ await sendMsg({
1396
+ variables: { postId, channelId, content: message, files: fileIds },
1397
+ optimisticResponse: {
1398
+ __typename: 'Mutation',
1399
+ sendMessage: createOptimisticMessage(uploadedFiles),
1400
+ },
1401
+ update: (cache, { data: mutationData }) => {
1402
+ if (!mutationData?.sendMessage) return;
1403
+
1404
+ // Update messages cache optimistically
1405
+ const messagesQuery = {
1406
+ query: MessagesDocument,
1407
+ variables: {
1408
+ channelId: channelId.toString(),
1409
+ parentId: null,
1410
+ limit: MESSAGES_PER_PAGE,
1411
+ },
1412
+ };
1413
+
1414
+ try {
1415
+ const existingData = cache.readQuery(messagesQuery) as any;
1416
+ if (existingData?.messages) {
1417
+ cache.writeQuery({
1418
+ ...messagesQuery,
1419
+ data: {
1420
+ messages: {
1421
+ ...existingData.messages,
1422
+ data: [
1423
+ ...(existingData.messages.data || []),
1424
+ mutationData.sendMessage,
1425
+ ],
1426
+ totalCount: (existingData.messages.totalCount || 0) + 1,
1427
+ },
1428
+ },
1429
+ });
1430
+ }
1431
+ } catch (error) {
1432
+ console.debug('Cache update failed (expected on first message):', error);
1433
+ }
1434
+ },
1435
+ });
1436
+ }
1437
+ } else {
1438
+ await sendMsg({
1439
+ variables: { channelId, content: message },
1440
+ optimisticResponse: {
1441
+ __typename: 'Mutation',
1442
+ sendMessage: optimisticMessage,
1443
+ },
1444
+ update: (cache, { data: mutationData }) => {
1445
+ if (!mutationData?.sendMessage) return;
1446
+
1447
+ // Update messages cache optimistically
1448
+ const messagesQuery = {
1449
+ query: MessagesDocument,
1450
+ variables: {
1451
+ channelId: channelId.toString(),
1452
+ parentId: null,
1453
+ limit: MESSAGES_PER_PAGE,
1454
+ },
1455
+ };
1456
+
1457
+ try {
1458
+ const existingData = cache.readQuery(messagesQuery) as any;
1459
+ if (existingData?.messages) {
1460
+ cache.writeQuery({
1461
+ ...messagesQuery,
1462
+ data: {
1463
+ messages: {
1464
+ ...existingData.messages,
1465
+ data: [...(existingData.messages.data || []), mutationData.sendMessage],
1466
+ totalCount: (existingData.messages.totalCount || 0) + 1,
1467
+ },
1468
+ },
1469
+ });
1470
+ }
1471
+ } catch (error) {
1472
+ console.debug('Cache update failed (expected on first message):', error);
1473
+ }
1474
+ },
1475
+ });
1476
+ }
1477
+ } catch (error) {
1478
+ console.error('Error sending message:', error);
1479
+ }
1480
+ },
1481
+ [channelId, auth, startUpload, sendMsg],
1482
+ );
1483
+
1484
+ // Show loading spinner for initial load
1485
+ if ((messageLoading || channelLoading) && messages.length === 0) {
1486
+ return (
1487
+ <div className="flex-1 flex justify-center items-center">
1488
+ <Spinner className="w-12 h-12" />
1489
+ </div>
1490
+ );
1491
+ }
1492
+
1493
+ // If AI Agent mode is enabled, render the AI Agent component
1494
+ if (isAIAgentMode) {
1495
+ return (
1496
+ <div className="flex-1 flex flex-col min-h-0 overflow-hidden">
1497
+ <AIAgent
1498
+ channelId={channelId}
1499
+ placeholder="Ask me anything..."
1500
+ className="h-full"
1501
+ currentUser={currentUser}
1502
+ channelName={channelName}
1503
+ />
1504
+ </div>
1505
+ );
1506
+ }
1507
+
1508
+ // Regular messages mode
1509
+ return (
1510
+ <>
1511
+ <div
1512
+ ref={messageRootListRef}
1513
+ className={`overflow-y-scroll bg-white ${
1514
+ isSmallScreen ? 'p-2 px-3' : isDesktopView ? 'p-6 px-8' : 'p-4 px-6'
1515
+ }`}
1516
+ onScroll={onMessagesScroll}
1517
+ style={{
1518
+ height: `${windowHeight - 140}px`, // Subtract header + input height
1519
+ maxHeight: '100vh',
1520
+ scrollbarWidth: 'thin',
1521
+ scrollbarColor: '#cbd5e0 #f7fafc',
1522
+ overflowY: 'scroll',
1523
+ WebkitOverflowScrolling: 'touch',
1524
+ backgroundAttachment: 'fixed',
1525
+ }}
1526
+ >
1527
+ <div className="min-h-full">
1528
+ {messages.length > 0 ? (
1529
+ <>
1530
+ {/* Loading indicator for older messages at the top */}
1531
+ {isLoadingOlder && (
1532
+ <div className="flex justify-center py-4">
1533
+ <div className="flex items-center space-x-2 text-gray-500">
1534
+ <Spinner className="w-4 h-4" />
1535
+ <span className="text-sm">Loading older messages...</span>
1536
+ </div>
1537
+ </div>
1538
+ )}
1539
+ <MessagesBuilderUi
1540
+ innerRef={messageListRef}
1541
+ channelId={channelId}
1542
+ currentUser={currentUser}
1543
+ channelMessages={messages}
1544
+ totalCount={totalCount}
1545
+ onMessageClick={onMessageClick}
1546
+ isDesktopView={isDesktopView || false}
1547
+ isSmallScreen={isSmallScreen || false}
1548
+ />
1549
+ <SubscriptionHandler
1550
+ subscribeToMore={subscribeToMore}
1551
+ document={CHAT_MESSAGE_ADDED}
1552
+ variables={{ channelId: channelId.toString() }}
1553
+ enabled={!!channelId && !!subscribeToMore}
1554
+ updateQuery={(prev: any, { subscriptionData }: any) => {
1555
+ console.log('Subscription updateQuery called:', { prev, subscriptionData });
1556
+ if (!subscriptionData.data) {
1557
+ console.log('No subscription data, returning prev');
1558
+ return prev;
1559
+ }
1560
+ const newMessage = subscriptionData.data.chatMessageAdded;
1561
+ console.log('New message received via subscription:', newMessage);
1562
+
1563
+ return {
1564
+ ...prev,
1565
+ messages: {
1566
+ ...prev?.messages,
1567
+ data: uniqBy([...(prev?.messages?.data || []), newMessage], 'id'),
1568
+ totalCount: (prev?.messages?.totalCount || 0) + 1,
1569
+ },
1570
+ };
1571
+ }}
1572
+ onError={(error) => {
1573
+ console.error('Subscription error:', error);
1574
+ }}
1575
+ />
1576
+ </>
1577
+ ) : (
1578
+ <div className="flex items-center justify-center text-gray-500 min-h-96">
1579
+ <div className="text-center max-w-sm mx-auto px-4">
1580
+ <div className="text-6xl mb-4 opacity-50">💬</div>
1581
+ <h3 className="text-lg font-semibold text-gray-600 mb-2">No messages yet</h3>
1582
+ <p className="text-sm text-gray-500">
1583
+ Start the conversation by sending a message below!
1584
+ </p>
1585
+ </div>
1586
+ </div>
1587
+ )}
1588
+ </div>
1589
+ </div>
1590
+ <div className="flex-shrink-0 border-t border-gray-200 bg-white">
1591
+ <InputComponent channelId={channelId} placeholder="Message" />
1592
+ </div>
1593
+ </>
1594
+ );
1595
+ });
1596
+
1597
+ // Display names for debugging
1598
+ InboxTemplate1.displayName = 'Inbox';
1599
+ InboxTemplate1Internal.displayName = 'InboxTemplate1Internal';
1600
+ HeaderWithTabs.displayName = 'HeaderWithTabs';
1601
+ ContentComponent.displayName = 'ContentComponent';
1602
+ MessagesComponent.displayName = 'MessagesComponent';
1603
+ RightSidebarWrapper.displayName = 'RightSidebarWrapper';
1604
+ RightSidebarContent.displayName = 'RightSidebarContent';
1605
+
1606
+ export default React.memo(InboxTemplate1);