@messenger-box/tailwind-ui-inbox 10.0.3-alpha.0

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 (415) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/jest.config.js +9 -0
  4. package/lib/cdm-locales/en/translations.json +31 -0
  5. package/lib/cdm-locales/es/translations.json +31 -0
  6. package/lib/components/AIAgent/AIAgent.d.ts +32 -0
  7. package/lib/components/AIAgent/AIAgent.d.ts.map +1 -0
  8. package/lib/components/AIAgent/AIAgent.js +1135 -0
  9. package/lib/components/AIAgent/AIAgent.js.map +1 -0
  10. package/lib/components/AIAgent/InputComponent.d.ts +84 -0
  11. package/lib/components/AIAgent/InputComponent.d.ts.map +1 -0
  12. package/lib/components/AIAgent/InputComponent.js +417 -0
  13. package/lib/components/AIAgent/InputComponent.js.map +1 -0
  14. package/lib/components/AIAgent/index.d.ts +2 -0
  15. package/lib/components/AIAgent/index.d.ts.map +1 -0
  16. package/lib/components/InboxMessage/CommonMessage.d.ts +8 -0
  17. package/lib/components/InboxMessage/CommonMessage.d.ts.map +1 -0
  18. package/lib/components/InboxMessage/CommonMessage.js +35 -0
  19. package/lib/components/InboxMessage/CommonMessage.js.map +1 -0
  20. package/lib/components/InboxMessage/ConversationItem.d.ts +14 -0
  21. package/lib/components/InboxMessage/ConversationItem.d.ts.map +1 -0
  22. package/lib/components/InboxMessage/ConversationItem.js +200 -0
  23. package/lib/components/InboxMessage/ConversationItem.js.map +1 -0
  24. package/lib/components/InboxMessage/InputComponent.d.ts +20 -0
  25. package/lib/components/InboxMessage/InputComponent.d.ts.map +1 -0
  26. package/lib/components/InboxMessage/InputComponent.js +148 -0
  27. package/lib/components/InboxMessage/InputComponent.js.map +1 -0
  28. package/lib/components/InboxMessage/LeftSidebar.d.ts +20 -0
  29. package/lib/components/InboxMessage/LeftSidebar.d.ts.map +1 -0
  30. package/lib/components/InboxMessage/LeftSidebar.js +102 -0
  31. package/lib/components/InboxMessage/LeftSidebar.js.map +1 -0
  32. package/lib/components/InboxMessage/MessageInput.d.ts +9 -0
  33. package/lib/components/InboxMessage/MessageInput.d.ts.map +1 -0
  34. package/lib/components/InboxMessage/MessageInput.js +154 -0
  35. package/lib/components/InboxMessage/MessageInput.js.map +1 -0
  36. package/lib/components/InboxMessage/MessageInputComponent.d.ts +9 -0
  37. package/lib/components/InboxMessage/MessageInputComponent.d.ts.map +1 -0
  38. package/lib/components/InboxMessage/Messages.d.ts +17 -0
  39. package/lib/components/InboxMessage/Messages.d.ts.map +1 -0
  40. package/lib/components/InboxMessage/Messages.js +99 -0
  41. package/lib/components/InboxMessage/Messages.js.map +1 -0
  42. package/lib/components/InboxMessage/MessagesBuilderUi.d.ts +17 -0
  43. package/lib/components/InboxMessage/MessagesBuilderUi.d.ts.map +1 -0
  44. package/lib/components/InboxMessage/Popover.d.ts +3 -0
  45. package/lib/components/InboxMessage/Popover.d.ts.map +1 -0
  46. package/lib/components/InboxMessage/Popover.js +31 -0
  47. package/lib/components/InboxMessage/Popover.js.map +1 -0
  48. package/lib/components/InboxMessage/RightSidebar.d.ts +9 -0
  49. package/lib/components/InboxMessage/RightSidebar.d.ts.map +1 -0
  50. package/lib/components/InboxMessage/RightSidebar.js +9 -0
  51. package/lib/components/InboxMessage/RightSidebar.js.map +1 -0
  52. package/lib/components/InboxMessage/RightSidebarAi.d.ts +37 -0
  53. package/lib/components/InboxMessage/RightSidebarAi.d.ts.map +1 -0
  54. package/lib/components/InboxMessage/RightSidebarAi.js +9 -0
  55. package/lib/components/InboxMessage/RightSidebarAi.js.map +1 -0
  56. package/lib/components/InboxMessage/ServiceConversationItem.d.ts +12 -0
  57. package/lib/components/InboxMessage/ServiceConversationItem.d.ts.map +1 -0
  58. package/lib/components/InboxMessage/ServiceConversationItem.js +185 -0
  59. package/lib/components/InboxMessage/ServiceConversationItem.js.map +1 -0
  60. package/lib/components/InboxMessage/ServiceInboxItem.d.ts +12 -0
  61. package/lib/components/InboxMessage/ServiceInboxItem.d.ts.map +1 -0
  62. package/lib/components/InboxMessage/ServiceInboxItem.js +182 -0
  63. package/lib/components/InboxMessage/ServiceInboxItem.js.map +1 -0
  64. package/lib/components/InboxMessage/StreamingMessageBubble.d.ts +18 -0
  65. package/lib/components/InboxMessage/StreamingMessageBubble.d.ts.map +1 -0
  66. package/lib/components/InboxMessage/SubscriptionHandler.d.ts +19 -0
  67. package/lib/components/InboxMessage/SubscriptionHandler.d.ts.map +1 -0
  68. package/lib/components/InboxMessage/SubscriptionHandler.js +41 -0
  69. package/lib/components/InboxMessage/SubscriptionHandler.js.map +1 -0
  70. package/lib/components/InboxMessage/TypingIndicator.d.ts +11 -0
  71. package/lib/components/InboxMessage/TypingIndicator.d.ts.map +1 -0
  72. package/lib/components/InboxMessage/UploadImageButton.d.ts +7 -0
  73. package/lib/components/InboxMessage/UploadImageButton.d.ts.map +1 -0
  74. package/lib/components/InboxMessage/UploadImageButton.js +30 -0
  75. package/lib/components/InboxMessage/UploadImageButton.js.map +1 -0
  76. package/lib/components/InboxMessage/UserModalContent.d.ts +3 -0
  77. package/lib/components/InboxMessage/UserModalContent.d.ts.map +1 -0
  78. package/lib/components/InboxMessage/UserModalContent.js +60 -0
  79. package/lib/components/InboxMessage/UserModalContent.js.map +1 -0
  80. package/lib/components/InboxMessage/index.d.ts +19 -0
  81. package/lib/components/InboxMessage/index.d.ts.map +1 -0
  82. package/lib/components/InboxMessage/message-widgets/CommonMessage.d.ts +11 -0
  83. package/lib/components/InboxMessage/message-widgets/CommonMessage.d.ts.map +1 -0
  84. package/lib/components/InboxMessage/message-widgets/CommonMessage.js +44 -0
  85. package/lib/components/InboxMessage/message-widgets/CommonMessage.js.map +1 -0
  86. package/lib/components/InboxMessage/message-widgets/ErrorFixCard.d.ts +10 -0
  87. package/lib/components/InboxMessage/message-widgets/ErrorFixCard.d.ts.map +1 -0
  88. package/lib/components/InboxMessage/message-widgets/ErrorFixCard.js +194 -0
  89. package/lib/components/InboxMessage/message-widgets/ErrorFixCard.js.map +1 -0
  90. package/lib/components/InboxMessage/message-widgets/MessageCard.d.ts +8 -0
  91. package/lib/components/InboxMessage/message-widgets/MessageCard.d.ts.map +1 -0
  92. package/lib/components/InboxMessage/message-widgets/MessageSliceRenderer.d.ts +12 -0
  93. package/lib/components/InboxMessage/message-widgets/MessageSliceRenderer.d.ts.map +1 -0
  94. package/lib/components/InboxMessage/message-widgets/MessageSliceRenderer.js +37 -0
  95. package/lib/components/InboxMessage/message-widgets/MessageSliceRenderer.js.map +1 -0
  96. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts +42 -0
  97. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts.map +1 -0
  98. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js +1339 -0
  99. package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js.map +1 -0
  100. package/lib/components/InboxMessage/message-widgets/PlainMessage.d.ts +8 -0
  101. package/lib/components/InboxMessage/message-widgets/PlainMessage.d.ts.map +1 -0
  102. package/lib/components/InboxMessage/message-widgets/PlainMessage.js +14 -0
  103. package/lib/components/InboxMessage/message-widgets/PlainMessage.js.map +1 -0
  104. package/lib/components/InboxMessage/message-widgets/PropertyMessageWidget.d.ts +9 -0
  105. package/lib/components/InboxMessage/message-widgets/PropertyMessageWidget.d.ts.map +1 -0
  106. package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.d.ts +14 -0
  107. package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.d.ts.map +1 -0
  108. package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.js +333 -0
  109. package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.js.map +1 -0
  110. package/lib/components/InboxMessage/message-widgets/index.d.ts +4 -0
  111. package/lib/components/InboxMessage/message-widgets/index.d.ts.map +1 -0
  112. package/lib/components/ModelConfigPanel.d.ts +74 -0
  113. package/lib/components/ModelConfigPanel.d.ts.map +1 -0
  114. package/lib/components/ModelConfigPanel.js +1152 -0
  115. package/lib/components/ModelConfigPanel.js.map +1 -0
  116. package/lib/components/filler-components/RightSiderBar.d.ts +3 -0
  117. package/lib/components/filler-components/RightSiderBar.d.ts.map +1 -0
  118. package/lib/components/filler-components/RightSiderBar.js +532 -0
  119. package/lib/components/filler-components/RightSiderBar.js.map +1 -0
  120. package/lib/components/inbox/FilesList.d.ts +20 -0
  121. package/lib/components/inbox/FilesList.d.ts.map +1 -0
  122. package/lib/components/inbox/FilesList.js +68 -0
  123. package/lib/components/inbox/FilesList.js.map +1 -0
  124. package/lib/components/inbox/MessageItem.d.ts +17 -0
  125. package/lib/components/inbox/MessageItem.d.ts.map +1 -0
  126. package/lib/components/inbox/MessageItem.js +50 -0
  127. package/lib/components/inbox/MessageItem.js.map +1 -0
  128. package/lib/components/inbox/ThreadItem.d.ts +11 -0
  129. package/lib/components/inbox/ThreadItem.d.ts.map +1 -0
  130. package/lib/components/inbox/ThreadItem.js +147 -0
  131. package/lib/components/inbox/ThreadItem.js.map +1 -0
  132. package/lib/components/inbox/index.d.ts +4 -0
  133. package/lib/components/inbox/index.d.ts.map +1 -0
  134. package/lib/components/index.d.ts +10 -0
  135. package/lib/components/index.d.ts.map +1 -0
  136. package/lib/components/live-code-editor/hybrid-live-editor.d.ts +20 -0
  137. package/lib/components/live-code-editor/hybrid-live-editor.d.ts.map +1 -0
  138. package/lib/components/live-code-editor/index.d.ts +4 -0
  139. package/lib/components/live-code-editor/index.d.ts.map +1 -0
  140. package/lib/components/live-code-editor/live-code-editor.d.ts +14 -0
  141. package/lib/components/live-code-editor/live-code-editor.d.ts.map +1 -0
  142. package/lib/components/messages-container-ui/MessagesContainerUI.d.ts +81 -0
  143. package/lib/components/messages-container-ui/MessagesContainerUI.d.ts.map +1 -0
  144. package/lib/components/messages-container-ui/MessagesContainerUI.js +77 -0
  145. package/lib/components/messages-container-ui/MessagesContainerUI.js.map +1 -0
  146. package/lib/components/messages-container-ui/PlanModeView.d.ts +82 -0
  147. package/lib/components/messages-container-ui/PlanModeView.d.ts.map +1 -0
  148. package/lib/components/messages-container-ui/PlanModeView.js +267 -0
  149. package/lib/components/messages-container-ui/PlanModeView.js.map +1 -0
  150. package/lib/components/messages-container-ui/index.d.ts +6 -0
  151. package/lib/components/messages-container-ui/index.d.ts.map +1 -0
  152. package/lib/components/messages-container-ui/types.d.ts +38 -0
  153. package/lib/components/messages-container-ui/types.d.ts.map +1 -0
  154. package/lib/components/slot-fill/chat-message-filler.d.ts +4 -0
  155. package/lib/components/slot-fill/chat-message-filler.d.ts.map +1 -0
  156. package/lib/components/slot-fill/chat-message-filler.js +5 -0
  157. package/lib/components/slot-fill/chat-message-filler.js.map +1 -0
  158. package/lib/components/slot-fill/chat-message-slot.d.ts +11 -0
  159. package/lib/components/slot-fill/chat-message-slot.d.ts.map +1 -0
  160. package/lib/components/slot-fill/chat-message-slot.js +6 -0
  161. package/lib/components/slot-fill/chat-message-slot.js.map +1 -0
  162. package/lib/components/slot-fill/index.d.ts +4 -0
  163. package/lib/components/slot-fill/index.d.ts.map +1 -0
  164. package/lib/components/slot-fill/right-sidebar-filler.d.ts +4 -0
  165. package/lib/components/slot-fill/right-sidebar-filler.d.ts.map +1 -0
  166. package/lib/components/slot-fill/right-sidebar-filler.js +13 -0
  167. package/lib/components/slot-fill/right-sidebar-filler.js.map +1 -0
  168. package/lib/components/ui/button.d.ts +9 -0
  169. package/lib/components/ui/button.d.ts.map +1 -0
  170. package/lib/compute.d.ts +8 -0
  171. package/lib/compute.d.ts.map +1 -0
  172. package/lib/compute.js +264 -0
  173. package/lib/compute.js.map +1 -0
  174. package/lib/config/env-config.d.ts +20 -0
  175. package/lib/config/env-config.d.ts.map +1 -0
  176. package/lib/config/env-config.js +55 -0
  177. package/lib/config/env-config.js.map +1 -0
  178. package/lib/config/index.d.ts +2 -0
  179. package/lib/config/index.d.ts.map +1 -0
  180. package/lib/constants/breakpoints.d.ts +8 -0
  181. package/lib/constants/breakpoints.d.ts.map +1 -0
  182. package/lib/constants/index.d.ts +3 -0
  183. package/lib/constants/index.d.ts.map +1 -0
  184. package/lib/container/AiInbox.d.ts +15 -0
  185. package/lib/container/AiInbox.d.ts.map +1 -0
  186. package/lib/container/AiInboxWithLoader.d.ts +36 -0
  187. package/lib/container/AiInboxWithLoader.d.ts.map +1 -0
  188. package/lib/container/AiLandingInput.d.ts +27 -0
  189. package/lib/container/AiLandingInput.d.ts.map +1 -0
  190. package/lib/container/AiLandingInput.js +149 -0
  191. package/lib/container/AiLandingInput.js.map +1 -0
  192. package/lib/container/Inbox.d.ts +15 -0
  193. package/lib/container/Inbox.d.ts.map +1 -0
  194. package/lib/container/Inbox.js +964 -0
  195. package/lib/container/Inbox.js.map +1 -0
  196. package/lib/container/InboxAiMessagesLoader.d.ts +45 -0
  197. package/lib/container/InboxAiMessagesLoader.d.ts.map +1 -0
  198. package/lib/container/InboxAiMessagesLoader.js +80 -0
  199. package/lib/container/InboxAiMessagesLoader.js.map +1 -0
  200. package/lib/container/InboxContainer.d.ts +41 -0
  201. package/lib/container/InboxContainer.d.ts.map +1 -0
  202. package/lib/container/InboxContainer.js +27 -0
  203. package/lib/container/InboxContainer.js.map +1 -0
  204. package/lib/container/InboxTemplate1.d.ts +15 -0
  205. package/lib/container/InboxTemplate1.d.ts.map +1 -0
  206. package/lib/container/InboxTemplate1WithLoader.d.ts +36 -0
  207. package/lib/container/InboxTemplate1WithLoader.d.ts.map +1 -0
  208. package/lib/container/InboxTemplate2.d.ts +15 -0
  209. package/lib/container/InboxTemplate2.d.ts.map +1 -0
  210. package/lib/container/InboxWithAiLoader.d.ts +47 -0
  211. package/lib/container/InboxWithAiLoader.d.ts.map +1 -0
  212. package/lib/container/InboxWithAiLoader.js +118 -0
  213. package/lib/container/InboxWithAiLoader.js.map +1 -0
  214. package/lib/container/InboxWithLoader.d.ts +36 -0
  215. package/lib/container/InboxWithLoader.d.ts.map +1 -0
  216. package/lib/container/InboxWithLoader.js +277 -0
  217. package/lib/container/InboxWithLoader.js.map +1 -0
  218. package/lib/container/ServiceInbox.d.ts +9 -0
  219. package/lib/container/ServiceInbox.d.ts.map +1 -0
  220. package/lib/container/ServiceInbox.js +141 -0
  221. package/lib/container/ServiceInbox.js.map +1 -0
  222. package/lib/container/TestInboxWithAiLoader.d.ts +7 -0
  223. package/lib/container/TestInboxWithAiLoader.d.ts.map +1 -0
  224. package/lib/container/TestInboxWithAiLoader.js +135 -0
  225. package/lib/container/TestInboxWithAiLoader.js.map +1 -0
  226. package/lib/container/ThreadMessages.d.ts +13 -0
  227. package/lib/container/ThreadMessages.d.ts.map +1 -0
  228. package/lib/container/ThreadMessages.js +320 -0
  229. package/lib/container/ThreadMessages.js.map +1 -0
  230. package/lib/container/ThreadMessagesInbox.d.ts +14 -0
  231. package/lib/container/ThreadMessagesInbox.d.ts.map +1 -0
  232. package/lib/container/ThreadMessagesInbox.js +347 -0
  233. package/lib/container/ThreadMessagesInbox.js.map +1 -0
  234. package/lib/container/Threads.d.ts +8 -0
  235. package/lib/container/Threads.d.ts.map +1 -0
  236. package/lib/container/Threads.js +231 -0
  237. package/lib/container/Threads.js.map +1 -0
  238. package/lib/container/ThreadsInbox.d.ts +21 -0
  239. package/lib/container/ThreadsInbox.d.ts.map +1 -0
  240. package/lib/container/ThreadsInbox.js +243 -0
  241. package/lib/container/ThreadsInbox.js.map +1 -0
  242. package/lib/container/apply-footer-styles.d.ts +2 -0
  243. package/lib/container/apply-footer-styles.d.ts.map +1 -0
  244. package/lib/container/apply-footer-styles.js +16 -0
  245. package/lib/container/apply-footer-styles.js.map +1 -0
  246. package/lib/container/index.d.ts +13 -0
  247. package/lib/container/index.d.ts.map +1 -0
  248. package/lib/enums/index.d.ts +2 -0
  249. package/lib/enums/index.d.ts.map +1 -0
  250. package/lib/enums/messenger-slot-fill-name-enum.d.ts +11 -0
  251. package/lib/enums/messenger-slot-fill-name-enum.d.ts.map +1 -0
  252. package/lib/enums/messenger-slot-fill-name-enum.js +11 -0
  253. package/lib/enums/messenger-slot-fill-name-enum.js.map +1 -0
  254. package/lib/hooks/index.d.ts +4 -0
  255. package/lib/hooks/index.d.ts.map +1 -0
  256. package/lib/hooks/usePersistentModelConfig.d.ts +33 -0
  257. package/lib/hooks/usePersistentModelConfig.d.ts.map +1 -0
  258. package/lib/hooks/usePersistentModelConfig.js +123 -0
  259. package/lib/hooks/usePersistentModelConfig.js.map +1 -0
  260. package/lib/hooks/useStreamAssembler.d.ts +8 -0
  261. package/lib/hooks/useStreamAssembler.d.ts.map +1 -0
  262. package/lib/hooks/useTemplates.d.ts +14 -0
  263. package/lib/hooks/useTemplates.d.ts.map +1 -0
  264. package/lib/hooks/useTemplates.js +59 -0
  265. package/lib/hooks/useTemplates.js.map +1 -0
  266. package/lib/index.d.ts +14 -0
  267. package/lib/index.d.ts.map +1 -0
  268. package/lib/index.js +1 -0
  269. package/lib/index.js.map +1 -0
  270. package/lib/interfaces/index.d.ts +2 -0
  271. package/lib/interfaces/index.d.ts.map +1 -0
  272. package/lib/interfaces/message-widgets.interface.d.ts +21 -0
  273. package/lib/interfaces/message-widgets.interface.d.ts.map +1 -0
  274. package/lib/machines/aiAgentMachine.d.ts +3 -0
  275. package/lib/machines/aiAgentMachine.d.ts.map +1 -0
  276. package/lib/machines/aiAgentMachine.js +1083 -0
  277. package/lib/machines/aiAgentMachine.js.map +1 -0
  278. package/lib/machines/aiAgentMachine.simple.d.ts +3 -0
  279. package/lib/machines/aiAgentMachine.simple.d.ts.map +1 -0
  280. package/lib/machines/aiAgentMachine.simple.js +108 -0
  281. package/lib/machines/aiAgentMachine.simple.js.map +1 -0
  282. package/lib/machines/index.d.ts +3 -0
  283. package/lib/machines/index.d.ts.map +1 -0
  284. package/lib/machines/types.d.ts +77 -0
  285. package/lib/machines/types.d.ts.map +1 -0
  286. package/lib/module.d.ts +7 -0
  287. package/lib/module.d.ts.map +1 -0
  288. package/lib/module.js +26 -0
  289. package/lib/module.js.map +1 -0
  290. package/lib/routes.json +251 -0
  291. package/lib/styles/responsive.css +76 -0
  292. package/lib/templates/InboxWithAi.d.ts +44 -0
  293. package/lib/templates/InboxWithAi.d.ts.map +1 -0
  294. package/lib/templates/InboxWithAi.js +651 -0
  295. package/lib/templates/InboxWithAi.js.map +1 -0
  296. package/lib/templates/InboxWithAi.tsx +844 -0
  297. package/lib/templates/index.d.ts +2 -0
  298. package/lib/templates/index.d.ts.map +1 -0
  299. package/lib/templates/index.ts +1 -0
  300. package/lib/types/templates.d.ts +35 -0
  301. package/lib/types/templates.d.ts.map +1 -0
  302. package/lib/utils/utils.d.ts +2 -0
  303. package/lib/utils/utils.d.ts.map +1 -0
  304. package/lib/xstate/index.d.ts +3 -0
  305. package/lib/xstate/index.d.ts.map +1 -0
  306. package/lib/xstate/rightSidebar.machine.d.ts +4 -0
  307. package/lib/xstate/rightSidebar.machine.d.ts.map +1 -0
  308. package/lib/xstate/rightSidebar.types.d.ts +57 -0
  309. package/lib/xstate/rightSidebar.types.d.ts.map +1 -0
  310. package/package.json +69 -0
  311. package/rollup.config.mjs +47 -0
  312. package/src/cdm-locales/en/translations.json +31 -0
  313. package/src/cdm-locales/es/translations.json +31 -0
  314. package/src/components/AIAgent/AIAgent.tsx +1468 -0
  315. package/src/components/AIAgent/AIAgent.tsx.bk +1365 -0
  316. package/src/components/AIAgent/InputComponent.tsx +608 -0
  317. package/src/components/AIAgent/README.md +174 -0
  318. package/src/components/AIAgent/index.ts +1 -0
  319. package/src/components/InboxMessage/CommonMessage.tsx +40 -0
  320. package/src/components/InboxMessage/ConversationItem.tsx +255 -0
  321. package/src/components/InboxMessage/InputComponent.tsx +198 -0
  322. package/src/components/InboxMessage/LeftSidebar.tsx +140 -0
  323. package/src/components/InboxMessage/MessageInput.tsx +209 -0
  324. package/src/components/InboxMessage/MessageInputComponent.tsx +245 -0
  325. package/src/components/InboxMessage/Messages.tsx +137 -0
  326. package/src/components/InboxMessage/MessagesBuilderUi.tsx +205 -0
  327. package/src/components/InboxMessage/Popover.tsx +42 -0
  328. package/src/components/InboxMessage/RightSidebar.tsx +22 -0
  329. package/src/components/InboxMessage/RightSidebarAi.tsx +47 -0
  330. package/src/components/InboxMessage/ServiceConversationItem.tsx +234 -0
  331. package/src/components/InboxMessage/ServiceInboxItem.tsx +223 -0
  332. package/src/components/InboxMessage/StreamingMessageBubble.tsx +270 -0
  333. package/src/components/InboxMessage/SubscriptionHandler.tsx +55 -0
  334. package/src/components/InboxMessage/TypingIndicator.tsx +38 -0
  335. package/src/components/InboxMessage/UploadImageButton.tsx +46 -0
  336. package/src/components/InboxMessage/UserModalContent.tsx +60 -0
  337. package/src/components/InboxMessage/index.ts +18 -0
  338. package/src/components/InboxMessage/message-widgets/CommonMessage.tsx +69 -0
  339. package/src/components/InboxMessage/message-widgets/ErrorFixCard.tsx +239 -0
  340. package/src/components/InboxMessage/message-widgets/MessageCard.tsx +127 -0
  341. package/src/components/InboxMessage/message-widgets/MessageSliceRenderer.tsx +40 -0
  342. package/src/components/InboxMessage/message-widgets/ModernMessageGroup.tsx +1733 -0
  343. package/src/components/InboxMessage/message-widgets/PlainMessage.tsx +18 -0
  344. package/src/components/InboxMessage/message-widgets/PropertyMessageWidget.tsx +29 -0
  345. package/src/components/InboxMessage/message-widgets/SlackLikeMessageGroup.tsx +492 -0
  346. package/src/components/InboxMessage/message-widgets/index.ts +8 -0
  347. package/src/components/ModelConfigPanel.tsx +1357 -0
  348. package/src/components/filler-components/RightSiderBar.tsx +572 -0
  349. package/src/components/inbox/FilesList.tsx +89 -0
  350. package/src/components/inbox/MessageItem.tsx +50 -0
  351. package/src/components/inbox/ThreadItem.tsx +295 -0
  352. package/src/components/inbox/index.ts +3 -0
  353. package/src/components/index.ts +29 -0
  354. package/src/components/live-code-editor/hybrid-live-editor.tsx +105 -0
  355. package/src/components/live-code-editor/index.ts +3 -0
  356. package/src/components/live-code-editor/live-code-editor.tsx +257 -0
  357. package/src/components/messages-container-ui/MessagesContainerUI.tsx +151 -0
  358. package/src/components/messages-container-ui/PlanModeView.tsx +426 -0
  359. package/src/components/messages-container-ui/README.md +91 -0
  360. package/src/components/messages-container-ui/index.ts +5 -0
  361. package/src/components/messages-container-ui/types.ts +40 -0
  362. package/src/components/slot-fill/chat-message-filler.tsx +18 -0
  363. package/src/components/slot-fill/chat-message-slot.tsx +18 -0
  364. package/src/components/slot-fill/index.ts +3 -0
  365. package/src/components/slot-fill/right-sidebar-filler.tsx +48 -0
  366. package/src/components/ui/button.tsx +32 -0
  367. package/src/compute.ts +271 -0
  368. package/src/config/env-config.ts +24 -0
  369. package/src/config/index.ts +1 -0
  370. package/src/constants/breakpoints.ts +7 -0
  371. package/src/constants/index.ts +5 -0
  372. package/src/container/AiInbox.tsx +1879 -0
  373. package/src/container/AiInboxWithLoader.tsx +356 -0
  374. package/src/container/AiLandingInput.tsx +200 -0
  375. package/src/container/Inbox.tsx +1095 -0
  376. package/src/container/InboxAiMessagesLoader.tsx +129 -0
  377. package/src/container/InboxContainer.tsx +61 -0
  378. package/src/container/InboxTemplate1.tsx +1553 -0
  379. package/src/container/InboxTemplate1WithLoader.tsx +338 -0
  380. package/src/container/InboxTemplate2.tsx +1617 -0
  381. package/src/container/InboxWithAiLoader.tsx +177 -0
  382. package/src/container/InboxWithLoader.tsx +341 -0
  383. package/src/container/ServiceInbox.tsx +188 -0
  384. package/src/container/TestInboxWithAiLoader.tsx +147 -0
  385. package/src/container/ThreadMessages.tsx +378 -0
  386. package/src/container/ThreadMessagesInbox.tsx +457 -0
  387. package/src/container/Threads.tsx +270 -0
  388. package/src/container/ThreadsInbox.tsx +351 -0
  389. package/src/container/apply-footer-styles.ts +17 -0
  390. package/src/container/index.ts +31 -0
  391. package/src/enums/index.ts +1 -0
  392. package/src/enums/messenger-slot-fill-name-enum.ts +10 -0
  393. package/src/hooks/index.ts +3 -0
  394. package/src/hooks/usePersistentModelConfig.ts +166 -0
  395. package/src/hooks/useStreamAssembler.ts +7 -0
  396. package/src/hooks/useTemplates.ts +75 -0
  397. package/src/index.ts +49 -0
  398. package/src/interfaces/index.ts +1 -0
  399. package/src/interfaces/message-widgets.interface.ts +21 -0
  400. package/src/machines/aiAgentMachine.simple.ts +89 -0
  401. package/src/machines/aiAgentMachine.ts +1296 -0
  402. package/src/machines/aiAgentMachine.ts.bk +1296 -0
  403. package/src/machines/index.ts +2 -0
  404. package/src/machines/types.ts +59 -0
  405. package/src/module.tsx +32 -0
  406. package/src/styles/responsive.css +76 -0
  407. package/src/templates/InboxWithAi.tsx +844 -0
  408. package/src/templates/index.ts +1 -0
  409. package/src/types/templates.ts +35 -0
  410. package/src/utils/utils.ts +3 -0
  411. package/src/xstate/index.ts +2 -0
  412. package/src/xstate/rightSidebar.machine.ts +304 -0
  413. package/src/xstate/rightSidebar.types.ts +58 -0
  414. package/tsconfig.json +14 -0
  415. package/webpack.config.js +92 -0
@@ -0,0 +1,1879 @@
1
+ import React, { ReactNode, useCallback, useEffect, useMemo, useReducer, useRef } from 'react';
2
+ import { orderBy, uniqBy } from 'lodash-es';
3
+ import {
4
+ OnChatMessageAddedDocument as CHAT_MESSAGE_ADDED,
5
+ useSendMessagesMutation,
6
+ GetChannelsByUserDocument,
7
+ MessagesDocument,
8
+ } from 'common/graphql';
9
+ import { useUploadFiles } from '@messenger-box/platform-client';
10
+ import { IFileInfo, RoomType, PostTypeEnum } from 'common';
11
+ import { useSelector, shallowEqual } from 'react-redux';
12
+ import { useNavigate, useParams } from '@remix-run/react';
13
+ import { LeftSidebar, Messages, RightSidebar, MessageInputComponent } from '../components';
14
+ import { StreamingMessageBubble } from '../components/InboxMessage/StreamingMessageBubble';
15
+ import { Store, userSelector } from '@adminide-stack/user-auth0-client';
16
+ import { IUserState } from '@adminide-stack/core';
17
+ import { config } from '../config';
18
+ import { applyFooterStyles } from './apply-footer-styles';
19
+ import { objectId } from '@messenger-box/core';
20
+ import { ThreadsInbox } from './ThreadsInbox';
21
+ import { ThreadMessagesInbox } from './ThreadMessagesInbox';
22
+ import { useApolloClient } from '@apollo/client';
23
+ import { SubscriptionHandler } from '../components/InboxMessage/SubscriptionHandler';
24
+ import { useStreamAssembler, parseStreamMessage } from '../hooks/useStreamAssembler';
25
+
26
+ const { MESSAGES_PER_PAGE } = config;
27
+
28
+ // Types
29
+ interface DrawerProps {
30
+ isOpen: boolean;
31
+ onClose: () => void;
32
+ children: ReactNode;
33
+ title?: string;
34
+ }
35
+
36
+ export interface InboxProps {
37
+ channelFilters?: Record<string, unknown>;
38
+ channelRole?: string;
39
+ supportServices?: boolean;
40
+ pathPrefix?: string;
41
+ data?: any;
42
+ orgName?: string;
43
+ }
44
+
45
+ interface MobilePreviewState {
46
+ mobilePreviewVisibility: boolean;
47
+ mobilePreviewText: string | ReactNode;
48
+ mobilePreviewCTAText: string | ReactNode;
49
+ }
50
+
51
+ // Static utility hooks and components
52
+ const useMediaQuery = (query: string) => {
53
+ const [matches, setMatches] = React.useState(false);
54
+
55
+ useEffect(() => {
56
+ if (typeof window === 'undefined') return;
57
+
58
+ const mediaQuery = window.matchMedia(query);
59
+ const updateMatches = () => setMatches(mediaQuery.matches);
60
+
61
+ updateMatches();
62
+ mediaQuery.addEventListener('change', updateMatches);
63
+ return () => mediaQuery.removeEventListener('change', updateMatches);
64
+ }, [query]);
65
+
66
+ return matches;
67
+ };
68
+
69
+ // Hook to get window dimensions
70
+ const useWindowDimensions = () => {
71
+ const [windowDimensions, setWindowDimensions] = React.useState({
72
+ width: typeof window !== 'undefined' ? window.innerWidth : 1024,
73
+ height: typeof window !== 'undefined' ? window.innerHeight : 768,
74
+ });
75
+
76
+ useEffect(() => {
77
+ if (typeof window === 'undefined') return;
78
+
79
+ const handleResize = () => {
80
+ setWindowDimensions({
81
+ width: window.innerWidth,
82
+ height: window.innerHeight,
83
+ });
84
+ };
85
+
86
+ window.addEventListener('resize', handleResize);
87
+ handleResize(); // Set initial dimensions
88
+
89
+ return () => window.removeEventListener('resize', handleResize);
90
+ }, []);
91
+
92
+ return windowDimensions;
93
+ };
94
+
95
+ // Static components
96
+ const Spinner = React.memo(({ className = '' }: { className?: string }) => (
97
+ <div className={`animate-spin rounded-full border-4 border-gray-200 border-t-blue-500 ${className}`}>
98
+ <span className="sr-only">Loading...</span>
99
+ </div>
100
+ ));
101
+
102
+ const Drawer = React.memo(({ isOpen, onClose, children, title }: DrawerProps) => {
103
+ if (!isOpen) return null;
104
+
105
+ return (
106
+ <div className="fixed inset-0 z-50 overflow-hidden">
107
+ <div className="absolute inset-0 bg-black bg-opacity-50" onClick={onClose} />
108
+ <div className="absolute bottom-0 left-0 right-0 bg-white rounded-t-lg shadow-lg max-h-[80vh] flex flex-col overflow-hidden">
109
+ <div className="flex items-center justify-between p-4 border-b border-gray-200 flex-shrink-0">
110
+ <h2 className="text-lg font-semibold truncate">{title}</h2>
111
+ <button
112
+ onClick={onClose}
113
+ className="p-1 hover:bg-gray-100 rounded-full transition-colors flex-shrink-0 ml-2"
114
+ >
115
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
116
+ <path
117
+ strokeLinecap="round"
118
+ strokeLinejoin="round"
119
+ strokeWidth={2}
120
+ d="M6 18L18 6M6 6l12 12"
121
+ />
122
+ </svg>
123
+ </button>
124
+ </div>
125
+ <div className="flex-1 p-4 overflow-y-auto" style={{ minHeight: 0 }}>
126
+ {children}
127
+ </div>
128
+ </div>
129
+ </div>
130
+ );
131
+ });
132
+
133
+ const EmptyState = React.memo(() => (
134
+ <div className="h-full flex items-center justify-center bg-gray-100 p-4 sm:p-6 overflow-hidden">
135
+ <div className="text-center max-w-sm mx-auto">
136
+ <div className="text-3xl sm:text-4xl text-gray-400 mb-4">💬</div>
137
+ <h3 className="text-lg sm:text-xl font-semibold text-gray-600 mb-2">Welcome to Messenger</h3>
138
+ <p className="text-sm sm:text-base text-gray-500 leading-relaxed">
139
+ Select a conversation from the sidebar to start messaging
140
+ </p>
141
+ </div>
142
+ </div>
143
+ ));
144
+
145
+ // Mobile preview reducer
146
+ const mobilePreviewReducer = (
147
+ state: MobilePreviewState,
148
+ action: { payload: Partial<MobilePreviewState>; type: string },
149
+ ) => {
150
+ if (action.type === 'update') {
151
+ return { ...state, ...action.payload };
152
+ }
153
+ return state;
154
+ };
155
+
156
+ const Inbox = (props: InboxProps) => {
157
+ const { channelFilters: channelFilterProp, channelRole, supportServices, data, orgName, pathPrefix = null } = props;
158
+ const { id: pathChannelId, postId: pathPostId } = useParams();
159
+ const navigate = useNavigate();
160
+ const apolloClient = useApolloClient();
161
+
162
+ // Reduced state - only UI state remains, data comes from Apollo cache
163
+ const [isBottomDrawerOpen, setBottomDrawer] = React.useState(false);
164
+ const [mobilePreviewState, localDispatch] = useReducer(mobilePreviewReducer, {
165
+ mobilePreviewVisibility: false,
166
+ mobilePreviewText: false,
167
+ mobilePreviewCTAText: false,
168
+ });
169
+
170
+ // Hooks - improved responsive breakpoints with better granularity
171
+ const { width: windowWidth, height: windowHeight } = useWindowDimensions();
172
+ const isMobileView = useMediaQuery('(max-width: 640px)');
173
+ const isSmallTabletView = useMediaQuery('(min-width: 641px) and (max-width: 900px)');
174
+ const isTabletView = useMediaQuery('(min-width: 901px) and (max-width: 1024px)');
175
+ const isDesktopView = useMediaQuery('(min-width: 1025px)');
176
+ const isLargeDesktopView = useMediaQuery('(min-width: 1440px)');
177
+ const isSmallScreen = useMediaQuery('(max-width: 900px)');
178
+ // const auth = useSelector(userSelector);
179
+ const auth: any = useSelector<Store.Auth, IUserState>(userSelector, shallowEqual);
180
+ // const user = useSelector((state: any) => state.user, shallowEqual);
181
+
182
+ // Data destructuring from Apollo queries
183
+ const GetChannelsByUserQuery = data?.[0];
184
+ const {
185
+ data: userChannels,
186
+ loading: userChannelsLoading,
187
+ refetch: getChannelsRefetch,
188
+ } = GetChannelsByUserQuery || {};
189
+
190
+ // Get data directly from Apollo cache instead of local state
191
+ const channels = useMemo(() => {
192
+ if (!userChannels?.channelsByUser && !userChannels?.supportServiceChannels) return [];
193
+
194
+ return uniqBy([...(userChannels?.supportServiceChannels ?? []), ...(userChannels?.channelsByUser ?? [])], 'id');
195
+ }, [userChannels]);
196
+
197
+ // Memoize stable channel array to prevent unnecessary re-renders
198
+ const stableChannels = useMemo(() => {
199
+ return channels || [];
200
+ }, [channels]);
201
+
202
+ // Memoized values derived from Apollo cache data
203
+ const channelFilters = useMemo(() => {
204
+ const filters = { ...channelFilterProp };
205
+ const channelType = filters?.type ?? RoomType.Direct;
206
+ filters.type = supportServices ? [channelType, RoomType.Service] : channelType;
207
+ return filters;
208
+ }, [channelFilterProp, supportServices]);
209
+
210
+ const users = useMemo(() => {
211
+ return (
212
+ channels?.reduce((acc, curr) => {
213
+ const newMembers = curr.members?.filter(({ user }) => !acc.find(({ id }) => id === user.id)) || [];
214
+ return [...acc, ...newMembers.map(({ user }) => user)];
215
+ }, []) || []
216
+ );
217
+ }, [channels]);
218
+
219
+ // const currentUser = useMemo(
220
+ // () => users?.find((user) => user && user.alias?.includes(auth?.authUserId)),
221
+ // [users, auth?.authUserId],
222
+ // );
223
+ const currentUser = auth;
224
+
225
+ const channelName = useMemo(() => {
226
+ if (!channels || !pathChannelId) return '';
227
+
228
+ const currChannel = channels?.find((ch) => ch.id === pathChannelId);
229
+ if (!currChannel) return '';
230
+
231
+ const { members, title, type } = currChannel;
232
+
233
+ if (type === RoomType.Direct && members?.length >= 2) {
234
+ const otherUser = members.find((member) => member.user.id !== currentUser?.id);
235
+ if (otherUser?.user) {
236
+ const { givenName, familyName } = otherUser.user;
237
+ if (givenName && familyName) return `${givenName} ${familyName}`;
238
+ return givenName || familyName || title || 'Direct Message';
239
+ }
240
+ return title || 'Direct Message';
241
+ }
242
+
243
+ if (type === RoomType.Direct && members?.length === 1) {
244
+ if (members[0].user?.givenName && members[0]?.user?.familyName) {
245
+ return `${members[0].user?.givenName} ${members[0].user?.familyName}`;
246
+ }
247
+ return members[0].user?.givenName || members[0].user?.familyName || 'Direct Message';
248
+ }
249
+
250
+ return title || 'Channel';
251
+ }, [channels, pathChannelId, currentUser]);
252
+
253
+ // Effects
254
+ useEffect(() => {
255
+ applyFooterStyles();
256
+
257
+ // Optimistic refetch with cache update
258
+ const timeout = setTimeout(() => {
259
+ getChannelsRefetch?.({
260
+ role: channelRole,
261
+ criteria: orgName
262
+ ? { ...channelFilters, orgName: channelFilters?.orgName || orgName || '' }
263
+ : channelFilters,
264
+ supportServices: !!supportServices,
265
+ supportServiceCriteria: { type: RoomType.Service },
266
+ });
267
+ }, 0);
268
+ return () => clearTimeout(timeout);
269
+ }, [channelRole, channelFilters, supportServices, getChannelsRefetch]);
270
+
271
+ // Optimistic navigation with cache updates
272
+ const handleSelectChannel = useCallback(
273
+ async (channelId: string, pId: string | null = null) => {
274
+ // Optimistic UI update
275
+ const mainPath = orgName
276
+ ? pId
277
+ ? `/o/${orgName}/ai-messenger/${channelId}/${pId}`
278
+ : `/o/${orgName}/ai-messenger/${channelId}`
279
+ : pId
280
+ ? `/inbox/${channelId}/${pId}`
281
+ : `/inbox/${channelId}`;
282
+ const basePath = pathPrefix ? `${pathPrefix}${mainPath}` : mainPath;
283
+
284
+ const searchParams = new URLSearchParams();
285
+ // if (channelRole) searchParams.set('channelRole', channelRole);
286
+ // if (orgName) searchParams.set('orgName', orgName);
287
+
288
+ const newPath = searchParams.toString() ? `${basePath}?${searchParams.toString()}` : basePath;
289
+ navigate(newPath, { replace: true });
290
+
291
+ // Optimistically update Apollo cache for immediate UI feedback
292
+ try {
293
+ apolloClient.writeQuery({
294
+ query: MessagesDocument,
295
+ variables: {
296
+ channelId: channelId.toString(),
297
+ parentId: null,
298
+ limit: MESSAGES_PER_PAGE,
299
+ },
300
+ data: {
301
+ messages: {
302
+ __typename: 'Messages',
303
+ data: [],
304
+ totalCount: 0,
305
+ messagesRefId: channelId,
306
+ },
307
+ },
308
+ });
309
+ } catch (error) {
310
+ // Cache write might fail if query hasn't been executed yet, that's OK
311
+ console.debug('Cache write failed (expected on first load):', error);
312
+ }
313
+ },
314
+ [navigate, apolloClient, orgName, channelRole, pathPrefix],
315
+ );
316
+
317
+ const detailSidebarOptions = useMemo(
318
+ () => ({
319
+ isMobileView,
320
+ isSmallTabletView,
321
+ isTabletView,
322
+ isDesktopView,
323
+ isLargeDesktopView,
324
+ isSmallScreen,
325
+ setMobilePreviewCTAText: (v: string | ReactNode) =>
326
+ localDispatch({ payload: { mobilePreviewCTAText: v }, type: 'update' }),
327
+ setMobilePreviewText: (v: string | ReactNode) =>
328
+ localDispatch({ payload: { mobilePreviewText: v }, type: 'update' }),
329
+ setMobilePreviewVisibility: (v: boolean) =>
330
+ localDispatch({ payload: { mobilePreviewVisibility: v }, type: 'update' }),
331
+ }),
332
+ [isMobileView, isSmallTabletView, isTabletView, isDesktopView, isLargeDesktopView, isSmallScreen],
333
+ );
334
+
335
+ return (
336
+ <div
337
+ className="border-t border-gray-300 flex overflow-hidden"
338
+ style={{
339
+ height: `${windowHeight}px`,
340
+ maxHeight: '100vh',
341
+ }}
342
+ >
343
+ {/* Left Sidebar - Responsive Design */}
344
+ <div
345
+ className={`
346
+ flex-shrink-0 bg-gray-50 border-r border-gray-300 overflow-hidden transition-all duration-300 ease-in-out
347
+ ${isMobileView && pathChannelId ? 'hidden' : ''}
348
+ `}
349
+ style={{
350
+ width:
351
+ isMobileView && !pathChannelId
352
+ ? '100%'
353
+ : isMobileView && pathChannelId
354
+ ? '0px'
355
+ : isSmallTabletView
356
+ ? `${Math.min(288, windowWidth * 0.35)}px` // w-72 or 35% of window
357
+ : isTabletView
358
+ ? `${Math.min(320, windowWidth * 0.3)}px` // w-80 or 30% of window
359
+ : isLargeDesktopView
360
+ ? `${Math.min(384, windowWidth * 0.25)}px` // w-96 or 25% of window
361
+ : `${Math.min(320, windowWidth * 0.28)}px`, // w-80 or 28% of window
362
+ height: `${windowHeight}px`,
363
+ maxHeight: '100vh',
364
+ }}
365
+ >
366
+ <LeftSidebar
367
+ currentUser={currentUser}
368
+ userChannels={stableChannels}
369
+ userChannelsLoading={userChannelsLoading}
370
+ users={users}
371
+ handleSelectChannel={handleSelectChannel}
372
+ selectedChannelId={pathChannelId}
373
+ channelToTop={0}
374
+ getChannelsRefetch={getChannelsRefetch}
375
+ role={channelRole}
376
+ messagesQuery={data?.[1]}
377
+ windowHeight={windowHeight}
378
+ windowWidth={windowWidth}
379
+ />
380
+ </div>
381
+
382
+ {/* Main Content Area - Responsive */}
383
+ <div
384
+ className={`
385
+ flex-1 min-w-0 flex flex-col overflow-hidden transition-all duration-300 ease-in-out
386
+ ${isMobileView && !pathChannelId ? 'hidden' : 'flex'}
387
+ `}
388
+ style={{
389
+ minWidth: isSmallScreen ? '300px' : isDesktopView ? '500px' : '400px',
390
+ width: 'auto',
391
+ height: `${windowHeight}px`,
392
+ maxHeight: '100vh',
393
+ }}
394
+ >
395
+ {pathChannelId ? (
396
+ <ContentComponent
397
+ channelId={pathChannelId}
398
+ postId={pathPostId}
399
+ channelRole={channelRole}
400
+ pathPrefix={props.pathPrefix}
401
+ isMobileView={isMobileView}
402
+ isSmallTabletView={isSmallTabletView}
403
+ isTabletView={isTabletView}
404
+ isDesktopView={isDesktopView}
405
+ isLargeDesktopView={isLargeDesktopView}
406
+ isSmallScreen={isSmallScreen}
407
+ windowWidth={windowWidth}
408
+ windowHeight={windowHeight}
409
+ mobilePreviewState={mobilePreviewState}
410
+ detailSidebarOptions={detailSidebarOptions}
411
+ isBottomDrawerOpen={isBottomDrawerOpen}
412
+ setBottomDrawer={setBottomDrawer}
413
+ channelName={channelName}
414
+ loaderdata={data}
415
+ />
416
+ ) : (
417
+ <EmptyState />
418
+ )}
419
+ </div>
420
+
421
+ {/* Right Sidebar - Desktop Only */}
422
+ {pathChannelId && data?.[1] && isDesktopView && (
423
+ <div
424
+ className="border-l border-gray-200 bg-white flex-shrink-0 overflow-hidden"
425
+ style={{
426
+ width: `${windowWidth * 0.35}px`, // 40% of window width
427
+ height: `${windowHeight}px`,
428
+ maxHeight: '100vh',
429
+ }}
430
+ >
431
+ <RightSidebarWrapper
432
+ MessagesLoaderQuery={data?.[1]}
433
+ selectedPost={null}
434
+ detailSidebarOptions={detailSidebarOptions}
435
+ />
436
+ </div>
437
+ )}
438
+ </div>
439
+ );
440
+ };
441
+
442
+ const ContentComponent = React.memo((props: any) => {
443
+ const {
444
+ channelId,
445
+ channelRole,
446
+ pathPrefix,
447
+ postId,
448
+ isMobileView,
449
+ isSmallTabletView,
450
+ isTabletView,
451
+ isDesktopView,
452
+ isLargeDesktopView,
453
+ isSmallScreen,
454
+ windowWidth,
455
+ windowHeight,
456
+ mobilePreviewState,
457
+ detailSidebarOptions,
458
+ isBottomDrawerOpen,
459
+ setBottomDrawer,
460
+ channelName,
461
+ loaderdata,
462
+ } = props;
463
+
464
+ const ViewChannelDetailLoaderQuery = loaderdata?.[2];
465
+ const MessagesLoaderQuery = loaderdata?.[1];
466
+
467
+ const [selectedPost, setSelectedPost] = React.useState(null);
468
+ const { data: channelData, loading: channelLoading } = ViewChannelDetailLoaderQuery || {};
469
+
470
+ const onMessageClick = useCallback((msg: any) => {
471
+ setSelectedPost(msg);
472
+ }, []);
473
+
474
+ const channelsDetail = useMemo(() => {
475
+ return channelData?.viewChannelDetail || null;
476
+ }, [channelData]);
477
+
478
+ return (
479
+ <div className="flex overflow-hidden" style={{ height: `${windowHeight}px`, maxHeight: '100vh' }}>
480
+ {/* Main Chat Content */}
481
+ <div className="flex-1 flex flex-col min-w-0 overflow-hidden">
482
+ {/* Channel Header */}
483
+ {channelId && (
484
+ <div
485
+ className={`border-b border-gray-200 bg-white flex-shrink-0 z-10 ${
486
+ isSmallScreen ? 'px-3 py-3' : 'px-4 sm:px-6 py-4'
487
+ }`}
488
+ >
489
+ <div className="flex items-center justify-between">
490
+ <div className="flex items-center space-x-2 min-w-0 flex-1">
491
+ {/* Mobile/Small Screen Back Button */}
492
+ {(isMobileView || isSmallTabletView) && (
493
+ <button
494
+ className="p-2 hover:bg-gray-100 rounded-full transition-colors flex-shrink-0"
495
+ onClick={() => window.history.back()}
496
+ aria-label="Go back"
497
+ >
498
+ <svg
499
+ className="w-5 h-5 text-gray-600"
500
+ fill="none"
501
+ stroke="currentColor"
502
+ viewBox="0 0 24 24"
503
+ >
504
+ <path
505
+ strokeLinecap="round"
506
+ strokeLinejoin="round"
507
+ strokeWidth={2}
508
+ d="M15 19l-7-7 7-7"
509
+ />
510
+ </svg>
511
+ </button>
512
+ )}
513
+ <h2
514
+ className={`font-semibold text-gray-800 truncate ${
515
+ isSmallScreen ? 'text-base' : 'text-lg'
516
+ }`}
517
+ >
518
+ {channelName}
519
+ </h2>
520
+ </div>
521
+ {(isMobileView || isSmallTabletView) && mobilePreviewState?.mobilePreviewVisibility && (
522
+ <button
523
+ className="text-sm px-3 py-1 bg-teal-500 hover:bg-teal-600 text-white rounded-md transition-colors flex-shrink-0"
524
+ onClick={() => setBottomDrawer(true)}
525
+ >
526
+ {mobilePreviewState?.mobilePreviewCTAText}
527
+ </button>
528
+ )}
529
+ </div>
530
+ </div>
531
+ )}
532
+
533
+ {/* Mobile Preview */}
534
+ {(isMobileView || isSmallTabletView) && channelId && mobilePreviewState?.mobilePreviewVisibility && (
535
+ <div className={`mt-4 ${isSmallScreen ? 'mx-3' : 'mx-4'}`}>
536
+ <div className="mb-2">
537
+ <div className="w-full flex justify-between items-center gap-2 mb-[5px]">
538
+ <span className="truncate flex-1 text-sm">{mobilePreviewState?.mobilePreviewText}</span>
539
+ <button
540
+ className="text-sm px-3 py-2 bg-teal-500 hover:bg-teal-600 text-white rounded-md transition-colors flex-shrink-0"
541
+ onClick={() => setBottomDrawer(true)}
542
+ >
543
+ {mobilePreviewState?.mobilePreviewCTAText}
544
+ </button>
545
+ </div>
546
+ </div>
547
+ <hr className="border-gray-200" />
548
+ </div>
549
+ )}
550
+
551
+ {/* Content based on postId */}
552
+ <div className="flex-1 flex flex-col min-h-0 overflow-hidden">
553
+ {channelId && (
554
+ <>
555
+ {postId ? (
556
+ postId === '1' ? (
557
+ <ThreadsInbox
558
+ channelId={channelId}
559
+ role={channelRole}
560
+ pathPrefix={pathPrefix}
561
+ setChannelId={() => {}}
562
+ setPostId={() => {}}
563
+ setGoBack={() => {}}
564
+ />
565
+ ) : (
566
+ <ThreadMessagesInbox
567
+ channelId={channelId}
568
+ postId={postId}
569
+ role={channelRole}
570
+ goBack={false}
571
+ pathPrefix={pathPrefix}
572
+ setPostId={() => {}}
573
+ setChannelId={() => {}}
574
+ onMessageClick={onMessageClick}
575
+ />
576
+ )
577
+ ) : (
578
+ <MessagesComponent
579
+ channelId={channelId}
580
+ MessagesLoaderQuery={MessagesLoaderQuery}
581
+ channelsDetail={channelsDetail}
582
+ channelLoading={channelLoading}
583
+ onMessageClick={onMessageClick}
584
+ isSmallScreen={isSmallScreen}
585
+ isDesktopView={isDesktopView}
586
+ windowHeight={windowHeight}
587
+ windowWidth={windowWidth}
588
+ />
589
+ )}
590
+ </>
591
+ )}
592
+ </div>
593
+ </div>
594
+
595
+ {/* Mobile/Small Screen Drawer */}
596
+ {(isMobileView || isSmallTabletView) && (
597
+ <Drawer
598
+ isOpen={isBottomDrawerOpen}
599
+ onClose={() => setBottomDrawer(false)}
600
+ title={mobilePreviewState.mobilePreviewText as string}
601
+ >
602
+ <RightSidebarWrapper
603
+ MessagesLoaderQuery={MessagesLoaderQuery}
604
+ selectedPost={selectedPost}
605
+ detailSidebarOptions={detailSidebarOptions}
606
+ />
607
+ </Drawer>
608
+ )}
609
+ </div>
610
+ );
611
+ });
612
+
613
+ const RightSidebarWrapper = React.memo(({ MessagesLoaderQuery, selectedPost, detailSidebarOptions }: any) => {
614
+ const { data } = MessagesLoaderQuery || {};
615
+ const [activeTab, setActiveTab] = React.useState('preview');
616
+
617
+ const sortedMessages = useMemo(() => {
618
+ const messages = data?.messages?.data || [];
619
+ return orderBy(uniqBy(messages, 'id'), ['createdAt'], ['asc']);
620
+ }, [data?.messages?.data]);
621
+
622
+ const handleRefreshSandbox = useCallback(() => {
623
+ // Refresh sandbox functionality
624
+ console.log('Refreshing sandbox...');
625
+ // Add actual refresh logic here
626
+ }, []);
627
+
628
+ if (!sortedMessages.length) return null;
629
+
630
+ return (
631
+ <div className="h-full flex flex-col overflow-hidden bg-white">
632
+ {/* Header with tabs and refresh icon */}
633
+ <div className="flex-shrink-0 border-b border-gray-200">
634
+ <div className="flex items-center justify-between px-4 py-3">
635
+ <div className="flex items-center space-x-1">
636
+ <button
637
+ onClick={() => setActiveTab('preview')}
638
+ className={`flex items-center space-x-2 px-3 py-2 text-sm font-medium rounded-lg transition-colors ${
639
+ activeTab === 'preview'
640
+ ? 'bg-gray-100 text-gray-900'
641
+ : 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'
642
+ }`}
643
+ >
644
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
645
+ <path
646
+ strokeLinecap="round"
647
+ strokeLinejoin="round"
648
+ strokeWidth={2}
649
+ d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
650
+ />
651
+ <path
652
+ strokeLinecap="round"
653
+ strokeLinejoin="round"
654
+ strokeWidth={2}
655
+ 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"
656
+ />
657
+ </svg>
658
+ <span>Preview</span>
659
+ </button>
660
+ <button
661
+ onClick={() => setActiveTab('code')}
662
+ className={`flex items-center space-x-2 px-3 py-2 text-sm font-medium rounded-lg transition-colors ${
663
+ activeTab === 'code'
664
+ ? 'bg-gray-100 text-gray-900'
665
+ : 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'
666
+ }`}
667
+ >
668
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
669
+ <path
670
+ strokeLinecap="round"
671
+ strokeLinejoin="round"
672
+ strokeWidth={2}
673
+ d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"
674
+ />
675
+ </svg>
676
+ <span>Code</span>
677
+ </button>
678
+ </div>
679
+
680
+ {/* Refresh icon on the right */}
681
+ <button
682
+ onClick={handleRefreshSandbox}
683
+ className="p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-50 rounded-lg transition-colors"
684
+ title="Refresh Sandbox"
685
+ >
686
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
687
+ <path
688
+ strokeLinecap="round"
689
+ strokeLinejoin="round"
690
+ strokeWidth={2}
691
+ 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"
692
+ />
693
+ </svg>
694
+ </button>
695
+ </div>
696
+ <hr className="border-gray-300 my-6" />
697
+ {/* Refresh Sandbox Button - Below tabs, aligned right */}
698
+ <div className="px-4 pb-3 flex justify-end">
699
+ <button
700
+ onClick={handleRefreshSandbox}
701
+ className="flex items-center space-x-2 px-4 py-2 text-sm font-medium text-gray-600 hover:text-gray-900 hover:bg-gray-50 rounded-lg transition-colors border border-gray-200"
702
+ title="Refresh Sandbox"
703
+ >
704
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
705
+ <path
706
+ strokeLinecap="round"
707
+ strokeLinejoin="round"
708
+ strokeWidth={2}
709
+ 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"
710
+ />
711
+ </svg>
712
+ <span>Refresh Sandbox</span>
713
+ </button>
714
+ </div>
715
+ </div>
716
+
717
+ {/* Content Area */}
718
+ <div className="flex-1 overflow-hidden">
719
+ {activeTab === 'preview' ? (
720
+ <div className="h-full flex items-center justify-center bg-gray-50">
721
+ <div className="text-center">
722
+ {/* Next.js Logo */}
723
+ <div className="mb-8">
724
+ <svg className="w-32 h-16 mx-auto text-black" viewBox="0 0 394 80" fill="currentColor">
725
+ <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" />
726
+ <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" />
727
+ </svg>
728
+ </div>
729
+
730
+ <div className="space-y-4 text-gray-600">
731
+ <h3 className="text-lg font-semibold text-gray-900">Get started by editing</h3>
732
+ <p className="font-mono text-sm bg-gray-100 px-3 py-1 rounded">app/page.tsx</p>
733
+ <p>Save and see your changes instantly.</p>
734
+ </div>
735
+
736
+ <div className="mt-8 space-y-4">
737
+ <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">
738
+ <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
739
+ <path d="M12 2L2 7v10c0 5.55 3.84 9.739 9 11 5.16-1.261 9-5.45 9-11V7l-10-5z" />
740
+ </svg>
741
+ <span>Deploy now</span>
742
+ </button>
743
+ <p className="text-sm text-gray-500 underline cursor-pointer hover:text-gray-700">
744
+ Read our docs
745
+ </p>
746
+ </div>
747
+ </div>
748
+ </div>
749
+ ) : (
750
+ <div className="h-full bg-gray-900 text-gray-300 p-4 overflow-auto">
751
+ <div className="font-mono text-sm">
752
+ <div className="mb-4">
753
+ <div className="text-green-400">// app/page.tsx</div>
754
+ </div>
755
+ <div className="space-y-2">
756
+ <div>
757
+ <span className="text-purple-400">export</span>{' '}
758
+ <span className="text-purple-400">default</span>{' '}
759
+ <span className="text-blue-400">function</span>{' '}
760
+ <span className="text-yellow-400">Home</span>() {'{'}
761
+ </div>
762
+ <div className="ml-4">
763
+ <span className="text-purple-400">return</span> (
764
+ </div>
765
+ <div className="ml-8">
766
+ {'<'}
767
+ <span className="text-red-400">main</span>{' '}
768
+ <span className="text-green-400">className</span>=
769
+ <span className="text-yellow-400">
770
+ "flex min-h-screen flex-col items-center justify-between p-24"
771
+ </span>
772
+ {'>'}
773
+ </div>
774
+ <div className="ml-12">
775
+ {'<'}
776
+ <span className="text-red-400">div</span>{' '}
777
+ <span className="text-green-400">className</span>=
778
+ <span className="text-yellow-400">
779
+ "z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex"
780
+ </span>
781
+ {'>'}
782
+ </div>
783
+ <div className="ml-16">
784
+ {'<'}
785
+ <span className="text-red-400">p</span>{' '}
786
+ <span className="text-green-400">className</span>=
787
+ <span className="text-yellow-400">
788
+ "fixed left-0 top-0 flex w-full justify-center border-b border-gray-300
789
+ bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl
790
+ dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static
791
+ lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30"
792
+ </span>
793
+ {'>'}
794
+ </div>
795
+ <div className="ml-20 text-gray-400">Get started by editing&nbsp;</div>
796
+ <div className="ml-20">
797
+ {'<'}
798
+ <span className="text-red-400">code</span>{' '}
799
+ <span className="text-green-400">className</span>=
800
+ <span className="text-yellow-400">"font-mono font-bold"</span>
801
+ {'>'}app/page.tsx{'</'}
802
+ <span className="text-red-400">code</span>
803
+ {'>'}
804
+ </div>
805
+ <div className="ml-16">
806
+ {'</'}
807
+ <span className="text-red-400">p</span>
808
+ {'>'}
809
+ </div>
810
+ <div className="ml-12">
811
+ {'</'}
812
+ <span className="text-red-400">div</span>
813
+ {'>'}
814
+ </div>
815
+ <div className="ml-8">
816
+ {'</'}
817
+ <span className="text-red-400">main</span>
818
+ {'>'}
819
+ </div>
820
+ <div className="ml-4">);</div>
821
+ <div>{'}'}</div>
822
+ </div>
823
+ </div>
824
+ </div>
825
+ )}
826
+ </div>
827
+ </div>
828
+ );
829
+ });
830
+
831
+ const MessagesComponent = React.memo((props: any) => {
832
+ const {
833
+ channelId,
834
+ MessagesLoaderQuery,
835
+ channelsDetail,
836
+ channelLoading,
837
+ onMessageClick,
838
+ isSmallScreen,
839
+ isDesktopView,
840
+ windowHeight = 768,
841
+ windowWidth = 1024,
842
+ } = props;
843
+
844
+ const messageRootListRef = useRef(null);
845
+ const messageListRef = useRef(null);
846
+ const apolloClient = useApolloClient();
847
+ const [isLoadingOlder, setIsLoadingOlder] = React.useState(false);
848
+ const isLoadingOlderRef = useRef(false);
849
+ const scrollTimeoutRef = useRef(null);
850
+ const shouldAutoScrollRef = useRef(true);
851
+ const hasInitializedRef = useRef(false);
852
+
853
+ const auth = useSelector(userSelector);
854
+ const { startUpload } = useUploadFiles();
855
+ const [sendMsg] = useSendMessagesMutation();
856
+
857
+ // Stream assembler — intercepts [STREAM:*] messages and reassembles them
858
+ const { activeStreams, ingest, isSuppressed, reset: resetStreams } = useStreamAssembler();
859
+
860
+ // Reset streams on channel change
861
+ useEffect(() => {
862
+ resetStreams();
863
+ }, [channelId, resetStreams]);
864
+
865
+ const { data, loading: messageLoading, fetchMore: fetchMoreMessages, subscribeToMore } = MessagesLoaderQuery || {};
866
+
867
+ // Get messages directly from Apollo cache — filter out suppressed stream fragments
868
+ const messages = useMemo(() => {
869
+ const messagesData = data?.messages?.data || [];
870
+ const sorted = orderBy(uniqBy(messagesData, 'id'), ['createdAt'], ['asc']);
871
+ return sorted.filter((msg: any) => !isSuppressed(msg.id));
872
+ }, [data?.messages?.data, isSuppressed]);
873
+
874
+ const totalCount = data?.messages?.totalCount || 0;
875
+
876
+ const scrollToBottom = useCallback(() => {
877
+ if (messageRootListRef?.current) {
878
+ messageRootListRef.current.scrollTop = messageRootListRef.current.scrollHeight;
879
+ }
880
+ }, []);
881
+
882
+ // Auto-scroll rules:
883
+ // - Initial load: once messages arrive the first time
884
+ // - When user is at bottom and new messages arrive
885
+ // - When we explicitly set shouldAutoScrollRef (e.g., on send)
886
+ useEffect(() => {
887
+ if (isLoadingOlderRef.current) return;
888
+
889
+ // Initial load: first time messages appear
890
+ if (!hasInitializedRef.current && messages.length > 0) {
891
+ hasInitializedRef.current = true;
892
+ const timer = setTimeout(() => scrollToBottom(), 100);
893
+ return () => clearTimeout(timer);
894
+ }
895
+
896
+ if (shouldAutoScrollRef.current) {
897
+ const timer = setTimeout(() => scrollToBottom(), 100);
898
+ return () => clearTimeout(timer);
899
+ }
900
+ }, [messages.length, scrollToBottom]);
901
+
902
+ // Auto-scroll when streaming content updates
903
+ useEffect(() => {
904
+ if (activeStreams.size > 0 && shouldAutoScrollRef.current) {
905
+ const timer = setTimeout(() => scrollToBottom(), 50);
906
+ return () => clearTimeout(timer);
907
+ }
908
+ }, [activeStreams, scrollToBottom]);
909
+
910
+ const onFetchOld = useCallback(
911
+ async (skip: number) => {
912
+ if (channelId && fetchMoreMessages && !isLoadingOlder) {
913
+ try {
914
+ setIsLoadingOlder(true);
915
+ isLoadingOlderRef.current = true;
916
+ // Capture current scroll height before fetching
917
+ const oldScrollHeight = messageRootListRef?.current?.scrollHeight || 0;
918
+
919
+ await fetchMoreMessages({
920
+ variables: {
921
+ channelId: channelId.toString(),
922
+ parentId: null,
923
+ skip,
924
+ },
925
+ updateQuery: (prev, { fetchMoreResult }) => {
926
+ if (!fetchMoreResult) return prev;
927
+
928
+ const newMessages = fetchMoreResult.messages.data;
929
+ const existingMessages = prev.messages?.data || [];
930
+
931
+ return {
932
+ ...prev,
933
+ messages: {
934
+ ...fetchMoreResult.messages,
935
+ data: uniqBy([...newMessages, ...existingMessages], 'id'),
936
+ },
937
+ };
938
+ },
939
+ });
940
+
941
+ // Maintain scroll position after loading older messages
942
+ setTimeout(() => {
943
+ if (messageRootListRef?.current) {
944
+ const newScrollHeight = messageRootListRef.current.scrollHeight;
945
+ const scrollDiff = newScrollHeight - oldScrollHeight;
946
+ // For normal flex layout, maintain position by adjusting scroll offset
947
+ messageRootListRef.current.scrollTop = scrollDiff;
948
+ }
949
+ // Reset the loading flag after position is maintained
950
+ setTimeout(() => {
951
+ isLoadingOlderRef.current = false;
952
+ }, 50);
953
+ }, 100);
954
+ } catch (error) {
955
+ console.error('Error fetching older messages:', error);
956
+ isLoadingOlderRef.current = false;
957
+ } finally {
958
+ setIsLoadingOlder(false);
959
+ }
960
+ }
961
+ },
962
+ [channelId, fetchMoreMessages, isLoadingOlder],
963
+ );
964
+
965
+ // Scroll to bottom when channel changes
966
+ useEffect(() => {
967
+ if (channelId && messages.length > 0) {
968
+ isLoadingOlderRef.current = false; // Reset flag on channel change
969
+ const timer = setTimeout(() => scrollToBottom(), 200);
970
+ return () => clearTimeout(timer);
971
+ }
972
+ }, [channelId, scrollToBottom]);
973
+
974
+ // Alternative scroll detection for Firefox
975
+ useEffect(() => {
976
+ const element = messageRootListRef.current;
977
+ if (!element) return;
978
+
979
+ // Firefox-specific scroll detection using passive listeners
980
+ const handleScrollEnd = () => {
981
+ if (!isLoadingOlder && element) {
982
+ const { scrollTop } = element;
983
+ const isAtTop = Math.round(scrollTop) <= 30;
984
+ const hasMoreMessages = totalCount > messages.length;
985
+
986
+ if (isAtTop && hasMoreMessages) {
987
+ console.log('ScrollEnd triggered load more (Firefox):', {
988
+ scrollTop: Math.round(scrollTop),
989
+ totalCount,
990
+ messagesLength: messages.length,
991
+ });
992
+ onFetchOld(messages.length);
993
+ }
994
+ }
995
+ };
996
+
997
+ // Use scrollend event if available (modern Firefox/Chrome)
998
+ if ('onscrollend' in element) {
999
+ element.addEventListener('scrollend', handleScrollEnd, { passive: true });
1000
+ return () => {
1001
+ element.removeEventListener('scrollend', handleScrollEnd);
1002
+ };
1003
+ }
1004
+ }, [totalCount, messages.length, onFetchOld, isLoadingOlder]);
1005
+
1006
+ // Cleanup scroll timeout on unmount
1007
+ useEffect(() => {
1008
+ return () => {
1009
+ if (scrollTimeoutRef.current) {
1010
+ clearTimeout(scrollTimeoutRef.current);
1011
+ }
1012
+ };
1013
+ }, []);
1014
+
1015
+ const onMessagesScroll = useCallback(
1016
+ async (e: any) => {
1017
+ // Throttle scroll events for better performance, especially in Firefox
1018
+ if (scrollTimeoutRef.current) {
1019
+ clearTimeout(scrollTimeoutRef.current);
1020
+ }
1021
+
1022
+ scrollTimeoutRef.current = setTimeout(async () => {
1023
+ if (messageRootListRef.current && !isLoadingOlder) {
1024
+ const element = messageRootListRef.current;
1025
+ const { clientHeight, scrollHeight, scrollTop } = element;
1026
+
1027
+ // Firefox-compatible scroll detection
1028
+ // Use Math.ceil to handle Firefox's fractional scrollTop values
1029
+ const isAtTop = Math.ceil(scrollTop) <= 25;
1030
+ const hasMoreMessages = totalCount > messages.length;
1031
+
1032
+ // Track bottom proximity to decide future auto-scroll behavior
1033
+ const distanceFromBottom = scrollHeight - (scrollTop + clientHeight);
1034
+ const isAtBottom = distanceFromBottom <= 30;
1035
+ // Only auto-scroll for future incoming messages if user is at bottom
1036
+ shouldAutoScrollRef.current = isAtBottom;
1037
+
1038
+ // Additional Firefox-specific check
1039
+ const isFirefox = navigator.userAgent.includes('Firefox');
1040
+ const firefoxAdjustedTop = isFirefox ? Math.round(scrollTop) <= 30 : isAtTop;
1041
+
1042
+ if ((isAtTop || firefoxAdjustedTop) && hasMoreMessages) {
1043
+ console.log('Triggering load more:', {
1044
+ scrollTop: Math.ceil(scrollTop),
1045
+ originalScrollTop: scrollTop,
1046
+ totalCount,
1047
+ messagesLength: messages.length,
1048
+ scrollHeight,
1049
+ clientHeight,
1050
+ browser: isFirefox ? 'Firefox' : 'Other',
1051
+ isAtTop,
1052
+ firefoxAdjustedTop,
1053
+ });
1054
+ await onFetchOld(messages.length);
1055
+ }
1056
+ }
1057
+ }, 100);
1058
+ },
1059
+ [totalCount, messages.length, onFetchOld, isLoadingOlder],
1060
+ );
1061
+
1062
+ // Optimistic message sending with Apollo cache updates
1063
+ const handleSend = useCallback(
1064
+ async (message: string, files: any[] = []) => {
1065
+ // Allow sending if there's either a message or files
1066
+ if ((!message || !message.trim()) && (!files || files.length === 0)) return;
1067
+ if (!channelId) return;
1068
+
1069
+ try {
1070
+ // Ensure we auto-scroll when sending a new message
1071
+ shouldAutoScrollRef.current = true;
1072
+ setTimeout(() => scrollToBottom(), 0);
1073
+ const postId = objectId();
1074
+ const currentDate = new Date();
1075
+
1076
+ const createOptimisticMessage = (files?: any[]) => ({
1077
+ __typename: 'Post' as const,
1078
+ id: postId,
1079
+ message,
1080
+ createdAt: currentDate.toISOString(),
1081
+ updatedAt: currentDate.toISOString(),
1082
+ author: {
1083
+ __typename: 'UserAccount' as const,
1084
+ id: auth?.id,
1085
+ givenName: auth?.profile?.given_name || '',
1086
+ familyName: auth?.profile?.family_name || '',
1087
+ email: auth?.profile?.email || '',
1088
+ username: auth?.profile?.nickname || '',
1089
+ fullName: auth?.profile?.name || '',
1090
+ picture: auth?.profile?.picture || '',
1091
+ alias: [auth?.authUserId ?? ''],
1092
+ tokens: [],
1093
+ },
1094
+ isDelivered: false, // Will be true once confirmed by server
1095
+ isRead: false,
1096
+ type: 'TEXT' as PostTypeEnum,
1097
+ parentId: null,
1098
+ fromServer: false,
1099
+ channel: {
1100
+ __typename: 'Channel' as const,
1101
+ id: channelId,
1102
+ },
1103
+ propsConfiguration: {
1104
+ __typename: 'MachineConfiguration' as const,
1105
+ id: null,
1106
+ resource: '' as any,
1107
+ contents: null,
1108
+ keys: null,
1109
+ target: null,
1110
+ overrides: null,
1111
+ },
1112
+ props: {},
1113
+ files: {
1114
+ __typename: 'FilesInfo' as const,
1115
+ data: files || [],
1116
+ totalCount: files?.length || 0,
1117
+ },
1118
+ replies: {
1119
+ __typename: 'Messages' as const,
1120
+ data: [],
1121
+ totalCount: 0,
1122
+ },
1123
+ });
1124
+
1125
+ const optimisticMessage = createOptimisticMessage(files);
1126
+
1127
+ if (files?.length > 0) {
1128
+ const uploadResponse = await startUpload({
1129
+ file: files,
1130
+ saveUploadedFile: { variables: { postId } },
1131
+ createUploadLink: { variables: { postId } },
1132
+ });
1133
+
1134
+ if (!uploadResponse || uploadResponse.error) {
1135
+ const errorMsg =
1136
+ uploadResponse?.error?.message || 'File upload failed. Please check your S3 configuration.';
1137
+ console.error('Upload failed:', errorMsg);
1138
+ alert(`Upload failed: ${errorMsg}`);
1139
+ return;
1140
+ }
1141
+
1142
+ const uploadedFiles = uploadResponse.data as unknown as IFileInfo[];
1143
+ if (uploadedFiles) {
1144
+ const fileIds = uploadedFiles.map((f: any) => f.id);
1145
+ await sendMsg({
1146
+ variables: { postId, channelId, content: message, files: fileIds },
1147
+ optimisticResponse: {
1148
+ __typename: 'Mutation',
1149
+ sendMessage: createOptimisticMessage(uploadedFiles),
1150
+ },
1151
+ update: (cache, { data: mutationData }) => {
1152
+ if (!mutationData?.sendMessage) return;
1153
+ const newPost = mutationData.sendMessage;
1154
+ const cid = channelId.toString();
1155
+ cache.modify({
1156
+ fields: {
1157
+ messages(existing: any, { storeFieldName, toReference }: any) {
1158
+ if (!storeFieldName.includes(cid)) return existing;
1159
+ if (
1160
+ storeFieldName.includes('"parentId"') &&
1161
+ !storeFieldName.includes('"parentId":null')
1162
+ ) {
1163
+ return existing;
1164
+ }
1165
+ if (!existing) {
1166
+ return {
1167
+ __typename: 'Messages',
1168
+ data: [toReference(newPost)],
1169
+ totalCount: 1,
1170
+ };
1171
+ }
1172
+ const newRef = toReference(newPost);
1173
+ const existingRefs = existing.data || [];
1174
+ if (existingRefs.some((ref: any) => ref?.__ref === newRef?.__ref))
1175
+ return existing;
1176
+ return {
1177
+ ...existing,
1178
+ data: [...existingRefs, newRef],
1179
+ totalCount: (existing.totalCount || 0) + 1,
1180
+ };
1181
+ },
1182
+ },
1183
+ });
1184
+ },
1185
+ });
1186
+ }
1187
+ } else {
1188
+ await sendMsg({
1189
+ variables: { postId, channelId, content: message },
1190
+ optimisticResponse: {
1191
+ __typename: 'Mutation',
1192
+ sendMessage: optimisticMessage,
1193
+ },
1194
+ update: (cache, { data: mutationData }) => {
1195
+ if (!mutationData?.sendMessage) return;
1196
+ const newPost = mutationData.sendMessage;
1197
+ const cid = channelId.toString();
1198
+ cache.modify({
1199
+ fields: {
1200
+ messages(existing: any, { storeFieldName, toReference }: any) {
1201
+ if (!storeFieldName.includes(cid)) return existing;
1202
+ if (
1203
+ storeFieldName.includes('"parentId"') &&
1204
+ !storeFieldName.includes('"parentId":null')
1205
+ ) {
1206
+ return existing;
1207
+ }
1208
+ if (!existing) {
1209
+ return {
1210
+ __typename: 'Messages',
1211
+ data: [toReference(newPost)],
1212
+ totalCount: 1,
1213
+ };
1214
+ }
1215
+ const newRef = toReference(newPost);
1216
+ const existingRefs = existing.data || [];
1217
+ if (existingRefs.some((ref: any) => ref?.__ref === newRef?.__ref))
1218
+ return existing;
1219
+ return {
1220
+ ...existing,
1221
+ data: [...existingRefs, newRef],
1222
+ totalCount: (existing.totalCount || 0) + 1,
1223
+ };
1224
+ },
1225
+ },
1226
+ });
1227
+ },
1228
+ });
1229
+ }
1230
+ } catch (error) {
1231
+ console.error('Error sending message:', error);
1232
+ }
1233
+ },
1234
+ [channelId, auth, startUpload, sendMsg, scrollToBottom],
1235
+ );
1236
+
1237
+ // Show loading spinner for initial load
1238
+ if ((messageLoading || channelLoading) && messages.length === 0) {
1239
+ return (
1240
+ <div className="flex-1 flex justify-center items-center">
1241
+ <Spinner className="w-12 h-12" />
1242
+ </div>
1243
+ );
1244
+ }
1245
+
1246
+ return (
1247
+ <>
1248
+ <div
1249
+ ref={messageRootListRef}
1250
+ className={`overflow-y-scroll bg-gray-50 ${
1251
+ isSmallScreen ? 'p-2 px-3' : isDesktopView ? 'p-6 px-8' : 'p-4 px-6'
1252
+ }`}
1253
+ onScroll={onMessagesScroll}
1254
+ style={{
1255
+ height: `${windowHeight - 140}px`, // Subtract header + input height
1256
+ maxHeight: '100vh',
1257
+ scrollbarWidth: 'thin',
1258
+ scrollbarColor: '#cbd5e0 #f7fafc',
1259
+ overflowY: 'scroll',
1260
+ WebkitOverflowScrolling: 'touch',
1261
+ }}
1262
+ >
1263
+ <div className="min-h-full">
1264
+ {messages.length > 0 ? (
1265
+ <>
1266
+ {/* Loading indicator for older messages at the top */}
1267
+ {isLoadingOlder && (
1268
+ <div className="flex justify-center py-4">
1269
+ <div className="flex items-center space-x-2 text-gray-500">
1270
+ <Spinner className="w-4 h-4" />
1271
+ <span className="text-sm">Loading older messages...</span>
1272
+ </div>
1273
+ </div>
1274
+ )}
1275
+ <Messages
1276
+ innerRef={messageListRef}
1277
+ channelId={channelId}
1278
+ currentUser={auth}
1279
+ channelMessages={messages}
1280
+ totalCount={totalCount}
1281
+ onMessageClick={onMessageClick}
1282
+ isDesktopView={isDesktopView || false}
1283
+ isSmallScreen={isSmallScreen || false}
1284
+ />
1285
+ <SubscriptionHandler
1286
+ subscribeToMore={subscribeToMore}
1287
+ document={CHAT_MESSAGE_ADDED}
1288
+ variables={{ channelId: channelId.toString() }}
1289
+ enabled={!!channelId && !!subscribeToMore}
1290
+ updateQuery={(prev: any, { subscriptionData }: any) => {
1291
+ console.log('Subscription updateQuery called:', { prev, subscriptionData });
1292
+ if (!subscriptionData.data) {
1293
+ console.log('No subscription data, returning prev');
1294
+ return prev;
1295
+ }
1296
+ const newMessage = subscriptionData.data.chatMessageAdded;
1297
+ console.log('New message received via subscription:', newMessage);
1298
+
1299
+ // Check if this is a streaming message
1300
+ const isStream = parseStreamMessage(newMessage.message);
1301
+ if (isStream) {
1302
+ // Route to stream assembler — suppress from message list
1303
+ ingest(newMessage);
1304
+ // Still add to cache so we have a record, but it will be
1305
+ // filtered out by the isSuppressed check in the messages memo
1306
+ return {
1307
+ ...prev,
1308
+ messages: {
1309
+ ...prev?.messages,
1310
+ data: uniqBy([...(prev?.messages?.data || []), newMessage], 'id'),
1311
+ totalCount: (prev?.messages?.totalCount || 0) + 1,
1312
+ },
1313
+ };
1314
+ }
1315
+
1316
+ return {
1317
+ ...prev,
1318
+ messages: {
1319
+ ...prev?.messages,
1320
+ data: uniqBy([...(prev?.messages?.data || []), newMessage], 'id'),
1321
+ totalCount: (prev?.messages?.totalCount || 0) + 1,
1322
+ },
1323
+ };
1324
+ }}
1325
+ onError={(error) => {
1326
+ console.error('Subscription error:', error);
1327
+ }}
1328
+ />
1329
+ {/* Active streaming messages — live typing bubbles */}
1330
+ {activeStreams.size > 0 && (
1331
+ <div className="streaming-messages-container">
1332
+ {Array.from(activeStreams.values()).map((stream) => (
1333
+ <StreamingMessageBubble
1334
+ key={stream.streamId}
1335
+ stream={stream}
1336
+ isDesktopView={isDesktopView || false}
1337
+ isSmallScreen={isSmallScreen || false}
1338
+ />
1339
+ ))}
1340
+ </div>
1341
+ )}
1342
+ </>
1343
+ ) : (
1344
+ <div className="flex items-center justify-center text-gray-500 min-h-96">
1345
+ <div className="text-center max-w-sm mx-auto px-4">
1346
+ <div className="text-6xl mb-4 opacity-50">💬</div>
1347
+ <h3 className="text-lg font-semibold text-gray-600 mb-2">No messages yet</h3>
1348
+ <p className="text-sm text-gray-500">
1349
+ Start the conversation by sending a message below!
1350
+ </p>
1351
+ </div>
1352
+ </div>
1353
+ )}
1354
+ </div>
1355
+ </div>
1356
+ <div className="flex-shrink-0 border-t border-gray-200 bg-white">
1357
+ {/* Selection Options */}
1358
+ <div className="px-4 py-3 border-b border-gray-100">
1359
+ <div className="flex gap-3">
1360
+ {/* Framework Selection */}
1361
+ <div className="flex-1 relative">
1362
+ <button
1363
+ type="button"
1364
+ className="w-full px-3 py-2 text-sm border border-gray-300 rounded-md bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-left pr-10 flex items-center space-x-2"
1365
+ onClick={() => {
1366
+ const modal = document.getElementById('framework-modal');
1367
+ if (modal) {
1368
+ modal.classList.toggle('hidden');
1369
+ }
1370
+ }}
1371
+ >
1372
+ <div className="w-5 h-5 bg-purple-100 rounded flex items-center justify-center">⚛️</div>
1373
+ <span>Next.js</span>
1374
+ </button>
1375
+ <div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
1376
+ <svg
1377
+ className="w-4 h-4 text-gray-400"
1378
+ fill="none"
1379
+ stroke="currentColor"
1380
+ viewBox="0 0 24 24"
1381
+ >
1382
+ <path
1383
+ strokeLinecap="round"
1384
+ strokeLinejoin="round"
1385
+ strokeWidth={2}
1386
+ d="M19 9l-7 7-7-7"
1387
+ />
1388
+ </svg>
1389
+ </div>
1390
+
1391
+ {/* Framework Modal */}
1392
+ <div
1393
+ id="framework-modal"
1394
+ className="hidden absolute bottom-full left-0 mb-1 w-80 bg-white border border-gray-200 rounded-lg shadow-lg z-50 p-4"
1395
+ >
1396
+ <div className="space-y-2">
1397
+ <div className="flex items-center justify-between p-3 hover:bg-gray-50 rounded cursor-pointer border border-blue-300 bg-blue-50">
1398
+ <div className="flex items-center space-x-3">
1399
+ <div className="w-8 h-8 bg-purple-100 rounded flex items-center justify-center">
1400
+ ⚛️
1401
+ </div>
1402
+ <div>
1403
+ <div className="text-sm font-medium">Next.js</div>
1404
+ <div className="text-xs text-gray-500">
1405
+ React + Next.js 15 with Shadcn UI
1406
+ </div>
1407
+ </div>
1408
+ </div>
1409
+ <svg className="w-4 h-4 text-blue-500" fill="currentColor" viewBox="0 0 20 20">
1410
+ <path
1411
+ fillRule="evenodd"
1412
+ d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
1413
+ clipRule="evenodd"
1414
+ />
1415
+ </svg>
1416
+ </div>
1417
+
1418
+ <div className="flex items-center p-3 hover:bg-gray-50 rounded cursor-pointer">
1419
+ <div className="flex items-center space-x-3">
1420
+ <div className="w-8 h-8 bg-green-100 rounded flex items-center justify-center">
1421
+ 💚
1422
+ </div>
1423
+ <div>
1424
+ <div className="text-sm font-medium">Vue.js</div>
1425
+ <div className="text-xs text-gray-500">
1426
+ Vue 3 + Nuxt 3 with Tailwind CSS
1427
+ </div>
1428
+ </div>
1429
+ </div>
1430
+ </div>
1431
+
1432
+ <div className="flex items-center p-3 hover:bg-gray-50 rounded cursor-pointer">
1433
+ <div className="flex items-center space-x-3">
1434
+ <div className="w-8 h-8 bg-blue-100 rounded flex items-center justify-center">
1435
+ ⚛️
1436
+ </div>
1437
+ <div>
1438
+ <div className="text-sm font-medium">React</div>
1439
+ <div className="text-xs text-gray-500">
1440
+ React 18 + Vite with TypeScript
1441
+ </div>
1442
+ </div>
1443
+ </div>
1444
+ </div>
1445
+
1446
+ <div className="flex items-center p-3 hover:bg-gray-50 rounded cursor-pointer">
1447
+ <div className="flex items-center space-x-3">
1448
+ <div className="w-8 h-8 bg-red-100 rounded flex items-center justify-center">
1449
+ 🅰️
1450
+ </div>
1451
+ <div>
1452
+ <div className="text-sm font-medium">Angular</div>
1453
+ <div className="text-xs text-gray-500">
1454
+ Angular 17 + Material Design
1455
+ </div>
1456
+ </div>
1457
+ </div>
1458
+ </div>
1459
+
1460
+ <div className="flex items-center p-3 hover:bg-gray-50 rounded cursor-pointer">
1461
+ <div className="flex items-center space-x-3">
1462
+ <div className="w-8 h-8 bg-orange-100 rounded flex items-center justify-center">
1463
+ 🔥
1464
+ </div>
1465
+ <div>
1466
+ <div className="text-sm font-medium">Svelte</div>
1467
+ <div className="text-xs text-gray-500">SvelteKit + TailwindCSS</div>
1468
+ </div>
1469
+ </div>
1470
+ </div>
1471
+ </div>
1472
+ </div>
1473
+ </div>
1474
+
1475
+ {/* AI Model Selection */}
1476
+ <div className="flex-1 relative">
1477
+ <button
1478
+ type="button"
1479
+ className="w-full px-3 py-2 text-sm border border-gray-300 rounded-md bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-left pr-10"
1480
+ onClick={() => {
1481
+ const modal = document.getElementById('ai-model-modal');
1482
+ if (modal) {
1483
+ modal.classList.toggle('hidden');
1484
+ }
1485
+ }}
1486
+ >
1487
+ 🧠 Claude 3.5 Sonnet
1488
+ </button>
1489
+ <div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
1490
+ <svg
1491
+ className="w-4 h-4 text-gray-400"
1492
+ fill="none"
1493
+ stroke="currentColor"
1494
+ viewBox="0 0 24 24"
1495
+ >
1496
+ <path
1497
+ strokeLinecap="round"
1498
+ strokeLinejoin="round"
1499
+ strokeWidth={2}
1500
+ d="M19 9l-7 7-7-7"
1501
+ />
1502
+ </svg>
1503
+ </div>
1504
+
1505
+ {/* AI Model Modal */}
1506
+ <div
1507
+ id="ai-model-modal"
1508
+ className="hidden absolute bottom-full left-0 mb-1 w-72 bg-white border border-gray-200 rounded-lg shadow-lg z-50 p-4"
1509
+ >
1510
+ <div className="space-y-3">
1511
+ {/* Claude Models */}
1512
+ <div id="claude-models" className="space-y-2">
1513
+ <div className="flex items-center justify-between p-2 hover:bg-gray-50 rounded cursor-pointer">
1514
+ <div className="flex items-center space-x-3">
1515
+ <div className="w-6 h-6 bg-orange-100 rounded flex items-center justify-center">
1516
+ 🧠
1517
+ </div>
1518
+ <span className="text-sm font-medium">Claude 3.5 Sonnet</span>
1519
+ </div>
1520
+ <svg
1521
+ className="w-4 h-4 text-blue-500"
1522
+ fill="currentColor"
1523
+ viewBox="0 0 20 20"
1524
+ >
1525
+ <path
1526
+ fillRule="evenodd"
1527
+ d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
1528
+ clipRule="evenodd"
1529
+ />
1530
+ </svg>
1531
+ </div>
1532
+
1533
+ <div className="flex items-center p-2 hover:bg-gray-50 rounded cursor-pointer">
1534
+ <div className="flex items-center space-x-3">
1535
+ <div className="w-6 h-6 bg-orange-100 rounded flex items-center justify-center">
1536
+ 🧠
1537
+ </div>
1538
+ <span className="text-sm">Claude 3.5 Haiku</span>
1539
+ </div>
1540
+ </div>
1541
+
1542
+ <div className="flex items-center p-2 hover:bg-gray-50 rounded cursor-pointer">
1543
+ <div className="flex items-center space-x-3">
1544
+ <div className="w-6 h-6 bg-orange-100 rounded flex items-center justify-center">
1545
+ 🧠
1546
+ </div>
1547
+ <span className="text-sm">Claude 3 Opus</span>
1548
+ </div>
1549
+ </div>
1550
+
1551
+ <div className="flex items-center p-2 hover:bg-gray-50 rounded cursor-pointer">
1552
+ <div className="flex items-center space-x-3">
1553
+ <div className="w-6 h-6 bg-orange-100 rounded flex items-center justify-center">
1554
+ 🧠
1555
+ </div>
1556
+ <span className="text-sm">Claude 3 Sonnet</span>
1557
+ </div>
1558
+ </div>
1559
+
1560
+ <div className="flex items-center p-2 hover:bg-gray-50 rounded cursor-pointer">
1561
+ <div className="flex items-center space-x-3">
1562
+ <div className="w-6 h-6 bg-orange-100 rounded flex items-center justify-center">
1563
+ 🧠
1564
+ </div>
1565
+ <span className="text-sm">Claude 3 Haiku</span>
1566
+ </div>
1567
+ </div>
1568
+ </div>
1569
+
1570
+ {/* OpenAI Models */}
1571
+ <div id="openai-models" className="space-y-2 hidden">
1572
+ <div className="flex items-center justify-between p-2 hover:bg-gray-50 rounded cursor-pointer">
1573
+ <div className="flex items-center space-x-3">
1574
+ <div className="w-6 h-6 bg-green-100 rounded flex items-center justify-center">
1575
+ 🤖
1576
+ </div>
1577
+ <span className="text-sm font-medium">GPT-4o</span>
1578
+ </div>
1579
+ <svg
1580
+ className="w-4 h-4 text-blue-500"
1581
+ fill="currentColor"
1582
+ viewBox="0 0 20 20"
1583
+ >
1584
+ <path
1585
+ fillRule="evenodd"
1586
+ d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
1587
+ clipRule="evenodd"
1588
+ />
1589
+ </svg>
1590
+ </div>
1591
+
1592
+ <div className="flex items-center p-2 hover:bg-gray-50 rounded cursor-pointer">
1593
+ <div className="flex items-center space-x-3">
1594
+ <div className="w-6 h-6 bg-green-100 rounded flex items-center justify-center">
1595
+ 🤖
1596
+ </div>
1597
+ <span className="text-sm">GPT-4o mini</span>
1598
+ </div>
1599
+ </div>
1600
+
1601
+ <div className="flex items-center p-2 hover:bg-gray-50 rounded cursor-pointer">
1602
+ <div className="flex items-center space-x-3">
1603
+ <div className="w-6 h-6 bg-green-100 rounded flex items-center justify-center">
1604
+ 🤖
1605
+ </div>
1606
+ <span className="text-sm">GPT-4 Turbo</span>
1607
+ </div>
1608
+ </div>
1609
+
1610
+ <div className="flex items-center p-2 hover:bg-gray-50 rounded cursor-pointer">
1611
+ <div className="flex items-center space-x-3">
1612
+ <div className="w-6 h-6 bg-green-100 rounded flex items-center justify-center">
1613
+ 🤖
1614
+ </div>
1615
+ <span className="text-sm">GPT-4</span>
1616
+ </div>
1617
+ </div>
1618
+
1619
+ <div className="flex items-center p-2 hover:bg-gray-50 rounded cursor-pointer">
1620
+ <div className="flex items-center space-x-3">
1621
+ <div className="w-6 h-6 bg-green-100 rounded flex items-center justify-center">
1622
+ 🤖
1623
+ </div>
1624
+ <span className="text-sm">o1</span>
1625
+ </div>
1626
+ </div>
1627
+
1628
+ <div className="flex items-center p-2 hover:bg-gray-50 rounded cursor-pointer">
1629
+ <div className="flex items-center space-x-3">
1630
+ <div className="w-6 h-6 bg-green-100 rounded flex items-center justify-center">
1631
+ 🤖
1632
+ </div>
1633
+ <span className="text-sm">o1-mini</span>
1634
+ </div>
1635
+ </div>
1636
+ </div>
1637
+
1638
+ {/* Provider Buttons */}
1639
+ <div className="flex gap-2 pt-2 border-t border-gray-100">
1640
+ <button
1641
+ id="openai-btn"
1642
+ className="flex-1 px-3 py-2 border border-gray-300 text-gray-700 text-sm rounded-md hover:bg-gray-50 transition-colors"
1643
+ onClick={() => {
1644
+ document.getElementById('claude-models')?.classList.add('hidden');
1645
+ document.getElementById('openai-models')?.classList.remove('hidden');
1646
+ document
1647
+ .getElementById('openai-btn')
1648
+ ?.classList.add('bg-blue-500', 'text-white');
1649
+ document
1650
+ .getElementById('openai-btn')
1651
+ ?.classList.remove('border-gray-300', 'text-gray-700');
1652
+ document
1653
+ .getElementById('anthropic-btn')
1654
+ ?.classList.remove('bg-blue-500', 'text-white');
1655
+ document
1656
+ .getElementById('anthropic-btn')
1657
+ ?.classList.add('border-gray-300', 'text-gray-700');
1658
+ }}
1659
+ >
1660
+ OpenAI
1661
+ </button>
1662
+ <button
1663
+ id="anthropic-btn"
1664
+ className="flex-1 px-3 py-2 bg-blue-500 text-white text-sm rounded-md hover:bg-blue-600 transition-colors"
1665
+ onClick={() => {
1666
+ document.getElementById('openai-models')?.classList.add('hidden');
1667
+ document.getElementById('claude-models')?.classList.remove('hidden');
1668
+ document
1669
+ .getElementById('anthropic-btn')
1670
+ ?.classList.add('bg-blue-500', 'text-white');
1671
+ document
1672
+ .getElementById('anthropic-btn')
1673
+ ?.classList.remove('border-gray-300', 'text-gray-700');
1674
+ document
1675
+ .getElementById('openai-btn')
1676
+ ?.classList.remove('bg-blue-500', 'text-white');
1677
+ document
1678
+ .getElementById('openai-btn')
1679
+ ?.classList.add('border-gray-300', 'text-gray-700');
1680
+ }}
1681
+ >
1682
+ Anthropic
1683
+ </button>
1684
+ </div>
1685
+ </div>
1686
+ </div>
1687
+ </div>
1688
+
1689
+ {/* API Key Dropdown */}
1690
+ <div className="flex-1 relative">
1691
+ <button
1692
+ type="button"
1693
+ className="w-full px-3 py-2 text-sm border border-gray-300 rounded-md bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-left pr-10"
1694
+ onClick={() => {
1695
+ const modal = document.getElementById('api-key-modal');
1696
+ if (modal) {
1697
+ modal.classList.toggle('hidden');
1698
+ }
1699
+ }}
1700
+ >
1701
+ API Key ••••
1702
+ </button>
1703
+ <div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
1704
+ <svg
1705
+ className="w-4 h-4 text-gray-400"
1706
+ fill="none"
1707
+ stroke="currentColor"
1708
+ viewBox="0 0 24 24"
1709
+ >
1710
+ <path
1711
+ strokeLinecap="round"
1712
+ strokeLinejoin="round"
1713
+ strokeWidth={2}
1714
+ d="M19 9l-7 7-7-7"
1715
+ />
1716
+ </svg>
1717
+ </div>
1718
+
1719
+ {/* API Key Modal */}
1720
+ <div id="api-key-modal" className="hidden">
1721
+ {/* Modal Backdrop */}
1722
+ <div
1723
+ className="fixed inset-0 bg-opacity-50 z-[9998]"
1724
+ onClick={() => {
1725
+ const modal = document.getElementById('api-key-modal');
1726
+ if (modal) {
1727
+ modal.classList.add('hidden');
1728
+ }
1729
+ }}
1730
+ ></div>
1731
+
1732
+ {/* Modal Content */}
1733
+ <div
1734
+ className="fixed w-80 bg-white border border-gray-200 rounded-lg shadow-lg z-[9999] p-4"
1735
+ style={{
1736
+ position: 'fixed',
1737
+ left: '50%',
1738
+ top: '50%',
1739
+ transform: 'translate(-50%, -50%)',
1740
+ zIndex: 9999,
1741
+ }}
1742
+ >
1743
+ {/* Close Button */}
1744
+ <div className="flex justify-between items-center mb-4">
1745
+ <h3 className="text-lg font-semibold text-gray-900">API Key Settings</h3>
1746
+ <button
1747
+ type="button"
1748
+ className="text-gray-400 hover:text-gray-600 transition-colors"
1749
+ onClick={() => {
1750
+ const modal = document.getElementById('api-key-modal');
1751
+ if (modal) {
1752
+ modal.classList.add('hidden');
1753
+ }
1754
+ }}
1755
+ >
1756
+ <svg
1757
+ className="w-5 h-5"
1758
+ fill="none"
1759
+ stroke="currentColor"
1760
+ viewBox="0 0 24 24"
1761
+ >
1762
+ <path
1763
+ strokeLinecap="round"
1764
+ strokeLinejoin="round"
1765
+ strokeWidth={2}
1766
+ d="M6 18L18 6M6 6l12 12"
1767
+ />
1768
+ </svg>
1769
+ </button>
1770
+ </div>
1771
+
1772
+ <div className="space-y-4">
1773
+ {/* API Key Input Section */}
1774
+ <div>
1775
+ <label className="block text-sm font-medium text-gray-700 mb-2">
1776
+ API Key
1777
+ </label>
1778
+ <div className="relative">
1779
+ <input
1780
+ type="password"
1781
+ placeholder="••••••••••••••••••••••••••••••••••••••••••••"
1782
+ className="w-full px-3 py-2 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent pr-10"
1783
+ />
1784
+ <button
1785
+ type="button"
1786
+ className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-400 hover:text-gray-600"
1787
+ onClick={(e) => {
1788
+ const input = e.currentTarget
1789
+ .previousElementSibling as HTMLInputElement;
1790
+ if (input) {
1791
+ input.type =
1792
+ input.type === 'password' ? 'text' : 'password';
1793
+ }
1794
+ }}
1795
+ >
1796
+ <svg
1797
+ className="w-4 h-4"
1798
+ fill="none"
1799
+ stroke="currentColor"
1800
+ viewBox="0 0 24 24"
1801
+ >
1802
+ <path
1803
+ strokeLinecap="round"
1804
+ strokeLinejoin="round"
1805
+ strokeWidth={2}
1806
+ d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
1807
+ />
1808
+ <path
1809
+ strokeLinecap="round"
1810
+ strokeLinejoin="round"
1811
+ strokeWidth={2}
1812
+ 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"
1813
+ />
1814
+ </svg>
1815
+ </button>
1816
+ </div>
1817
+ </div>
1818
+
1819
+ {/* Parameters Section */}
1820
+ <div>
1821
+ <h3 className="text-sm font-medium text-gray-700 mb-3">Parameters</h3>
1822
+ <div className="space-y-2">
1823
+ <div className="flex justify-between items-center">
1824
+ <span className="text-sm text-gray-600">Output tokens</span>
1825
+ <span className="text-sm text-gray-500 bg-gray-100 px-2 py-1 rounded">
1826
+ Auto
1827
+ </span>
1828
+ </div>
1829
+ <div className="flex justify-between items-center">
1830
+ <span className="text-sm text-gray-600">Temperature</span>
1831
+ <span className="text-sm text-gray-500 bg-gray-100 px-2 py-1 rounded">
1832
+ Auto
1833
+ </span>
1834
+ </div>
1835
+ <div className="flex justify-between items-center">
1836
+ <span className="text-sm text-gray-600">Top P</span>
1837
+ <span className="text-sm text-gray-500 bg-gray-100 px-2 py-1 rounded">
1838
+ Auto
1839
+ </span>
1840
+ </div>
1841
+ <div className="flex justify-between items-center">
1842
+ <span className="text-sm text-gray-600">Top K</span>
1843
+ <span className="text-sm text-gray-500 bg-gray-100 px-2 py-1 rounded">
1844
+ Auto
1845
+ </span>
1846
+ </div>
1847
+ <div className="flex justify-between items-center">
1848
+ <span className="text-sm text-gray-600">Frequence penalty</span>
1849
+ <span className="text-sm text-gray-500 bg-gray-100 px-2 py-1 rounded">
1850
+ Auto
1851
+ </span>
1852
+ </div>
1853
+ <div className="flex justify-between items-center">
1854
+ <span className="text-sm text-gray-600">Presence penalty</span>
1855
+ <span className="text-sm text-gray-500 bg-gray-100 px-2 py-1 rounded">
1856
+ Auto
1857
+ </span>
1858
+ </div>
1859
+ </div>
1860
+ </div>
1861
+ </div>
1862
+ </div>
1863
+ </div>
1864
+ </div>
1865
+ </div>
1866
+ </div>
1867
+ <MessageInputComponent channelId={channelId} handleSend={handleSend} placeholder="Message" />
1868
+ </div>
1869
+ </>
1870
+ );
1871
+ });
1872
+
1873
+ // Display names for debugging
1874
+ Inbox.displayName = 'Inbox';
1875
+ ContentComponent.displayName = 'ContentComponent';
1876
+ MessagesComponent.displayName = 'MessagesComponent';
1877
+ RightSidebarWrapper.displayName = 'RightSidebarWrapper';
1878
+
1879
+ export default React.memo(Inbox);