@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.
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/jest.config.js +9 -0
- package/lib/cdm-locales/en/translations.json +31 -0
- package/lib/cdm-locales/es/translations.json +31 -0
- package/lib/components/AIAgent/AIAgent.d.ts +32 -0
- package/lib/components/AIAgent/AIAgent.d.ts.map +1 -0
- package/lib/components/AIAgent/AIAgent.js +1135 -0
- package/lib/components/AIAgent/AIAgent.js.map +1 -0
- package/lib/components/AIAgent/InputComponent.d.ts +84 -0
- package/lib/components/AIAgent/InputComponent.d.ts.map +1 -0
- package/lib/components/AIAgent/InputComponent.js +417 -0
- package/lib/components/AIAgent/InputComponent.js.map +1 -0
- package/lib/components/AIAgent/index.d.ts +2 -0
- package/lib/components/AIAgent/index.d.ts.map +1 -0
- package/lib/components/InboxMessage/CommonMessage.d.ts +8 -0
- package/lib/components/InboxMessage/CommonMessage.d.ts.map +1 -0
- package/lib/components/InboxMessage/CommonMessage.js +35 -0
- package/lib/components/InboxMessage/CommonMessage.js.map +1 -0
- package/lib/components/InboxMessage/ConversationItem.d.ts +14 -0
- package/lib/components/InboxMessage/ConversationItem.d.ts.map +1 -0
- package/lib/components/InboxMessage/ConversationItem.js +200 -0
- package/lib/components/InboxMessage/ConversationItem.js.map +1 -0
- package/lib/components/InboxMessage/InputComponent.d.ts +20 -0
- package/lib/components/InboxMessage/InputComponent.d.ts.map +1 -0
- package/lib/components/InboxMessage/InputComponent.js +148 -0
- package/lib/components/InboxMessage/InputComponent.js.map +1 -0
- package/lib/components/InboxMessage/LeftSidebar.d.ts +20 -0
- package/lib/components/InboxMessage/LeftSidebar.d.ts.map +1 -0
- package/lib/components/InboxMessage/LeftSidebar.js +102 -0
- package/lib/components/InboxMessage/LeftSidebar.js.map +1 -0
- package/lib/components/InboxMessage/MessageInput.d.ts +9 -0
- package/lib/components/InboxMessage/MessageInput.d.ts.map +1 -0
- package/lib/components/InboxMessage/MessageInput.js +154 -0
- package/lib/components/InboxMessage/MessageInput.js.map +1 -0
- package/lib/components/InboxMessage/MessageInputComponent.d.ts +9 -0
- package/lib/components/InboxMessage/MessageInputComponent.d.ts.map +1 -0
- package/lib/components/InboxMessage/Messages.d.ts +17 -0
- package/lib/components/InboxMessage/Messages.d.ts.map +1 -0
- package/lib/components/InboxMessage/Messages.js +99 -0
- package/lib/components/InboxMessage/Messages.js.map +1 -0
- package/lib/components/InboxMessage/MessagesBuilderUi.d.ts +17 -0
- package/lib/components/InboxMessage/MessagesBuilderUi.d.ts.map +1 -0
- package/lib/components/InboxMessage/Popover.d.ts +3 -0
- package/lib/components/InboxMessage/Popover.d.ts.map +1 -0
- package/lib/components/InboxMessage/Popover.js +31 -0
- package/lib/components/InboxMessage/Popover.js.map +1 -0
- package/lib/components/InboxMessage/RightSidebar.d.ts +9 -0
- package/lib/components/InboxMessage/RightSidebar.d.ts.map +1 -0
- package/lib/components/InboxMessage/RightSidebar.js +9 -0
- package/lib/components/InboxMessage/RightSidebar.js.map +1 -0
- package/lib/components/InboxMessage/RightSidebarAi.d.ts +37 -0
- package/lib/components/InboxMessage/RightSidebarAi.d.ts.map +1 -0
- package/lib/components/InboxMessage/RightSidebarAi.js +9 -0
- package/lib/components/InboxMessage/RightSidebarAi.js.map +1 -0
- package/lib/components/InboxMessage/ServiceConversationItem.d.ts +12 -0
- package/lib/components/InboxMessage/ServiceConversationItem.d.ts.map +1 -0
- package/lib/components/InboxMessage/ServiceConversationItem.js +185 -0
- package/lib/components/InboxMessage/ServiceConversationItem.js.map +1 -0
- package/lib/components/InboxMessage/ServiceInboxItem.d.ts +12 -0
- package/lib/components/InboxMessage/ServiceInboxItem.d.ts.map +1 -0
- package/lib/components/InboxMessage/ServiceInboxItem.js +182 -0
- package/lib/components/InboxMessage/ServiceInboxItem.js.map +1 -0
- package/lib/components/InboxMessage/StreamingMessageBubble.d.ts +18 -0
- package/lib/components/InboxMessage/StreamingMessageBubble.d.ts.map +1 -0
- package/lib/components/InboxMessage/SubscriptionHandler.d.ts +19 -0
- package/lib/components/InboxMessage/SubscriptionHandler.d.ts.map +1 -0
- package/lib/components/InboxMessage/SubscriptionHandler.js +41 -0
- package/lib/components/InboxMessage/SubscriptionHandler.js.map +1 -0
- package/lib/components/InboxMessage/TypingIndicator.d.ts +11 -0
- package/lib/components/InboxMessage/TypingIndicator.d.ts.map +1 -0
- package/lib/components/InboxMessage/UploadImageButton.d.ts +7 -0
- package/lib/components/InboxMessage/UploadImageButton.d.ts.map +1 -0
- package/lib/components/InboxMessage/UploadImageButton.js +30 -0
- package/lib/components/InboxMessage/UploadImageButton.js.map +1 -0
- package/lib/components/InboxMessage/UserModalContent.d.ts +3 -0
- package/lib/components/InboxMessage/UserModalContent.d.ts.map +1 -0
- package/lib/components/InboxMessage/UserModalContent.js +60 -0
- package/lib/components/InboxMessage/UserModalContent.js.map +1 -0
- package/lib/components/InboxMessage/index.d.ts +19 -0
- package/lib/components/InboxMessage/index.d.ts.map +1 -0
- package/lib/components/InboxMessage/message-widgets/CommonMessage.d.ts +11 -0
- package/lib/components/InboxMessage/message-widgets/CommonMessage.d.ts.map +1 -0
- package/lib/components/InboxMessage/message-widgets/CommonMessage.js +44 -0
- package/lib/components/InboxMessage/message-widgets/CommonMessage.js.map +1 -0
- package/lib/components/InboxMessage/message-widgets/ErrorFixCard.d.ts +10 -0
- package/lib/components/InboxMessage/message-widgets/ErrorFixCard.d.ts.map +1 -0
- package/lib/components/InboxMessage/message-widgets/ErrorFixCard.js +194 -0
- package/lib/components/InboxMessage/message-widgets/ErrorFixCard.js.map +1 -0
- package/lib/components/InboxMessage/message-widgets/MessageCard.d.ts +8 -0
- package/lib/components/InboxMessage/message-widgets/MessageCard.d.ts.map +1 -0
- package/lib/components/InboxMessage/message-widgets/MessageSliceRenderer.d.ts +12 -0
- package/lib/components/InboxMessage/message-widgets/MessageSliceRenderer.d.ts.map +1 -0
- package/lib/components/InboxMessage/message-widgets/MessageSliceRenderer.js +37 -0
- package/lib/components/InboxMessage/message-widgets/MessageSliceRenderer.js.map +1 -0
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts +42 -0
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.d.ts.map +1 -0
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js +1339 -0
- package/lib/components/InboxMessage/message-widgets/ModernMessageGroup.js.map +1 -0
- package/lib/components/InboxMessage/message-widgets/PlainMessage.d.ts +8 -0
- package/lib/components/InboxMessage/message-widgets/PlainMessage.d.ts.map +1 -0
- package/lib/components/InboxMessage/message-widgets/PlainMessage.js +14 -0
- package/lib/components/InboxMessage/message-widgets/PlainMessage.js.map +1 -0
- package/lib/components/InboxMessage/message-widgets/PropertyMessageWidget.d.ts +9 -0
- package/lib/components/InboxMessage/message-widgets/PropertyMessageWidget.d.ts.map +1 -0
- package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.d.ts +14 -0
- package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.d.ts.map +1 -0
- package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.js +333 -0
- package/lib/components/InboxMessage/message-widgets/SlackLikeMessageGroup.js.map +1 -0
- package/lib/components/InboxMessage/message-widgets/index.d.ts +4 -0
- package/lib/components/InboxMessage/message-widgets/index.d.ts.map +1 -0
- package/lib/components/ModelConfigPanel.d.ts +74 -0
- package/lib/components/ModelConfigPanel.d.ts.map +1 -0
- package/lib/components/ModelConfigPanel.js +1152 -0
- package/lib/components/ModelConfigPanel.js.map +1 -0
- package/lib/components/filler-components/RightSiderBar.d.ts +3 -0
- package/lib/components/filler-components/RightSiderBar.d.ts.map +1 -0
- package/lib/components/filler-components/RightSiderBar.js +532 -0
- package/lib/components/filler-components/RightSiderBar.js.map +1 -0
- package/lib/components/inbox/FilesList.d.ts +20 -0
- package/lib/components/inbox/FilesList.d.ts.map +1 -0
- package/lib/components/inbox/FilesList.js +68 -0
- package/lib/components/inbox/FilesList.js.map +1 -0
- package/lib/components/inbox/MessageItem.d.ts +17 -0
- package/lib/components/inbox/MessageItem.d.ts.map +1 -0
- package/lib/components/inbox/MessageItem.js +50 -0
- package/lib/components/inbox/MessageItem.js.map +1 -0
- package/lib/components/inbox/ThreadItem.d.ts +11 -0
- package/lib/components/inbox/ThreadItem.d.ts.map +1 -0
- package/lib/components/inbox/ThreadItem.js +147 -0
- package/lib/components/inbox/ThreadItem.js.map +1 -0
- package/lib/components/inbox/index.d.ts +4 -0
- package/lib/components/inbox/index.d.ts.map +1 -0
- package/lib/components/index.d.ts +10 -0
- package/lib/components/index.d.ts.map +1 -0
- package/lib/components/live-code-editor/hybrid-live-editor.d.ts +20 -0
- package/lib/components/live-code-editor/hybrid-live-editor.d.ts.map +1 -0
- package/lib/components/live-code-editor/index.d.ts +4 -0
- package/lib/components/live-code-editor/index.d.ts.map +1 -0
- package/lib/components/live-code-editor/live-code-editor.d.ts +14 -0
- package/lib/components/live-code-editor/live-code-editor.d.ts.map +1 -0
- package/lib/components/messages-container-ui/MessagesContainerUI.d.ts +81 -0
- package/lib/components/messages-container-ui/MessagesContainerUI.d.ts.map +1 -0
- package/lib/components/messages-container-ui/MessagesContainerUI.js +77 -0
- package/lib/components/messages-container-ui/MessagesContainerUI.js.map +1 -0
- package/lib/components/messages-container-ui/PlanModeView.d.ts +82 -0
- package/lib/components/messages-container-ui/PlanModeView.d.ts.map +1 -0
- package/lib/components/messages-container-ui/PlanModeView.js +267 -0
- package/lib/components/messages-container-ui/PlanModeView.js.map +1 -0
- package/lib/components/messages-container-ui/index.d.ts +6 -0
- package/lib/components/messages-container-ui/index.d.ts.map +1 -0
- package/lib/components/messages-container-ui/types.d.ts +38 -0
- package/lib/components/messages-container-ui/types.d.ts.map +1 -0
- package/lib/components/slot-fill/chat-message-filler.d.ts +4 -0
- package/lib/components/slot-fill/chat-message-filler.d.ts.map +1 -0
- package/lib/components/slot-fill/chat-message-filler.js +5 -0
- package/lib/components/slot-fill/chat-message-filler.js.map +1 -0
- package/lib/components/slot-fill/chat-message-slot.d.ts +11 -0
- package/lib/components/slot-fill/chat-message-slot.d.ts.map +1 -0
- package/lib/components/slot-fill/chat-message-slot.js +6 -0
- package/lib/components/slot-fill/chat-message-slot.js.map +1 -0
- package/lib/components/slot-fill/index.d.ts +4 -0
- package/lib/components/slot-fill/index.d.ts.map +1 -0
- package/lib/components/slot-fill/right-sidebar-filler.d.ts +4 -0
- package/lib/components/slot-fill/right-sidebar-filler.d.ts.map +1 -0
- package/lib/components/slot-fill/right-sidebar-filler.js +13 -0
- package/lib/components/slot-fill/right-sidebar-filler.js.map +1 -0
- package/lib/components/ui/button.d.ts +9 -0
- package/lib/components/ui/button.d.ts.map +1 -0
- package/lib/compute.d.ts +8 -0
- package/lib/compute.d.ts.map +1 -0
- package/lib/compute.js +264 -0
- package/lib/compute.js.map +1 -0
- package/lib/config/env-config.d.ts +20 -0
- package/lib/config/env-config.d.ts.map +1 -0
- package/lib/config/env-config.js +55 -0
- package/lib/config/env-config.js.map +1 -0
- package/lib/config/index.d.ts +2 -0
- package/lib/config/index.d.ts.map +1 -0
- package/lib/constants/breakpoints.d.ts +8 -0
- package/lib/constants/breakpoints.d.ts.map +1 -0
- package/lib/constants/index.d.ts +3 -0
- package/lib/constants/index.d.ts.map +1 -0
- package/lib/container/AiInbox.d.ts +15 -0
- package/lib/container/AiInbox.d.ts.map +1 -0
- package/lib/container/AiInboxWithLoader.d.ts +36 -0
- package/lib/container/AiInboxWithLoader.d.ts.map +1 -0
- package/lib/container/AiLandingInput.d.ts +27 -0
- package/lib/container/AiLandingInput.d.ts.map +1 -0
- package/lib/container/AiLandingInput.js +149 -0
- package/lib/container/AiLandingInput.js.map +1 -0
- package/lib/container/Inbox.d.ts +15 -0
- package/lib/container/Inbox.d.ts.map +1 -0
- package/lib/container/Inbox.js +964 -0
- package/lib/container/Inbox.js.map +1 -0
- package/lib/container/InboxAiMessagesLoader.d.ts +45 -0
- package/lib/container/InboxAiMessagesLoader.d.ts.map +1 -0
- package/lib/container/InboxAiMessagesLoader.js +80 -0
- package/lib/container/InboxAiMessagesLoader.js.map +1 -0
- package/lib/container/InboxContainer.d.ts +41 -0
- package/lib/container/InboxContainer.d.ts.map +1 -0
- package/lib/container/InboxContainer.js +27 -0
- package/lib/container/InboxContainer.js.map +1 -0
- package/lib/container/InboxTemplate1.d.ts +15 -0
- package/lib/container/InboxTemplate1.d.ts.map +1 -0
- package/lib/container/InboxTemplate1WithLoader.d.ts +36 -0
- package/lib/container/InboxTemplate1WithLoader.d.ts.map +1 -0
- package/lib/container/InboxTemplate2.d.ts +15 -0
- package/lib/container/InboxTemplate2.d.ts.map +1 -0
- package/lib/container/InboxWithAiLoader.d.ts +47 -0
- package/lib/container/InboxWithAiLoader.d.ts.map +1 -0
- package/lib/container/InboxWithAiLoader.js +118 -0
- package/lib/container/InboxWithAiLoader.js.map +1 -0
- package/lib/container/InboxWithLoader.d.ts +36 -0
- package/lib/container/InboxWithLoader.d.ts.map +1 -0
- package/lib/container/InboxWithLoader.js +277 -0
- package/lib/container/InboxWithLoader.js.map +1 -0
- package/lib/container/ServiceInbox.d.ts +9 -0
- package/lib/container/ServiceInbox.d.ts.map +1 -0
- package/lib/container/ServiceInbox.js +141 -0
- package/lib/container/ServiceInbox.js.map +1 -0
- package/lib/container/TestInboxWithAiLoader.d.ts +7 -0
- package/lib/container/TestInboxWithAiLoader.d.ts.map +1 -0
- package/lib/container/TestInboxWithAiLoader.js +135 -0
- package/lib/container/TestInboxWithAiLoader.js.map +1 -0
- package/lib/container/ThreadMessages.d.ts +13 -0
- package/lib/container/ThreadMessages.d.ts.map +1 -0
- package/lib/container/ThreadMessages.js +320 -0
- package/lib/container/ThreadMessages.js.map +1 -0
- package/lib/container/ThreadMessagesInbox.d.ts +14 -0
- package/lib/container/ThreadMessagesInbox.d.ts.map +1 -0
- package/lib/container/ThreadMessagesInbox.js +347 -0
- package/lib/container/ThreadMessagesInbox.js.map +1 -0
- package/lib/container/Threads.d.ts +8 -0
- package/lib/container/Threads.d.ts.map +1 -0
- package/lib/container/Threads.js +231 -0
- package/lib/container/Threads.js.map +1 -0
- package/lib/container/ThreadsInbox.d.ts +21 -0
- package/lib/container/ThreadsInbox.d.ts.map +1 -0
- package/lib/container/ThreadsInbox.js +243 -0
- package/lib/container/ThreadsInbox.js.map +1 -0
- package/lib/container/apply-footer-styles.d.ts +2 -0
- package/lib/container/apply-footer-styles.d.ts.map +1 -0
- package/lib/container/apply-footer-styles.js +16 -0
- package/lib/container/apply-footer-styles.js.map +1 -0
- package/lib/container/index.d.ts +13 -0
- package/lib/container/index.d.ts.map +1 -0
- package/lib/enums/index.d.ts +2 -0
- package/lib/enums/index.d.ts.map +1 -0
- package/lib/enums/messenger-slot-fill-name-enum.d.ts +11 -0
- package/lib/enums/messenger-slot-fill-name-enum.d.ts.map +1 -0
- package/lib/enums/messenger-slot-fill-name-enum.js +11 -0
- package/lib/enums/messenger-slot-fill-name-enum.js.map +1 -0
- package/lib/hooks/index.d.ts +4 -0
- package/lib/hooks/index.d.ts.map +1 -0
- package/lib/hooks/usePersistentModelConfig.d.ts +33 -0
- package/lib/hooks/usePersistentModelConfig.d.ts.map +1 -0
- package/lib/hooks/usePersistentModelConfig.js +123 -0
- package/lib/hooks/usePersistentModelConfig.js.map +1 -0
- package/lib/hooks/useStreamAssembler.d.ts +8 -0
- package/lib/hooks/useStreamAssembler.d.ts.map +1 -0
- package/lib/hooks/useTemplates.d.ts +14 -0
- package/lib/hooks/useTemplates.d.ts.map +1 -0
- package/lib/hooks/useTemplates.js +59 -0
- package/lib/hooks/useTemplates.js.map +1 -0
- package/lib/index.d.ts +14 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -0
- package/lib/interfaces/index.d.ts +2 -0
- package/lib/interfaces/index.d.ts.map +1 -0
- package/lib/interfaces/message-widgets.interface.d.ts +21 -0
- package/lib/interfaces/message-widgets.interface.d.ts.map +1 -0
- package/lib/machines/aiAgentMachine.d.ts +3 -0
- package/lib/machines/aiAgentMachine.d.ts.map +1 -0
- package/lib/machines/aiAgentMachine.js +1083 -0
- package/lib/machines/aiAgentMachine.js.map +1 -0
- package/lib/machines/aiAgentMachine.simple.d.ts +3 -0
- package/lib/machines/aiAgentMachine.simple.d.ts.map +1 -0
- package/lib/machines/aiAgentMachine.simple.js +108 -0
- package/lib/machines/aiAgentMachine.simple.js.map +1 -0
- package/lib/machines/index.d.ts +3 -0
- package/lib/machines/index.d.ts.map +1 -0
- package/lib/machines/types.d.ts +77 -0
- package/lib/machines/types.d.ts.map +1 -0
- package/lib/module.d.ts +7 -0
- package/lib/module.d.ts.map +1 -0
- package/lib/module.js +26 -0
- package/lib/module.js.map +1 -0
- package/lib/routes.json +251 -0
- package/lib/styles/responsive.css +76 -0
- package/lib/templates/InboxWithAi.d.ts +44 -0
- package/lib/templates/InboxWithAi.d.ts.map +1 -0
- package/lib/templates/InboxWithAi.js +651 -0
- package/lib/templates/InboxWithAi.js.map +1 -0
- package/lib/templates/InboxWithAi.tsx +844 -0
- package/lib/templates/index.d.ts +2 -0
- package/lib/templates/index.d.ts.map +1 -0
- package/lib/templates/index.ts +1 -0
- package/lib/types/templates.d.ts +35 -0
- package/lib/types/templates.d.ts.map +1 -0
- package/lib/utils/utils.d.ts +2 -0
- package/lib/utils/utils.d.ts.map +1 -0
- package/lib/xstate/index.d.ts +3 -0
- package/lib/xstate/index.d.ts.map +1 -0
- package/lib/xstate/rightSidebar.machine.d.ts +4 -0
- package/lib/xstate/rightSidebar.machine.d.ts.map +1 -0
- package/lib/xstate/rightSidebar.types.d.ts +57 -0
- package/lib/xstate/rightSidebar.types.d.ts.map +1 -0
- package/package.json +69 -0
- package/rollup.config.mjs +47 -0
- package/src/cdm-locales/en/translations.json +31 -0
- package/src/cdm-locales/es/translations.json +31 -0
- package/src/components/AIAgent/AIAgent.tsx +1468 -0
- package/src/components/AIAgent/AIAgent.tsx.bk +1365 -0
- package/src/components/AIAgent/InputComponent.tsx +608 -0
- package/src/components/AIAgent/README.md +174 -0
- package/src/components/AIAgent/index.ts +1 -0
- package/src/components/InboxMessage/CommonMessage.tsx +40 -0
- package/src/components/InboxMessage/ConversationItem.tsx +255 -0
- package/src/components/InboxMessage/InputComponent.tsx +198 -0
- package/src/components/InboxMessage/LeftSidebar.tsx +140 -0
- package/src/components/InboxMessage/MessageInput.tsx +209 -0
- package/src/components/InboxMessage/MessageInputComponent.tsx +245 -0
- package/src/components/InboxMessage/Messages.tsx +137 -0
- package/src/components/InboxMessage/MessagesBuilderUi.tsx +205 -0
- package/src/components/InboxMessage/Popover.tsx +42 -0
- package/src/components/InboxMessage/RightSidebar.tsx +22 -0
- package/src/components/InboxMessage/RightSidebarAi.tsx +47 -0
- package/src/components/InboxMessage/ServiceConversationItem.tsx +234 -0
- package/src/components/InboxMessage/ServiceInboxItem.tsx +223 -0
- package/src/components/InboxMessage/StreamingMessageBubble.tsx +270 -0
- package/src/components/InboxMessage/SubscriptionHandler.tsx +55 -0
- package/src/components/InboxMessage/TypingIndicator.tsx +38 -0
- package/src/components/InboxMessage/UploadImageButton.tsx +46 -0
- package/src/components/InboxMessage/UserModalContent.tsx +60 -0
- package/src/components/InboxMessage/index.ts +18 -0
- package/src/components/InboxMessage/message-widgets/CommonMessage.tsx +69 -0
- package/src/components/InboxMessage/message-widgets/ErrorFixCard.tsx +239 -0
- package/src/components/InboxMessage/message-widgets/MessageCard.tsx +127 -0
- package/src/components/InboxMessage/message-widgets/MessageSliceRenderer.tsx +40 -0
- package/src/components/InboxMessage/message-widgets/ModernMessageGroup.tsx +1733 -0
- package/src/components/InboxMessage/message-widgets/PlainMessage.tsx +18 -0
- package/src/components/InboxMessage/message-widgets/PropertyMessageWidget.tsx +29 -0
- package/src/components/InboxMessage/message-widgets/SlackLikeMessageGroup.tsx +492 -0
- package/src/components/InboxMessage/message-widgets/index.ts +8 -0
- package/src/components/ModelConfigPanel.tsx +1357 -0
- package/src/components/filler-components/RightSiderBar.tsx +572 -0
- package/src/components/inbox/FilesList.tsx +89 -0
- package/src/components/inbox/MessageItem.tsx +50 -0
- package/src/components/inbox/ThreadItem.tsx +295 -0
- package/src/components/inbox/index.ts +3 -0
- package/src/components/index.ts +29 -0
- package/src/components/live-code-editor/hybrid-live-editor.tsx +105 -0
- package/src/components/live-code-editor/index.ts +3 -0
- package/src/components/live-code-editor/live-code-editor.tsx +257 -0
- package/src/components/messages-container-ui/MessagesContainerUI.tsx +151 -0
- package/src/components/messages-container-ui/PlanModeView.tsx +426 -0
- package/src/components/messages-container-ui/README.md +91 -0
- package/src/components/messages-container-ui/index.ts +5 -0
- package/src/components/messages-container-ui/types.ts +40 -0
- package/src/components/slot-fill/chat-message-filler.tsx +18 -0
- package/src/components/slot-fill/chat-message-slot.tsx +18 -0
- package/src/components/slot-fill/index.ts +3 -0
- package/src/components/slot-fill/right-sidebar-filler.tsx +48 -0
- package/src/components/ui/button.tsx +32 -0
- package/src/compute.ts +271 -0
- package/src/config/env-config.ts +24 -0
- package/src/config/index.ts +1 -0
- package/src/constants/breakpoints.ts +7 -0
- package/src/constants/index.ts +5 -0
- package/src/container/AiInbox.tsx +1879 -0
- package/src/container/AiInboxWithLoader.tsx +356 -0
- package/src/container/AiLandingInput.tsx +200 -0
- package/src/container/Inbox.tsx +1095 -0
- package/src/container/InboxAiMessagesLoader.tsx +129 -0
- package/src/container/InboxContainer.tsx +61 -0
- package/src/container/InboxTemplate1.tsx +1553 -0
- package/src/container/InboxTemplate1WithLoader.tsx +338 -0
- package/src/container/InboxTemplate2.tsx +1617 -0
- package/src/container/InboxWithAiLoader.tsx +177 -0
- package/src/container/InboxWithLoader.tsx +341 -0
- package/src/container/ServiceInbox.tsx +188 -0
- package/src/container/TestInboxWithAiLoader.tsx +147 -0
- package/src/container/ThreadMessages.tsx +378 -0
- package/src/container/ThreadMessagesInbox.tsx +457 -0
- package/src/container/Threads.tsx +270 -0
- package/src/container/ThreadsInbox.tsx +351 -0
- package/src/container/apply-footer-styles.ts +17 -0
- package/src/container/index.ts +31 -0
- package/src/enums/index.ts +1 -0
- package/src/enums/messenger-slot-fill-name-enum.ts +10 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/usePersistentModelConfig.ts +166 -0
- package/src/hooks/useStreamAssembler.ts +7 -0
- package/src/hooks/useTemplates.ts +75 -0
- package/src/index.ts +49 -0
- package/src/interfaces/index.ts +1 -0
- package/src/interfaces/message-widgets.interface.ts +21 -0
- package/src/machines/aiAgentMachine.simple.ts +89 -0
- package/src/machines/aiAgentMachine.ts +1296 -0
- package/src/machines/aiAgentMachine.ts.bk +1296 -0
- package/src/machines/index.ts +2 -0
- package/src/machines/types.ts +59 -0
- package/src/module.tsx +32 -0
- package/src/styles/responsive.css +76 -0
- package/src/templates/InboxWithAi.tsx +844 -0
- package/src/templates/index.ts +1 -0
- package/src/types/templates.ts +35 -0
- package/src/utils/utils.ts +3 -0
- package/src/xstate/index.ts +2 -0
- package/src/xstate/rightSidebar.machine.ts +304 -0
- package/src/xstate/rightSidebar.types.ts +58 -0
- package/tsconfig.json +14 -0
- 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
|
+
};
|