@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,1357 @@
1
+ import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react';
2
+ import ReactDOM from 'react-dom';
3
+ import { UploadImageButton } from './InboxMessage/UploadImageButton';
4
+ import { ModelConfig as PersistentModelConfig } from '../hooks/usePersistentModelConfig';
5
+ import { ModelConfig } from '../hooks/usePersistentModelConfig';
6
+
7
+ interface ModelConfigPanelProps {
8
+ config: ModelConfig;
9
+ onConfigChange: (config: ModelConfig) => void;
10
+ isVisible: boolean;
11
+ onToggleVisibility: () => void;
12
+ showTemplate?: boolean;
13
+ }
14
+
15
+ export interface ModelToolbarProps {
16
+ modelConfig?: PersistentModelConfig;
17
+ onModelConfigChange?: (config: PersistentModelConfig) => void;
18
+ sending: boolean;
19
+ canSend: boolean;
20
+ onSend: () => void;
21
+ onUploadImageChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
22
+ showProjectSettings?: boolean;
23
+ isShowMeta?: boolean;
24
+ showModeSelector?: boolean;
25
+ showStopButton?: boolean;
26
+ onStop?: () => void;
27
+ }
28
+
29
+ const providerIcons: Record<ModelConfig['provider'], string> = {
30
+ openai: '🤖',
31
+ anthropic: '🧠',
32
+ gemini: '✨',
33
+ groq: '⚡',
34
+ };
35
+
36
+ export const templateOptions = [
37
+ {
38
+ value: 'vite-react' as const,
39
+ label: 'Vite React',
40
+ icon: '⚡',
41
+ description: 'Vite + React 18 with Visual Editor',
42
+ },
43
+ { value: 'nextjs' as const, label: 'Next.js', icon: '⚛️', description: 'React + Next.js 15 with Shadcn UI' },
44
+ { value: 'vue' as const, label: 'Vue.js', icon: '🟢', description: 'Vue 3 + Nuxt 3 with Tailwind CSS' },
45
+ ];
46
+
47
+ const templateDetails = {
48
+ nextjs: { name: 'Next.js', icon: '⚛️' },
49
+ vue: { name: 'Vue.js', icon: '🟢' },
50
+ 'vite-react': { name: 'Vite React', icon: '⚡' },
51
+ };
52
+
53
+ export const modelOptions: Record<ModelConfig['provider'], { value: string; label: string; description: string }[]> = {
54
+ openai: [
55
+ { value: 'gpt-4o', label: 'GPT-4o', description: 'Latest multimodal model' },
56
+ { value: 'gpt-4o-mini', label: 'GPT-4o mini', description: 'Fast and efficient' },
57
+ { value: 'gpt-4-turbo', label: 'GPT-4 Turbo', description: 'Enhanced GPT-4' },
58
+ { value: 'gpt-4', label: 'GPT-4', description: 'Most capable model' },
59
+ { value: 'o1', label: 'o1', description: 'Reasoning model' },
60
+ { value: 'o1-mini', label: 'o1-mini', description: 'Faster reasoning' },
61
+ ],
62
+ anthropic: [
63
+ { value: 'claude-sonnet-4-5-20250929', label: 'Claude Sonnet 4.5', description: 'Most intelligent model' },
64
+ { value: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5', description: 'Fast and accurate' },
65
+ { value: 'claude-opus-4-1-20250805', label: 'Claude Opus 4.1', description: 'Powerful reasoning' },
66
+ ],
67
+ gemini: [
68
+ {
69
+ value: 'gemini-2.5-flash-preview-05-20',
70
+ label: 'Gemini 2.5 Flash Preview 05-20',
71
+ description: 'Latest preview model',
72
+ },
73
+ {
74
+ value: 'gemini-2.5-pro-preview-05-06',
75
+ label: 'Gemini 2.5 Pro Preview 05-06',
76
+ description: 'Advanced reasoning',
77
+ },
78
+ { value: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash', description: 'Fast generation' },
79
+ { value: 'gemini-2.0-flash-lite', label: 'Gemini 2.0 Flash Lite', description: 'Lightweight version' },
80
+ { value: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro', description: 'Most capable' },
81
+ { value: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash', description: 'Fast and efficient' },
82
+ ],
83
+ groq: [
84
+ { value: 'llama-3.3-70b-versatile', label: 'Llama 3.3 70B Versatile', description: 'Plan agent default' },
85
+ { value: 'llama-3.1-70b-versatile', label: 'Llama 3.1 70B Versatile', description: 'Fast inference' },
86
+ { value: 'llama-3.1-8b-instant', label: 'Llama 3.1 8B Instant', description: 'Lightweight' },
87
+ { value: 'mixtral-8x7b-32768', label: 'Mixtral 8x7B', description: 'MoE model' },
88
+ ],
89
+ };
90
+
91
+ const providerDetails = {
92
+ openai: { name: 'OpenAI' },
93
+ anthropic: { name: 'Anthropic' },
94
+ gemini: { name: 'Google Vertex AI' },
95
+ groq: { name: 'Groq' },
96
+ };
97
+
98
+ // Helper function to get all models from all providers (includes provider for correct config update)
99
+ export const getAllModels = (): { value: string; label: string; provider?: ModelConfig['provider'] }[] => {
100
+ const allModels: { value: string; label: string; provider?: ModelConfig['provider'] }[] = [];
101
+ (Object.keys(modelOptions) as ModelConfig['provider'][]).forEach((provider) => {
102
+ modelOptions[provider].forEach((model) => {
103
+ allModels.push({ value: model.value, label: model.label, provider });
104
+ });
105
+ });
106
+ return allModels;
107
+ };
108
+
109
+ export const ModelConfigPanel: React.FC<ModelConfigPanelProps> = ({
110
+ config,
111
+ onConfigChange,
112
+ isVisible,
113
+ onToggleVisibility,
114
+ showTemplate = true,
115
+ }) => {
116
+ const [showApiKey, setShowApiKey] = useState(false);
117
+ const [showModelDropdown, setShowModelDropdown] = useState(false);
118
+ const [showApiKeyDropdown, setShowApiKeyDropdown] = useState(false);
119
+ const [showTemplateDropdown, setShowTemplateDropdown] = useState(false);
120
+ const modelDropdownRef = useRef<HTMLDivElement>(null);
121
+ const apiKeyDropdownRef = useRef<HTMLDivElement>(null);
122
+ const templateDropdownRef = useRef<HTMLDivElement>(null);
123
+
124
+ useEffect(() => {
125
+ const handleClickOutside = (event: MouseEvent) => {
126
+ const target = event.target as HTMLElement | null;
127
+
128
+ // If the click is inside any floating dropdown rendered via portal,
129
+ // do not treat it as an outside click.
130
+ if (target?.closest?.('[data-model-dropdown]')) {
131
+ return;
132
+ }
133
+
134
+ if (modelDropdownRef.current && !modelDropdownRef.current.contains(target as Node)) {
135
+ setShowModelDropdown(false);
136
+ }
137
+ if (apiKeyDropdownRef.current && !apiKeyDropdownRef.current.contains(target as Node)) {
138
+ setShowApiKeyDropdown(false);
139
+ }
140
+ if (templateDropdownRef.current && !templateDropdownRef.current.contains(target as Node)) {
141
+ setShowTemplateDropdown(false);
142
+ }
143
+ };
144
+ document.addEventListener('mousedown', handleClickOutside);
145
+ return () => document.removeEventListener('mousedown', handleClickOutside);
146
+ }, []);
147
+
148
+ const handleProviderChange = useCallback(
149
+ (provider: ModelConfig['provider']) => {
150
+ const defaultModel = modelOptions[provider][0].value;
151
+ onConfigChange({ ...config, provider, model: defaultModel });
152
+ },
153
+ [config, onConfigChange],
154
+ );
155
+
156
+ const handleModelChange = useCallback(
157
+ (model: string) => {
158
+ onConfigChange({ ...config, model });
159
+ },
160
+ [config, onConfigChange],
161
+ );
162
+
163
+ const handleApiKeyChange = useCallback(
164
+ (e: React.ChangeEvent<HTMLInputElement>) => {
165
+ onConfigChange({ ...config, apiKey: e.target.value });
166
+ },
167
+ [config, onConfigChange],
168
+ );
169
+
170
+ const handleTemplateChange = useCallback(
171
+ (template: ModelConfig['template']) => {
172
+ onConfigChange({ ...config, template });
173
+ },
174
+ [config, onConfigChange],
175
+ );
176
+
177
+ const getApiKeyPlaceholder = () => {
178
+ switch (config.provider) {
179
+ case 'openai':
180
+ return 'sk-...';
181
+ case 'anthropic':
182
+ return 'sk-ant-...';
183
+ case 'gemini':
184
+ return 'AI...';
185
+ case 'groq':
186
+ return 'gsk_...';
187
+ default:
188
+ return 'Enter API key';
189
+ }
190
+ };
191
+
192
+ const currentModels = modelOptions[config.provider] || [];
193
+ const currentModel = currentModels.find((m) => m.value === config.model);
194
+ const currentTemplate = templateOptions.find((t) => t.value === config.template);
195
+
196
+ return (
197
+ <div className="relative">
198
+ {/* <div className="flex items-center gap-2 mb-2">
199
+ <button
200
+ type="button"
201
+ className="px-2 py-1 text-xs rounded border border-gray-300 bg-white hover:bg-gray-50"
202
+ onClick={onToggleVisibility}
203
+ >
204
+ {isVisible ? 'Hide' : 'Show'} Config
205
+ </button>
206
+ {!config.apiKey && <span className="text-xs text-amber-600">API key required</span>}
207
+ </div> */}
208
+
209
+ {isVisible && (
210
+ <div className="py-2 bg-gray-50 rounded-lg grid grid-cols-1 md:grid-cols-3 gap-2">
211
+ {showTemplate && (
212
+ <div className="space-y-1 md:col-span-1">
213
+ {/* <label className="block text-xs text-gray-600">Template</label> */}
214
+ <div className="relative" ref={templateDropdownRef}>
215
+ <input
216
+ type="text"
217
+ readOnly
218
+ value={`${currentTemplate?.icon || '🔧'} ${
219
+ currentTemplate?.label || 'Select Template'
220
+ }`}
221
+ onClick={() => setShowTemplateDropdown(!showTemplateDropdown)}
222
+ className="w-full px-2 py-1 border border-gray-300 rounded cursor-pointer"
223
+ />
224
+ <span className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 text-xs pointer-events-none">
225
+
226
+ </span>
227
+ {showTemplateDropdown && (
228
+ <div className="absolute z-10 mt-1 w-full bg-white border border-gray-300 rounded shadow">
229
+ {templateOptions.map((template) => (
230
+ <button
231
+ key={template.value}
232
+ type="button"
233
+ onClick={() => {
234
+ handleTemplateChange(template.value);
235
+ setShowTemplateDropdown(false);
236
+ }}
237
+ className={`w-full text-left px-3 py-2 hover:bg-gray-50 ${
238
+ config.template === template.value ? 'bg-blue-50' : ''
239
+ }`}
240
+ >
241
+ <span className="mr-2">{template.icon}</span>
242
+ <span className="text-sm">{template.label}</span>
243
+ </button>
244
+ ))}
245
+ </div>
246
+ )}
247
+ </div>
248
+ </div>
249
+ )}
250
+
251
+ <div className="space-y-1 md:col-span-1">
252
+ {/* <label className="block text-xs text-gray-600">Model</label> */}
253
+ <div className="relative" ref={modelDropdownRef}>
254
+ <input
255
+ type="text"
256
+ readOnly
257
+ value={`${providerIcons[config.provider]} ${currentModel?.label || 'Select Model'}`}
258
+ onClick={() => setShowModelDropdown(!showModelDropdown)}
259
+ className="w-full px-2 py-1 border border-gray-300 rounded cursor-pointer"
260
+ />
261
+ <span className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 text-xs pointer-events-none">
262
+
263
+ </span>
264
+ {showModelDropdown && (
265
+ <div className="absolute z-10 mt-1 w-full bg-white border border-gray-300 rounded shadow max-h-60 overflow-y-auto">
266
+ {currentModels.map((model) => (
267
+ <button
268
+ key={model.value}
269
+ type="button"
270
+ onClick={() => {
271
+ handleModelChange(model.value);
272
+ setShowModelDropdown(false);
273
+ }}
274
+ className={`w-full text-left px-3 py-2 hover:bg-gray-50 ${
275
+ config.model === model.value ? 'bg-blue-50' : ''
276
+ }`}
277
+ >
278
+ <span className="mr-2">{providerIcons[config.provider]}</span>
279
+ <span className="text-sm">{model.label}</span>
280
+ </button>
281
+ ))}
282
+ <div className="p-2 border-t border-gray-100 flex gap-2">
283
+ {Object.keys(providerDetails).map((p) => (
284
+ <button
285
+ key={p}
286
+ type="button"
287
+ onClick={() => handleProviderChange(p as ModelConfig['provider'])}
288
+ className={`px-2 py-1 text-xs rounded ${
289
+ config.provider === p ? 'bg-blue-500 text-white' : 'bg-white border'
290
+ }`}
291
+ >
292
+ {providerDetails[p as ModelConfig['provider']].name}
293
+ </button>
294
+ ))}
295
+ </div>
296
+ </div>
297
+ )}
298
+ </div>
299
+ </div>
300
+
301
+ <div className="space-y-1 md:col-span-1">
302
+ {/* <label className="block text-xs text-gray-600">API Key</label> */}
303
+ <div className="relative" ref={apiKeyDropdownRef}>
304
+ <input
305
+ type="text"
306
+ readOnly
307
+ value={config.apiKey ? 'API Key ••••••••••••••••' : 'API Key'}
308
+ onClick={() => setShowApiKeyDropdown(!showApiKeyDropdown)}
309
+ className="w-full px-2 py-1 border border-gray-300 rounded cursor-pointer"
310
+ />
311
+ <span className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 text-xs pointer-events-none">
312
+
313
+ </span>
314
+ {showApiKeyDropdown && (
315
+ <div className="absolute right-0 z-10 mt-1 w-80 bg-white border border-gray-300 rounded shadow">
316
+ <div className="p-3">
317
+ <div className="mb-2">
318
+ <label className="block text-xs text-gray-600 mb-1">API Key</label>
319
+ <div className="relative">
320
+ <input
321
+ type={showApiKey ? 'text' : 'password'}
322
+ value={config.apiKey}
323
+ onChange={handleApiKeyChange}
324
+ placeholder={getApiKeyPlaceholder()}
325
+ className="w-full px-2 py-1 border border-gray-300 rounded pr-8"
326
+ />
327
+ <button
328
+ type="button"
329
+ onClick={() => setShowApiKey(!showApiKey)}
330
+ className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400"
331
+ >
332
+ {showApiKey ? 'Hide' : 'Show'}
333
+ </button>
334
+ </div>
335
+ </div>
336
+ <div>
337
+ <h4 className="text-xs font-medium text-gray-700 mb-1">Parameters</h4>
338
+ <div className="space-y-1 text-xs text-gray-600">
339
+ <div className="flex justify-between">
340
+ <span>Output tokens</span>
341
+ <span className="bg-gray-100 px-2 rounded">Auto</span>
342
+ </div>
343
+ <div className="flex justify-between">
344
+ <span>Temperature</span>
345
+ <span className="bg-gray-100 px-2 rounded">Auto</span>
346
+ </div>
347
+ <div className="flex justify-between">
348
+ <span>Top P</span>
349
+ <span className="bg-gray-100 px-2 rounded">Auto</span>
350
+ </div>
351
+ <div className="flex justify-between">
352
+ <span>Top K</span>
353
+ <span className="bg-gray-100 px-2 rounded">Auto</span>
354
+ </div>
355
+ <div className="flex justify-between">
356
+ <span>Frequency penalty</span>
357
+ <span className="bg-gray-100 px-2 rounded">Auto</span>
358
+ </div>
359
+ <div className="flex justify-between">
360
+ <span>Presence penalty</span>
361
+ <span className="bg-gray-100 px-2 rounded">Auto</span>
362
+ </div>
363
+ </div>
364
+ </div>
365
+ </div>
366
+ </div>
367
+ )}
368
+ </div>
369
+ </div>
370
+ </div>
371
+ )}
372
+ </div>
373
+ );
374
+ };
375
+
376
+ ModelConfigPanel.displayName = 'ModelConfigPanel';
377
+
378
+ export default ModelConfigPanel;
379
+
380
+ // Toolbar extracted for reuse from InputComponent.tsx (lines 209-394)
381
+ export interface ModelToolbarProps {
382
+ modelConfig?: PersistentModelConfig;
383
+ onModelConfigChange?: (config: PersistentModelConfig) => void;
384
+ sending: boolean;
385
+ canSend: boolean;
386
+ onSend: () => void;
387
+ onUploadImageChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
388
+ showProjectSettings?: boolean;
389
+ isShowMeta?: boolean;
390
+ showModeSelector?: boolean;
391
+ showStopButton?: boolean;
392
+ onStop?: () => void;
393
+ }
394
+
395
+ type MetadataFieldKey = keyof NonNullable<PersistentModelConfig['metadata']>;
396
+
397
+ const projectMetadataFields: Array<{ key: MetadataFieldKey; label: string }> = [
398
+ { key: 'formExtensionE2BAPIKey', label: 'E2B API Key' },
399
+ { key: 'formExtensionE2BClaudeTemplateId', label: 'E2B Claude Template ID' },
400
+ { key: 'formExtensionE2BDomain', label: 'E2B Domain' },
401
+ { key: 'formExtensionE2BTemplateId', label: 'E2B Template ID' },
402
+ { key: 'formExtensionE2BViteReactTemplateId', label: 'E2B Vite React Template ID' },
403
+ { key: 'formExtensionE2BVueTemplateId', label: 'E2B Vue Template ID' },
404
+ { key: 'formExtensionGithubOrg', label: 'GitHub Org' },
405
+ { key: 'formExtensionGithubReactBase', label: 'GitHub React Base' },
406
+ { key: 'formExtensionGithubToken', label: 'GitHub Token' },
407
+ ];
408
+
409
+ const FloatingDropdown: React.FC<{
410
+ anchorRef: React.RefObject<HTMLElement>;
411
+ open: boolean;
412
+ onClose?: () => void;
413
+ minWidth?: number;
414
+ children: React.ReactNode;
415
+ }> = ({ anchorRef, open, onClose, minWidth = 224, children }) => {
416
+ const [style, setStyle] = useState<{ top: number; left: number; width: number } | null>(null);
417
+
418
+ useEffect(() => {
419
+ if (!open) return;
420
+ const update = () => {
421
+ const el = anchorRef.current as HTMLElement | null;
422
+ if (!el) return;
423
+ const rect = el.getBoundingClientRect();
424
+ const width = Math.max(minWidth, rect.width);
425
+
426
+ // Heuristic expected dropdown height (px). Matches header + max-h-60 (15rem = 240px) + paddings.
427
+ const expectedHeight = 320;
428
+ const viewportHeight = window.innerHeight;
429
+ const spaceBelow = viewportHeight - rect.bottom;
430
+ const spaceAbove = rect.top;
431
+
432
+ const openAbove = spaceBelow < expectedHeight && spaceAbove > spaceBelow;
433
+ const top = openAbove ? Math.max(8, rect.top - expectedHeight - 8) : rect.bottom + 8;
434
+ const left = Math.min(Math.max(8, rect.left), window.innerWidth - width - 8);
435
+ setStyle({ top, left, width });
436
+ };
437
+ update();
438
+ window.addEventListener('scroll', update, true);
439
+ window.addEventListener('resize', update);
440
+ return () => {
441
+ window.removeEventListener('scroll', update, true);
442
+ window.removeEventListener('resize', update);
443
+ };
444
+ }, [open, anchorRef, minWidth]);
445
+
446
+ useEffect(() => {
447
+ if (!open) return;
448
+ const onKey = (e: KeyboardEvent) => {
449
+ if (e.key === 'Escape') onClose?.();
450
+ };
451
+ document.addEventListener('keydown', onKey);
452
+ return () => document.removeEventListener('keydown', onKey);
453
+ }, [open, onClose]);
454
+
455
+ if (!open || !style) return null;
456
+ return ReactDOM.createPortal(
457
+ <div
458
+ // Mark as model dropdown container so global click handler in ModelToolbar
459
+ // can detect clicks inside the floating panel (for both toolbar & modal usage).
460
+ data-model-dropdown
461
+ style={{ position: 'fixed', top: style.top, left: style.left, width: style.width, zIndex: 9999 }}
462
+ className="bg-white border border-gray-200 rounded-lg shadow-lg"
463
+ >
464
+ {children}
465
+ </div>,
466
+ document.body,
467
+ );
468
+ };
469
+
470
+ /** Reusable Project Settings modal with Configuration, Other Settings, and Secret tabs. */
471
+ export interface ProjectSettingsModalProps {
472
+ modelConfig: PersistentModelConfig;
473
+ onModelConfigChange: (config: PersistentModelConfig) => void;
474
+ onClose: () => void;
475
+ isShowMeta?: boolean;
476
+ }
477
+
478
+ export const ProjectSettingsModal: React.FC<ProjectSettingsModalProps> = ({
479
+ modelConfig,
480
+ onModelConfigChange,
481
+ onClose,
482
+ isShowMeta = false,
483
+ }) => {
484
+ const [settingsActiveTab, setSettingsActiveTab] = useState<'model' | 'other_settings' | 'secret'>('model');
485
+ const [showModelDropdown, setShowModelDropdown] = useState(false);
486
+ const [showTemplateDropdown, setShowTemplateDropdown] = useState(false);
487
+ const [modelSearch, setModelSearch] = useState('');
488
+ const [templateSearch, setTemplateSearch] = useState('');
489
+ const modelDropdownRef = useRef<HTMLDivElement>(null);
490
+ const templateDropdownRef = useRef<HTMLDivElement>(null);
491
+
492
+ useEffect(() => {
493
+ const handleClickOutside = (event: MouseEvent) => {
494
+ const target = event.target as HTMLElement;
495
+ if (target.closest('[data-model-dropdown]')) return;
496
+ if (modelDropdownRef.current && !modelDropdownRef.current.contains(target as Node)) {
497
+ setShowModelDropdown(false);
498
+ }
499
+ if (templateDropdownRef.current && !templateDropdownRef.current.contains(target as Node)) {
500
+ setShowTemplateDropdown(false);
501
+ }
502
+ };
503
+ document.addEventListener('mousedown', handleClickOutside);
504
+ return () => document.removeEventListener('mousedown', handleClickOutside);
505
+ }, []);
506
+
507
+ const allModelOptions = useMemo(() => getAllModels(), []);
508
+ const templateOptionsList = useMemo(
509
+ () => templateOptions.map((t) => ({ value: t.value, label: t.label, icon: t.icon })),
510
+ [],
511
+ );
512
+ const filteredTemplates = useMemo(() => {
513
+ if (!templateSearch) return templateOptionsList;
514
+ const q = templateSearch.toLowerCase();
515
+ return templateOptionsList.filter(
516
+ (o) => o.label.toLowerCase().includes(q) || String(o.value).toLowerCase().includes(q),
517
+ );
518
+ }, [templateOptionsList, templateSearch]);
519
+ const filteredModels = useMemo(() => {
520
+ if (!modelSearch) return allModelOptions;
521
+ const q = modelSearch.toLowerCase();
522
+ return allModelOptions.filter((o) => o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q));
523
+ }, [allModelOptions, modelSearch]);
524
+
525
+ const handleModelSelect = useCallback(
526
+ (modelValue: string, provider?: ModelConfig['provider']) => {
527
+ const update: Partial<ModelConfig> = { model: modelValue };
528
+ if (provider) update.provider = provider;
529
+ onModelConfigChange({ ...modelConfig, ...update });
530
+ setShowModelDropdown(false);
531
+ },
532
+ [modelConfig, onModelConfigChange],
533
+ );
534
+ const handleTemplateSelect = useCallback(
535
+ (templateValue: PersistentModelConfig['template']) => {
536
+ onModelConfigChange({ ...modelConfig, template: templateValue });
537
+ setShowTemplateDropdown(false);
538
+ },
539
+ [modelConfig, onModelConfigChange],
540
+ );
541
+ const handleMetadataChange = useCallback(
542
+ (field: MetadataFieldKey, value: string) => {
543
+ onModelConfigChange({
544
+ ...modelConfig,
545
+ metadata: { ...(modelConfig.metadata || {}), [field]: value },
546
+ });
547
+ },
548
+ [modelConfig, onModelConfigChange],
549
+ );
550
+
551
+ return (
552
+ <div
553
+ className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 overflow-y-auto py-12 px-4"
554
+ onClick={(e) => e.target === e.currentTarget && onClose()}
555
+ >
556
+ <div
557
+ className="bg-white rounded-lg shadow-xl w-full max-w-md max-h-[calc(100vh-6rem)] overflow-hidden flex flex-col"
558
+ onClick={(e) => e.stopPropagation()}
559
+ >
560
+ <div className="p-6 flex flex-col flex-1 overflow-hidden">
561
+ <div className="flex items-center justify-between pb-4">
562
+ <h3 className="text-lg font-semibold text-gray-900">Project Settings</h3>
563
+ <button
564
+ type="button"
565
+ onClick={onClose}
566
+ className="text-gray-400 hover:text-gray-600 transition-colors p-1"
567
+ aria-label="Close"
568
+ >
569
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
570
+ <path
571
+ strokeLinecap="round"
572
+ strokeLinejoin="round"
573
+ strokeWidth={2}
574
+ d="M6 18L18 6M6 6l12 12"
575
+ />
576
+ </svg>
577
+ </button>
578
+ </div>
579
+
580
+ <div className="mb-4">
581
+ <div className="flex border-b border-gray-200">
582
+ <button
583
+ type="button"
584
+ className={`px-3 py-2 text-sm -mb-px border-b-2 transition-colors ${
585
+ settingsActiveTab === 'model'
586
+ ? 'border-blue-500 text-blue-600'
587
+ : 'border-transparent text-gray-600 hover:text-gray-800'
588
+ }`}
589
+ onClick={() => setSettingsActiveTab('model')}
590
+ >
591
+ Configuration
592
+ </button>
593
+ <button
594
+ type="button"
595
+ className={`ml-2 px-3 py-2 text-sm -mb-px border-b-2 transition-colors ${
596
+ settingsActiveTab === 'other_settings'
597
+ ? 'border-blue-500 text-blue-600'
598
+ : 'border-transparent text-gray-600 hover:text-gray-800'
599
+ }`}
600
+ onClick={() => setSettingsActiveTab('other_settings')}
601
+ >
602
+ Other Settings
603
+ </button>
604
+ <button
605
+ type="button"
606
+ className={`ml-2 px-3 py-2 text-sm -mb-px border-b-2 transition-colors ${
607
+ settingsActiveTab === 'secret'
608
+ ? 'border-blue-500 text-blue-600'
609
+ : 'border-transparent text-gray-600 hover:text-gray-800'
610
+ }`}
611
+ onClick={() => setSettingsActiveTab('secret')}
612
+ >
613
+ Secret
614
+ </button>
615
+ </div>
616
+ </div>
617
+
618
+ <div className="flex-1 overflow-y-auto pr-1">
619
+ {settingsActiveTab === 'model' && (
620
+ <div className="space-y-4 pb-2">
621
+ <div className="space-y-2">
622
+ <label className="block text-sm font-medium text-gray-700">Model</label>
623
+ <div className="relative" ref={modelDropdownRef}>
624
+ <button
625
+ type="button"
626
+ onClick={() => setShowModelDropdown(!showModelDropdown)}
627
+ className="w-full flex items-center gap-2 px-3 py-2 text-sm rounded-lg border border-gray-300 bg-white hover:bg-gray-50 transition-colors overflow-hidden"
628
+ >
629
+ {allModelOptions.find((o) => o.value === modelConfig.model)?.label || (
630
+ <span className="text-gray-500">Model</span>
631
+ )}
632
+ <svg
633
+ className="w-4 h-4 text-gray-500 ml-auto"
634
+ fill="none"
635
+ stroke="currentColor"
636
+ viewBox="0 0 24 24"
637
+ >
638
+ <path
639
+ strokeLinecap="round"
640
+ strokeLinejoin="round"
641
+ strokeWidth={2}
642
+ d="M19 9l-7 7-7-7"
643
+ />
644
+ </svg>
645
+ </button>
646
+ <FloatingDropdown
647
+ anchorRef={modelDropdownRef as unknown as React.RefObject<HTMLElement>}
648
+ open={showModelDropdown}
649
+ onClose={() => setShowModelDropdown(false)}
650
+ minWidth={320}
651
+ >
652
+ <div className="p-2 border-b border-gray-100">
653
+ <input
654
+ autoFocus
655
+ value={modelSearch}
656
+ onChange={(e) => setModelSearch(e.target.value)}
657
+ placeholder="Search model..."
658
+ className="w-full px-2 py-1 text-xs border border-gray-200 rounded"
659
+ />
660
+ </div>
661
+ <div className="py-1 max-h-60 overflow-y-auto">
662
+ {filteredModels.map((option) => (
663
+ <button
664
+ key={option.value}
665
+ type="button"
666
+ onClick={() => handleModelSelect(option.value, option.provider)}
667
+ className={`w-full px-3 py-2 text-left text-xs hover:bg-gray-50 transition-colors ${
668
+ modelConfig?.model === option.value ? 'bg-blue-50' : ''
669
+ }`}
670
+ >
671
+ <div className="font-medium text-gray-900 truncate">
672
+ {option.label}
673
+ </div>
674
+ </button>
675
+ ))}
676
+ </div>
677
+ </FloatingDropdown>
678
+ </div>
679
+ </div>
680
+
681
+ <div className="space-y-2">
682
+ <label className="block text-sm font-medium text-gray-700">Template</label>
683
+ <div className="relative" ref={templateDropdownRef}>
684
+ <button
685
+ type="button"
686
+ onClick={() => setShowTemplateDropdown(!showTemplateDropdown)}
687
+ className="w-full flex items-center gap-2 px-3 py-2 text-sm rounded-lg border border-gray-300 bg-white hover:bg-gray-50 transition-colors overflow-hidden"
688
+ >
689
+ {templateOptionsList.find((o) => o.value === modelConfig.template) ? (
690
+ <>
691
+ <span className="text-base">
692
+ {
693
+ templateOptionsList.find(
694
+ (o) => o.value === modelConfig.template,
695
+ )?.icon
696
+ }
697
+ </span>
698
+ <span className="text-gray-700 truncate">
699
+ {
700
+ templateOptionsList.find(
701
+ (o) => o.value === modelConfig.template,
702
+ )?.label
703
+ }
704
+ </span>
705
+ </>
706
+ ) : (
707
+ <>
708
+ <span className="text-base">📝</span>
709
+ <span className="text-gray-500 truncate">Template</span>
710
+ </>
711
+ )}
712
+ <svg
713
+ className="w-4 h-4 text-gray-500 ml-auto"
714
+ fill="none"
715
+ stroke="currentColor"
716
+ viewBox="0 0 24 24"
717
+ >
718
+ <path
719
+ strokeLinecap="round"
720
+ strokeLinejoin="round"
721
+ strokeWidth={2}
722
+ d="M19 9l-7 7-7-7"
723
+ />
724
+ </svg>
725
+ </button>
726
+ <FloatingDropdown
727
+ anchorRef={templateDropdownRef as unknown as React.RefObject<HTMLElement>}
728
+ open={showTemplateDropdown}
729
+ onClose={() => setShowTemplateDropdown(false)}
730
+ minWidth={320}
731
+ >
732
+ <div className="p-2 border-b border-gray-100">
733
+ <input
734
+ autoFocus
735
+ value={templateSearch}
736
+ onChange={(e) => setTemplateSearch(e.target.value)}
737
+ placeholder="Search template..."
738
+ className="w-full px-2 py-1 text-xs border border-gray-200 rounded"
739
+ />
740
+ </div>
741
+ <div className="py-1 max-h-60 overflow-y-auto">
742
+ {filteredTemplates.map((option) => (
743
+ <button
744
+ key={option.value}
745
+ type="button"
746
+ onClick={() => handleTemplateSelect(option.value)}
747
+ className={`w-full px-3 py-2 text-left text-xs hover:bg-gray-50 transition-colors flex items-center gap-2 ${
748
+ modelConfig?.template === option.value ? 'bg-blue-50' : ''
749
+ }`}
750
+ >
751
+ <span className="text-sm">{option.icon}</span>
752
+ <span className="font-medium text-gray-900 truncate">
753
+ {option.label}
754
+ </span>
755
+ </button>
756
+ ))}
757
+ </div>
758
+ </FloatingDropdown>
759
+ </div>
760
+ </div>
761
+
762
+ <div className="space-y-2">
763
+ <label className="block text-sm font-medium text-gray-700">
764
+ Domain <span className="text-gray-500 text-xs">(optional)</span>
765
+ </label>
766
+ <input
767
+ type="text"
768
+ value={modelConfig.domain || ''}
769
+ onChange={(e) =>
770
+ onModelConfigChange({ ...modelConfig, domain: e.target.value })
771
+ }
772
+ className="w-full px-3 py-2 text-sm rounded-lg border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
773
+ placeholder="e.g. https://example.com"
774
+ />
775
+ </div>
776
+ </div>
777
+ )}
778
+
779
+ {settingsActiveTab === 'other_settings' && (
780
+ <div className="space-y-4 pb-2">
781
+ <div>
782
+ <label className="block text-sm font-medium text-gray-700 mb-2">
783
+ {modelConfig.provider === 'groq'
784
+ ? 'Groq API Key'
785
+ : modelConfig.provider === 'openai'
786
+ ? 'OpenAI API Key'
787
+ : modelConfig.provider === 'anthropic'
788
+ ? 'Anthropic API Key'
789
+ : modelConfig.provider === 'gemini'
790
+ ? 'Google / Gemini API Key'
791
+ : 'API Key'}{' '}
792
+ <span className="text-red-500">*</span>
793
+ </label>
794
+ <input
795
+ type="password"
796
+ value={modelConfig.apiKey || ''}
797
+ onChange={(e) =>
798
+ onModelConfigChange({ ...modelConfig, apiKey: e.target.value })
799
+ }
800
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
801
+ placeholder={
802
+ modelConfig.provider === 'groq'
803
+ ? 'gsk_... (Groq API key from console.groq.com)'
804
+ : modelConfig.provider === 'openai'
805
+ ? 'sk-...'
806
+ : modelConfig.provider === 'anthropic'
807
+ ? 'sk-ant-...'
808
+ : modelConfig.provider === 'gemini'
809
+ ? 'AI...'
810
+ : 'Enter your API key'
811
+ }
812
+ />
813
+ </div>
814
+ <div>
815
+ <label className="block text-sm font-medium text-gray-700 mb-2">
816
+ Extension ID <span className="text-red-500">*</span>
817
+ </label>
818
+ <input
819
+ type="text"
820
+ value={modelConfig.extensionId || ''}
821
+ onChange={(e) =>
822
+ onModelConfigChange({ ...modelConfig, extensionId: e.target.value })
823
+ }
824
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
825
+ placeholder="Enter your extension ID"
826
+ />
827
+ </div>
828
+ <div>
829
+ <label className="block text-sm font-medium text-gray-700 mb-2">
830
+ Form ID <span className="text-red-500">*</span>
831
+ </label>
832
+ <input
833
+ type="text"
834
+ value={modelConfig.formId || ''}
835
+ onChange={(e) =>
836
+ onModelConfigChange({ ...modelConfig, formId: e.target.value })
837
+ }
838
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
839
+ placeholder="Enter form ID"
840
+ />
841
+ </div>
842
+ <div>
843
+ <label className="block text-sm font-medium text-gray-700 mb-2">
844
+ Function ID <span className="text-red-500">*</span>
845
+ </label>
846
+ <input
847
+ type="text"
848
+ value={modelConfig.functionId || ''}
849
+ onChange={(e) =>
850
+ onModelConfigChange({ ...modelConfig, functionId: e.target.value })
851
+ }
852
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
853
+ placeholder="Enter function ID"
854
+ />
855
+ </div>
856
+ <div>
857
+ <label className="block text-sm font-medium text-gray-700 mb-2">
858
+ Step Name <span className="text-gray-500 text-xs">(optional)</span>
859
+ </label>
860
+ <input
861
+ type="text"
862
+ value={modelConfig.stepName || ''}
863
+ onChange={(e) =>
864
+ onModelConfigChange({ ...modelConfig, stepName: e.target.value })
865
+ }
866
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
867
+ placeholder="Enter step name (optional)"
868
+ />
869
+ </div>
870
+ </div>
871
+ )}
872
+
873
+ {settingsActiveTab === 'secret' && (
874
+ <div className="space-y-4 pb-2">
875
+ {isShowMeta ? (
876
+ <div>
877
+ <h4 className="text-sm font-semibold text-gray-800 mb-3">Secret Metadata</h4>
878
+ <div className="space-y-3">
879
+ {projectMetadataFields.map((field) => (
880
+ <div key={field.key}>
881
+ <label className="block text-xs font-medium text-gray-600 mb-1">
882
+ {field.label}
883
+ </label>
884
+ <input
885
+ type="text"
886
+ value={modelConfig.metadata?.[field.key] || ''}
887
+ onChange={(e) =>
888
+ handleMetadataChange(field.key, e.target.value)
889
+ }
890
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
891
+ placeholder={`Enter ${field.label.toLowerCase()}`}
892
+ />
893
+ </div>
894
+ ))}
895
+ </div>
896
+ </div>
897
+ ) : (
898
+ <div className="text-sm text-gray-600">
899
+ Secret metadata configuration is disabled for this environment.
900
+ </div>
901
+ )}
902
+ </div>
903
+ )}
904
+ </div>
905
+ </div>
906
+ </div>
907
+ </div>
908
+ );
909
+ };
910
+
911
+ ProjectSettingsModal.displayName = 'ProjectSettingsModal';
912
+
913
+ export const ModelToolbar: React.FC<ModelToolbarProps> = ({
914
+ modelConfig,
915
+ onModelConfigChange,
916
+ sending,
917
+ canSend,
918
+ onSend,
919
+ onUploadImageChange,
920
+ showProjectSettings = true,
921
+ isShowMeta = false,
922
+ showModeSelector = false,
923
+ showStopButton = false,
924
+ onStop,
925
+ }) => {
926
+ const [showModelDropdown, setShowModelDropdown] = useState(false);
927
+ const [showToolbarModelDropdown, setShowToolbarModelDropdown] = useState(false);
928
+ const [showTemplateDropdown, setShowTemplateDropdown] = useState(false);
929
+ const [showSettingsModal, setShowSettingsModal] = useState(false);
930
+ const [settingsActiveTab, setSettingsActiveTab] = useState<'model' | 'other_settings' | 'secret'>('model');
931
+ const [templateSearch, setTemplateSearch] = useState('');
932
+ const [modelSearch, setModelSearch] = useState('');
933
+ const [toolbarModelSearch, setToolbarModelSearch] = useState('');
934
+ const [mode, setMode] = useState<'plan' | 'build'>(modelConfig?.mode || 'plan');
935
+ const [showPlanTooltip, setShowPlanTooltip] = useState(false);
936
+ const [showBuildTooltip, setShowBuildTooltip] = useState(false);
937
+ const planButtonRef = useRef<HTMLButtonElement>(null);
938
+ const buildButtonRef = useRef<HTMLButtonElement>(null);
939
+ const modelDropdownRef = useRef<HTMLDivElement>(null);
940
+ const toolbarModelButtonRef = useRef<HTMLButtonElement>(null);
941
+ const templateDropdownRef = useRef<HTMLDivElement>(null);
942
+
943
+ // Sync mode with modelConfig
944
+ useEffect(() => {
945
+ if (modelConfig?.mode && modelConfig.mode !== mode) {
946
+ setMode(modelConfig.mode);
947
+ }
948
+ }, [modelConfig?.mode, mode]);
949
+
950
+ useEffect(() => {
951
+ const handleClickOutside = (event: MouseEvent) => {
952
+ const target = event.target as HTMLElement;
953
+
954
+ // If click is inside any floating dropdown rendered via portal,
955
+ // ignore it for outside-click logic (both toolbar & modal dropdowns).
956
+ if (target.closest('[data-model-dropdown]')) {
957
+ return;
958
+ }
959
+
960
+ if (modelDropdownRef.current && !modelDropdownRef.current.contains(target as Node)) {
961
+ setShowModelDropdown(false);
962
+ }
963
+
964
+ if (showToolbarModelDropdown) {
965
+ const isClickInsideButton = toolbarModelButtonRef.current?.contains(target);
966
+ if (!isClickInsideButton) {
967
+ setShowToolbarModelDropdown(false);
968
+ setToolbarModelSearch('');
969
+ }
970
+ }
971
+
972
+ if (templateDropdownRef.current && !templateDropdownRef.current.contains(target as Node)) {
973
+ setShowTemplateDropdown(false);
974
+ }
975
+ };
976
+ document.addEventListener('mousedown', handleClickOutside);
977
+ return () => document.removeEventListener('mousedown', handleClickOutside);
978
+ }, [showToolbarModelDropdown]);
979
+
980
+ const allModelOptions = useMemo(() => {
981
+ return getAllModels();
982
+ }, []);
983
+
984
+ const templateOptionsList = useMemo(() => {
985
+ return templateOptions.map((t) => ({ value: t.value, label: t.label, icon: t.icon }));
986
+ }, []);
987
+
988
+ const filteredTemplates = useMemo(() => {
989
+ if (!templateSearch) return templateOptionsList;
990
+ const q = templateSearch.toLowerCase();
991
+ return templateOptionsList.filter(
992
+ (o) => o.label.toLowerCase().includes(q) || String(o.value).toLowerCase().includes(q),
993
+ );
994
+ }, [templateOptionsList, templateSearch]);
995
+
996
+ const filteredModels = useMemo(() => {
997
+ if (!modelSearch) return allModelOptions;
998
+ const q = modelSearch.toLowerCase();
999
+ return allModelOptions.filter((o) => o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q));
1000
+ }, [allModelOptions, modelSearch]);
1001
+
1002
+ const filteredToolbarModels = useMemo(() => {
1003
+ if (!toolbarModelSearch) return allModelOptions;
1004
+ const q = toolbarModelSearch.toLowerCase();
1005
+ return allModelOptions.filter((o) => o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q));
1006
+ }, [allModelOptions, toolbarModelSearch]);
1007
+
1008
+ const handleModelSelect = useCallback(
1009
+ (modelValue: string) => {
1010
+ if (onModelConfigChange && modelConfig) {
1011
+ onModelConfigChange({ ...modelConfig, model: modelValue });
1012
+ }
1013
+ setShowModelDropdown(false);
1014
+ },
1015
+ [modelConfig, onModelConfigChange],
1016
+ );
1017
+
1018
+ const handleToolbarModelSelect = useCallback(
1019
+ (modelValue: string, provider?: ModelConfig['provider']) => {
1020
+ if (onModelConfigChange && modelConfig) {
1021
+ const update: Partial<ModelConfig> = { model: modelValue };
1022
+ if (provider) update.provider = provider;
1023
+ onModelConfigChange({ ...modelConfig, ...update });
1024
+ }
1025
+ setShowToolbarModelDropdown(false);
1026
+ setToolbarModelSearch('');
1027
+ },
1028
+ [modelConfig, onModelConfigChange],
1029
+ );
1030
+
1031
+ const handleTemplateSelect = useCallback(
1032
+ (templateValue: PersistentModelConfig['template']) => {
1033
+ if (onModelConfigChange && modelConfig) {
1034
+ onModelConfigChange({ ...modelConfig, template: templateValue });
1035
+ }
1036
+ setShowTemplateDropdown(false);
1037
+ },
1038
+ [modelConfig, onModelConfigChange],
1039
+ );
1040
+
1041
+ const handleMetadataChange = useCallback(
1042
+ (field: MetadataFieldKey, value: string) => {
1043
+ if (!onModelConfigChange || !modelConfig) return;
1044
+ onModelConfigChange({
1045
+ ...modelConfig,
1046
+ metadata: {
1047
+ ...(modelConfig.metadata || {}),
1048
+ [field]: value,
1049
+ },
1050
+ });
1051
+ },
1052
+ [modelConfig, onModelConfigChange],
1053
+ );
1054
+
1055
+ const handleModeChange = useCallback(
1056
+ (newMode: 'plan' | 'build') => {
1057
+ setMode(newMode);
1058
+ if (onModelConfigChange && modelConfig) {
1059
+ onModelConfigChange({ ...modelConfig, mode: newMode });
1060
+ }
1061
+ },
1062
+ [modelConfig, onModelConfigChange],
1063
+ );
1064
+
1065
+ return (
1066
+ <>
1067
+ <div className="flex items-center gap-2 overflow-x-auto overflow-y-visible whitespace-nowrap py-1 px-2 sm:px-0">
1068
+ {/* Mode Selection */}
1069
+ {showModeSelector && (
1070
+ <div className="flex items-center gap-2 mr-2">
1071
+ <div className="relative" style={{ overflow: 'visible' }}>
1072
+ <button
1073
+ ref={planButtonRef}
1074
+ type="button"
1075
+ onClick={() => handleModeChange('plan')}
1076
+ onMouseEnter={() => setShowPlanTooltip(true)}
1077
+ onMouseLeave={() => setShowPlanTooltip(false)}
1078
+ className={`w-7 h-7 rounded-full border border-gray-300 flex items-center justify-center transition-colors ${
1079
+ mode === 'plan'
1080
+ ? 'bg-gray-800 text-white'
1081
+ : 'bg-white text-gray-900 hover:bg-gray-50'
1082
+ }`}
1083
+ aria-label="Plan mode"
1084
+ >
1085
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1086
+ <path
1087
+ strokeLinecap="round"
1088
+ strokeLinejoin="round"
1089
+ strokeWidth={2}
1090
+ d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
1091
+ />
1092
+ </svg>
1093
+ </button>
1094
+ {showPlanTooltip &&
1095
+ planButtonRef.current &&
1096
+ ReactDOM.createPortal(
1097
+ <div
1098
+ role="tooltip"
1099
+ className="fixed z-[9999] inline-block px-3 py-2 text-sm font-medium text-white bg-gray-800 rounded-lg shadow-sm whitespace-nowrap pointer-events-none"
1100
+ style={{
1101
+ top: planButtonRef.current.getBoundingClientRect().top - 40,
1102
+ left:
1103
+ planButtonRef.current.getBoundingClientRect().left +
1104
+ planButtonRef.current.offsetWidth / 2,
1105
+ transform: 'translateX(-50%)',
1106
+ }}
1107
+ >
1108
+ Plan
1109
+ <div className="absolute top-full left-1/2 -translate-x-1/2 -mt-1">
1110
+ <div className="w-2 h-2 bg-gray-800 rotate-45"></div>
1111
+ </div>
1112
+ </div>,
1113
+ document.body,
1114
+ )}
1115
+ </div>
1116
+ <div className="relative" style={{ overflow: 'visible' }}>
1117
+ <button
1118
+ ref={buildButtonRef}
1119
+ type="button"
1120
+ onClick={() => handleModeChange('build')}
1121
+ onMouseEnter={() => setShowBuildTooltip(true)}
1122
+ onMouseLeave={() => setShowBuildTooltip(false)}
1123
+ className={`w-7 h-7 rounded-full border border-gray-300 flex items-center justify-center transition-colors ${
1124
+ mode === 'build'
1125
+ ? 'bg-gray-800 text-white'
1126
+ : 'bg-white text-gray-900 hover:bg-gray-50'
1127
+ }`}
1128
+ aria-label="Build mode"
1129
+ >
1130
+ <svg
1131
+ className="w-4 h-4"
1132
+ fill="none"
1133
+ stroke="currentColor"
1134
+ strokeWidth="2"
1135
+ strokeLinecap="round"
1136
+ strokeLinejoin="round"
1137
+ viewBox="0 0 24 24"
1138
+ >
1139
+ <path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5" />
1140
+ <path d="M9 18h6" />
1141
+ <path d="M10 22h4" />
1142
+ </svg>
1143
+ </button>
1144
+ {showBuildTooltip &&
1145
+ buildButtonRef.current &&
1146
+ ReactDOM.createPortal(
1147
+ <div
1148
+ role="tooltip"
1149
+ className="fixed z-[9999] inline-block px-3 py-2 text-sm font-medium text-white bg-gray-800 rounded-lg shadow-sm whitespace-nowrap pointer-events-none"
1150
+ style={{
1151
+ top: buildButtonRef.current.getBoundingClientRect().top - 40,
1152
+ left:
1153
+ buildButtonRef.current.getBoundingClientRect().left +
1154
+ buildButtonRef.current.offsetWidth / 2,
1155
+ transform: 'translateX(-50%)',
1156
+ }}
1157
+ >
1158
+ Build
1159
+ <div className="absolute top-full left-1/2 -translate-x-1/2 -mt-1">
1160
+ <div className="w-2 h-2 bg-gray-800 rotate-45"></div>
1161
+ </div>
1162
+ </div>,
1163
+ document.body,
1164
+ )}
1165
+ </div>
1166
+ </div>
1167
+ )}
1168
+
1169
+ {/* Template selection moved to Project Settings modal */}
1170
+
1171
+ {/* 2. Model Selection Dropdown */}
1172
+ {/* Model selection moved to Project Settings modal */}
1173
+
1174
+ <div className="flex-1"></div>
1175
+
1176
+ {showProjectSettings && (
1177
+ <button
1178
+ onClick={() => setShowSettingsModal(true)}
1179
+ className="flex items-center justify-center w-7 h-7 sm:w-7 sm:h-7 rounded-lg border border-gray-300 bg-white hover:bg-gray-50 transition-colors shrink-0"
1180
+ >
1181
+ <svg className="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1182
+ <path
1183
+ strokeLinecap="round"
1184
+ strokeLinejoin="round"
1185
+ strokeWidth={2}
1186
+ d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
1187
+ />
1188
+ <path
1189
+ strokeLinecap="round"
1190
+ strokeLinejoin="round"
1191
+ strokeWidth={2}
1192
+ d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
1193
+ />
1194
+ </svg>
1195
+ </button>
1196
+ )}
1197
+
1198
+ {/* Model Selection Button */}
1199
+ <div className="relative shrink-0">
1200
+ <button
1201
+ ref={toolbarModelButtonRef}
1202
+ onClick={() => setShowToolbarModelDropdown(!showToolbarModelDropdown)}
1203
+ className="flex items-center justify-center w-7 h-7 sm:w-7 sm:h-7 rounded-lg border border-gray-300 bg-white hover:bg-gray-50 transition-colors shrink-0"
1204
+ title={
1205
+ modelConfig?.model
1206
+ ? allModelOptions.find((o) => o.value === modelConfig.model)?.label || 'Choose a model'
1207
+ : 'Choose a model'
1208
+ }
1209
+ >
1210
+ <svg className="w-4 h-4 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1211
+ {/* Outer square */}
1212
+ <rect x="4" y="4" width="16" height="16" rx="1" strokeWidth="2" />
1213
+ {/* Inner square */}
1214
+ <rect x="8" y="8" width="8" height="8" rx="0.5" strokeWidth="2" />
1215
+ {/* Top pins */}
1216
+ <line x1="10" y1="4" x2="10" y2="2" strokeWidth="2" />
1217
+ <line x1="14" y1="4" x2="14" y2="2" strokeWidth="2" />
1218
+ {/* Bottom pins */}
1219
+ <line x1="10" y1="20" x2="10" y2="22" strokeWidth="2" />
1220
+ <line x1="14" y1="20" x2="14" y2="22" strokeWidth="2" />
1221
+ {/* Left pins */}
1222
+ <line x1="4" y1="10" x2="2" y2="10" strokeWidth="2" />
1223
+ <line x1="4" y1="14" x2="2" y2="14" strokeWidth="2" />
1224
+ {/* Right pins */}
1225
+ <line x1="20" y1="10" x2="22" y2="10" strokeWidth="2" />
1226
+ <line x1="20" y1="14" x2="22" y2="14" strokeWidth="2" />
1227
+ </svg>
1228
+ </button>
1229
+
1230
+ <FloatingDropdown
1231
+ anchorRef={toolbarModelButtonRef as unknown as React.RefObject<HTMLElement>}
1232
+ open={showToolbarModelDropdown}
1233
+ onClose={() => {
1234
+ setShowToolbarModelDropdown(false);
1235
+ setToolbarModelSearch('');
1236
+ }}
1237
+ minWidth={280}
1238
+ >
1239
+ <div data-model-dropdown className="bg-white">
1240
+ <div className="p-2 border-b border-gray-100">
1241
+ <input
1242
+ autoFocus
1243
+ value={toolbarModelSearch}
1244
+ onChange={(e) => setToolbarModelSearch(e.target.value)}
1245
+ placeholder="Search model..."
1246
+ className="w-full px-2 py-1.5 text-xs border border-gray-200 rounded focus:outline-none focus:ring-1 focus:ring-blue-500"
1247
+ />
1248
+ </div>
1249
+ <div className="py-1 max-h-60 overflow-y-auto">
1250
+ {filteredToolbarModels.map((option) => (
1251
+ <button
1252
+ key={option.value}
1253
+ onClick={() => handleToolbarModelSelect(option.value, option.provider)}
1254
+ className={`w-full px-3 py-2 text-left text-sm hover:bg-gray-50 transition-colors flex items-center justify-between ${
1255
+ modelConfig?.model === option.value ? 'bg-blue-50' : ''
1256
+ }`}
1257
+ >
1258
+ <span className="text-gray-900">{option.label}</span>
1259
+ {modelConfig?.model === option.value && (
1260
+ <svg
1261
+ className="w-4 h-4 text-blue-600"
1262
+ fill="none"
1263
+ stroke="currentColor"
1264
+ viewBox="0 0 24 24"
1265
+ >
1266
+ <path
1267
+ strokeLinecap="round"
1268
+ strokeLinejoin="round"
1269
+ strokeWidth={2}
1270
+ d="M5 13l4 4L19 7"
1271
+ />
1272
+ </svg>
1273
+ )}
1274
+ </button>
1275
+ ))}
1276
+ </div>
1277
+ </div>
1278
+ </FloatingDropdown>
1279
+ </div>
1280
+
1281
+ <UploadImageButton onChange={onUploadImageChange}>
1282
+ <div className="flex items-center justify-center w-7 h-7 sm:w-7 sm:h-7 rounded-lg border border-gray-300 bg-white shrink-0 ml-1">
1283
+ <svg className="w-3 h-3 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1284
+ <path
1285
+ strokeLinecap="round"
1286
+ strokeLinejoin="round"
1287
+ strokeWidth={2}
1288
+ d="M21.44 11.05l-9.19 9.19a6 6 0 11-8.49-8.49l9.19-9.19a4 4 0 115.66 5.66l-9.19 9.19a2 2 0 11-2.83-2.83l8.49-8.49"
1289
+ />
1290
+ </svg>
1291
+ </div>
1292
+ </UploadImageButton>
1293
+
1294
+ {/* Send/Stop Button */}
1295
+ {showStopButton ? (
1296
+ <button
1297
+ className="flex items-center justify-center w-7 h-7 sm:w-7 sm:h-7 rounded-lg border border-red-500 bg-red-500 hover:bg-red-600 text-white transition-colors shrink-0"
1298
+ onClick={onStop}
1299
+ type="button"
1300
+ >
1301
+ <svg className="w-3 h-3" fill="currentColor" viewBox="0 0 24 24">
1302
+ <circle cx="12" cy="12" r="8" fill="white" />
1303
+ </svg>
1304
+ </button>
1305
+ ) : (
1306
+ <button
1307
+ className={`flex items-center justify-center w-7 h-7 sm:w-7 sm:h-7 rounded-lg border transition-colors shrink-0 ${
1308
+ canSend && !sending
1309
+ ? 'border-blue-500 bg-blue-500 hover:bg-blue-600 text-white'
1310
+ : 'border-gray-300 bg-gray-100 text-gray-400 cursor-not-allowed'
1311
+ }`}
1312
+ onClick={onSend}
1313
+ disabled={!canSend || sending}
1314
+ type="button"
1315
+ >
1316
+ {sending ? (
1317
+ <svg className="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
1318
+ <circle
1319
+ className="opacity-25"
1320
+ cx="12"
1321
+ cy="12"
1322
+ r="10"
1323
+ stroke="currentColor"
1324
+ strokeWidth="4"
1325
+ ></circle>
1326
+ <path
1327
+ className="opacity-75"
1328
+ fill="currentColor"
1329
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
1330
+ ></path>
1331
+ </svg>
1332
+ ) : (
1333
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1334
+ <path
1335
+ strokeLinecap="round"
1336
+ strokeLinejoin="round"
1337
+ strokeWidth={2}
1338
+ d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"
1339
+ />
1340
+ </svg>
1341
+ )}
1342
+ </button>
1343
+ )}
1344
+ </div>
1345
+
1346
+ {/* Settings Modal */}
1347
+ {showProjectSettings && showSettingsModal && modelConfig && onModelConfigChange && (
1348
+ <ProjectSettingsModal
1349
+ modelConfig={modelConfig}
1350
+ onModelConfigChange={onModelConfigChange}
1351
+ onClose={() => setShowSettingsModal(false)}
1352
+ isShowMeta={isShowMeta}
1353
+ />
1354
+ )}
1355
+ </>
1356
+ );
1357
+ };