diff --git a/apps/server/agents/handle_agent_errors.py b/apps/server/agents/handle_agent_errors.py index 00761a9b4..b314c5cf9 100644 --- a/apps/server/agents/handle_agent_errors.py +++ b/apps/server/agents/handle_agent_errors.py @@ -12,9 +12,7 @@ def handle_agent_error(err: Exception) -> str: if isinstance(err, RateLimitError): return "OpenAI reached it's rate limit, please check billing on OpenAI" elif isinstance(err, AuthenticationError): - return ( - "Your OpenAI API key is invalid. Please recheck it in [Settings](/settings)" - ) + return "Your OpenAI API key is invalid. Please recheck it in [Settings](/integrations?setting=openai)" elif isinstance(err, TimeoutError): return "OpenAI timed out, please try again later" elif isinstance(err, ServiceUnavailableError): diff --git a/apps/server/utils/model.py b/apps/server/utils/model.py index 35bc3055f..4acc4a40f 100644 --- a/apps/server/utils/model.py +++ b/apps/server/utils/model.py @@ -123,7 +123,7 @@ def get_llm( if provider == ModelProviders.OPEN_AI: if not settings.openai_api_key: raise InvalidLLMApiKeyException( - "Please set OpenAI API Key in [Settings](/settings)" + "Please set OpenAI API Key in [Settings](/integrations?setting=openai)" ) return ChatOpenAI( @@ -134,7 +134,7 @@ def get_llm( elif provider == ModelProviders.HUGGING_FACE: if not settings.hugging_face_access_token: raise InvalidLLMApiKeyException( - "Please set Hugging Face Access Token in [Settings](/settings)" + "Please set Hugging Face Access Token in [Settings](/integrations?setting=huggingface)" ) return HuggingFaceHub( @@ -147,7 +147,7 @@ def get_llm( elif provider == ModelProviders.REPLICATE: if not settings.replicate_api_token: raise InvalidLLMApiKeyException( - "Please set Replicate API Token in [Settings](/settings)" + "Please set Replicate API Token in [Settings](/integrations?setting=replicate)" ) os.environ["REPLICATE_API_TOKEN"] = settings.replicate_api_token diff --git a/apps/ui/src/Route.tsx b/apps/ui/src/Route.tsx index 3b648a6ce..51f62bcff 100644 --- a/apps/ui/src/Route.tsx +++ b/apps/ui/src/Route.tsx @@ -107,6 +107,7 @@ import EditCampaignModal from 'modals/EditCampaignModal' import CreateScheduleModal from 'modals/CreateScheduleModal' import EditScheduleModal from 'modals/EditScheduleModal' import VoiceOptionsModal from 'modals/VoiceOptionsModal' +import LlmSettingsModal from 'modals/LlmSettingsModal' const Route = () => { const { loading } = useContext(AuthContext) @@ -432,6 +433,7 @@ const Route = () => { + diff --git a/apps/ui/src/assets/settings/hf-logo.png b/apps/ui/src/assets/settings/hf-logo.png new file mode 100644 index 000000000..49e2841dd Binary files /dev/null and b/apps/ui/src/assets/settings/hf-logo.png differ diff --git a/apps/ui/src/assets/settings/openAi-logo.png b/apps/ui/src/assets/settings/openAi-logo.png new file mode 100644 index 000000000..fc71f647c Binary files /dev/null and b/apps/ui/src/assets/settings/openAi-logo.png differ diff --git a/apps/ui/src/assets/settings/pinecone-logo.png b/apps/ui/src/assets/settings/pinecone-logo.png new file mode 100644 index 000000000..ac397b4aa Binary files /dev/null and b/apps/ui/src/assets/settings/pinecone-logo.png differ diff --git a/apps/ui/src/assets/settings/replicate-logo.png b/apps/ui/src/assets/settings/replicate-logo.png new file mode 100644 index 000000000..9fa534b32 Binary files /dev/null and b/apps/ui/src/assets/settings/replicate-logo.png differ diff --git a/apps/ui/src/assets/settings/weaviate-logo.jpg b/apps/ui/src/assets/settings/weaviate-logo.jpg new file mode 100644 index 000000000..2316ea9e1 Binary files /dev/null and b/apps/ui/src/assets/settings/weaviate-logo.jpg differ diff --git a/apps/ui/src/components/HeaderButtons/HeaderButtons.tsx b/apps/ui/src/components/HeaderButtons/HeaderButtons.tsx index 8a1496620..56d114726 100644 --- a/apps/ui/src/components/HeaderButtons/HeaderButtons.tsx +++ b/apps/ui/src/components/HeaderButtons/HeaderButtons.tsx @@ -49,7 +49,7 @@ const HeaderButtons = () => { )} - {t('docs')}} position={Tooltip.positions.BOTTOM} tooltipSize={Tooltip.tooltipSize.Small} @@ -66,7 +66,7 @@ const HeaderButtons = () => { /> - + */} {/* Twitter} @@ -101,7 +101,7 @@ const HeaderButtons = () => { */} - {isLinkModule && ( + {/* {isLinkModule && ( {t('github')}} position={Tooltip.positions.BOTTOM} @@ -125,7 +125,7 @@ const HeaderButtons = () => { - )} + )} */} ) } @@ -135,6 +135,7 @@ export default HeaderButtons const StyledButtonsWrapper = styled.div` display: flex; align-items: center; + gap: 5px; ` export const StyledImg = styled.img<{ customScale?: number }>` diff --git a/apps/ui/src/modals/AIChatModal/components/ChatMessageList/components/AiMessageMarkdown.tsx b/apps/ui/src/modals/AIChatModal/components/ChatMessageList/components/AiMessageMarkdown.tsx index 10f521337..6b7ee1de2 100644 --- a/apps/ui/src/modals/AIChatModal/components/ChatMessageList/components/AiMessageMarkdown.tsx +++ b/apps/ui/src/modals/AIChatModal/components/ChatMessageList/components/AiMessageMarkdown.tsx @@ -5,12 +5,14 @@ import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism' import remarkGfm from 'remark-gfm' import { useModal } from 'hooks' import { memo } from 'react' +import { t } from 'i18next' const YOUTUBE_REGEX = /^https:\/\/www\.youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)&/ const IMAGE_REGEX = /\.(gif|jpe?g|tiff?|png|webp|bmp)$/i -const SETTINGS_REGEX = /\/setting/ +// const SETTINGS_REGEX = /\/setting/ const TOOLKIT_REGEX = /\/toolkits\/[^/]+/ const VOICE_REGEX = /\/integrations\/voice\/[^/]+/ +const SETTINGS_REGEX = /\/integrations\?setting=([^/]+)/ // const SETTINGS_REGEX = /\[Settings\]\(\/settings\)/ const AiMessageMarkdown = ({ isReply = false, children }: { isReply?: boolean; children: any }) => { @@ -52,9 +54,17 @@ const AiMessageMarkdown = ({ isReply = false, children }: { isReply?: boolean; c } if (SETTINGS_REGEX.test(href as string)) { + const regex = /\/integrations\?setting=([^/]+)/ + const match: any = regex.exec(href || '') + const slug = match[1] + return ( - ) } diff --git a/apps/ui/src/modals/LlmSettingsModal.tsx b/apps/ui/src/modals/LlmSettingsModal.tsx new file mode 100644 index 000000000..8cc208b2b --- /dev/null +++ b/apps/ui/src/modals/LlmSettingsModal.tsx @@ -0,0 +1,35 @@ +import withRenderModal from 'hocs/withRenderModal' + +import Modal from 'share-ui/components/Modal/Modal' +import { useModal } from 'hooks' + +import ToolView from 'pages/Toolkit/ToolView' +import { useToolView } from 'pages/Toolkit/ToolView/useToolView' +import { StyledModalBody } from './ToolkitModal' +import SettingView from 'pages/Settings/SettingView' + +type LlmSettingsModalProps = { + data: { + settingsSlug: string + } +} + +const LlmSettingsModal = ({ data }: LlmSettingsModalProps) => { + const { closeModal } = useModal() + + const { configsData } = useToolView({}) + + if (!configsData) return
+ + return ( + <> + closeModal('llm-settings-modal')} show backgroundColor='light'> + + + + + + ) +} + +export default withRenderModal('llm-settings-modal')(LlmSettingsModal) diff --git a/apps/ui/src/pages/Integrations/Integrations.tsx b/apps/ui/src/pages/Integrations/Integrations.tsx index 88afd0557..fa06f4d17 100644 --- a/apps/ui/src/pages/Integrations/Integrations.tsx +++ b/apps/ui/src/pages/Integrations/Integrations.tsx @@ -32,6 +32,10 @@ import ToolView from 'pages/Toolkit/ToolView' import VoiceView from 'plugins/contact/pages/Voice/VoiceView' import { useToolView } from 'pages/Toolkit/ToolView/useToolView' +import SettingView from 'pages/Settings/SettingView' +import { SETTINGS_FIELDS } from 'pages/Settings/SettingView/useSettingView' +import { settingLogos } from 'pages/Settings/constants' + const Integrations = () => { const { getIntegrationModules } = useGetAccountModule() @@ -63,6 +67,7 @@ const Integrations = () => { const urlParams = new URLSearchParams(location.search) const toolQuery = urlParams.get('tool') || '' const voiceQuery = urlParams.get('voice') || '' + const settingQuery = urlParams.get('setting') || '' const [activeTab, setActiveTab] = useState(0) const handleTabClick = (tabId: number) => { @@ -76,7 +81,8 @@ const Integrations = () => { const { data: voiceTools } = useVoicesService() useEffect(() => { - navigate(`/integrations?tool=${tools?.[0]?.slug}`) + if (!toolQuery && !voiceQuery && !settingQuery) + navigate(`/integrations?tool=${tools?.[0]?.slug}`) }, [tools]) let isSettingsHidden = false @@ -94,13 +100,19 @@ const Integrations = () => { const handlePickTool = (slug: string) => { navigate(`/integrations?tool=${slug}`) - setActiveTab(0) } const handlePickVoice = (slug: string) => { navigate(`/integrations?voice=${slug}`) setActiveTab(0) } + const handlePickSetting = (slug: string) => { + navigate(`/integrations?setting=${slug}`) + setActiveTab(0) + } + + const llmSettings = SETTINGS_FIELDS?.filter((setting: any) => setting.group === 'llm') + const vectorDbSettings = SETTINGS_FIELDS?.filter((setting: any) => setting.group === 'vectorDb') return ( <> @@ -149,6 +161,50 @@ const Integrations = () => { /> ) })} + + + + + + {llmSettings?.map((setting: any, index: number) => { + const filteredLogos = settingLogos.filter( + (toolLogo: any) => toolLogo.settingName === setting.title, + ) + + const logoSrc = filteredLogos?.[0]?.logoSrc || '' + + return ( + handlePickSetting(setting.slug)} + name={setting.title} + logo={logoSrc} + picked={settingQuery === setting.slug} + /> + ) + })} + + + + + + {vectorDbSettings?.map((setting: any, index: number) => { + const filteredLogos = settingLogos.filter( + (toolLogo: any) => toolLogo.settingName === setting.title, + ) + + const logoSrc = filteredLogos?.[0]?.logoSrc || '' + + return ( + handlePickSetting(setting.slug)} + name={setting.title} + logo={logoSrc} + picked={settingQuery === setting.slug} + /> + ) + })} @@ -166,10 +222,12 @@ const Integrations = () => { {toolQuery && } {voiceQuery && } + {settingQuery && } {toolQuery && } {voiceQuery && } + {settingQuery && } diff --git a/apps/ui/src/pages/Settings/SettingView/SettingView.tsx b/apps/ui/src/pages/Settings/SettingView/SettingView.tsx new file mode 100644 index 000000000..2848b39cd --- /dev/null +++ b/apps/ui/src/pages/Settings/SettingView/SettingView.tsx @@ -0,0 +1,113 @@ +import FormikTextField from 'components/TextFieldFormik' +import { SETTINGS_FIELDS, useSettingView } from './useSettingView' +import { FormikProvider } from 'formik' +import { StyledSectionWrapper } from 'pages/Home/homeStyle.css' +import { + StyledButtonWrapper, + StyledFieldsWrapper, + StyledImg, + StyledInnerWrapper, + StyledMainTextWrapper, + StyledTextWrapper, +} from 'pages/Toolkit/ToolView/ToolView' +import { ButtonPrimary } from 'components/Button/Button' +import Button from 'share-ui/components/Button/Button' +import Loader from 'share-ui/components/Loader/Loader' +import { t } from 'i18next' +import { settingLogos } from '../constants' +import TypographySecondary from 'components/Typography/Secondary' +import Typography from 'share-ui/components/typography/Typography' +import TypographyPrimary from 'components/Typography/Primary' +import { useModal } from 'hooks' + +const SettingView = ({ + settingSlug, + hideInfo, + hideForm, +}: { + settingSlug: string + hideInfo?: boolean + hideForm?: boolean +}) => { + const { closeModal } = useModal() + + const fields = SETTINGS_FIELDS?.find((setting: any) => setting.slug === settingSlug)?.configs + const name = SETTINGS_FIELDS?.find((setting: any) => setting.slug === settingSlug)?.title + const { formik, isLoading, handleSubmit } = useSettingView({ fields }) + + const filteredLogos = settingLogos.find((setting: any) => setting.settingName === name) + const logoSrc = filteredLogos?.logoSrc || '' + + const onHandleSubmit = async () => { + await handleSubmit(formik?.values) + closeModal('llm-settings-modal') + } + return ( + + + + {!hideInfo && ( + <> + + + + + + + + + {/* */} + + + )} + + {!hideForm && ( + + {fields?.map((field: any) => { + return ( + + ) + })} + + )} + + {!hideForm && ( + + + {isLoading ? : t('save')} + + + )} + + + + ) +} + +export default SettingView diff --git a/apps/ui/src/pages/Settings/SettingView/index.ts b/apps/ui/src/pages/Settings/SettingView/index.ts new file mode 100644 index 000000000..e7aad2923 --- /dev/null +++ b/apps/ui/src/pages/Settings/SettingView/index.ts @@ -0,0 +1 @@ +export { default } from './SettingView' diff --git a/apps/ui/src/pages/Settings/SettingView/useSettingView.ts b/apps/ui/src/pages/Settings/SettingView/useSettingView.ts new file mode 100644 index 000000000..9e87fd900 --- /dev/null +++ b/apps/ui/src/pages/Settings/SettingView/useSettingView.ts @@ -0,0 +1,146 @@ +import { AuthContext, ToastContext } from 'contexts' +import { useFormik } from 'formik' +import { useContext, useState } from 'react' + +import { useConfigsService } from 'services/config/useConfigsService' +import { useCreateConfigService } from 'services/config/useCreateConfigService' +import { useUpdateConfigService } from 'services/config/useUpdateConfigService' + +export const SETTINGS_FIELDS = [ + { + title: 'OpenAI', + slug: 'openai', + group: 'llm', + configs: [ + { + key: 'open_api_key', + label: 'OpenAI API key', + }, + ], + }, + { + title: 'Hugging Face', + slug: 'huggingface', + group: 'llm', + configs: [{ key: 'hugging_face_access_token', label: 'Hugging Face Access Token' }], + }, + { + title: 'Replicate', + slug: 'replicate', + group: 'llm', + configs: [ + { + key: 'replicate_api_token', + label: 'Replicate API Token', + }, + ], + }, + { + title: 'Pinecone', + slug: 'pinecone', + group: 'vectorDb', + configs: [ + { + key: 'pinecone_api_key', + label: 'Pinecone API key', + }, + { + key: 'pinecone_environment', + label: 'Pinecone Environment', + }, + ], + }, + { + title: 'Weaviate', + slug: 'weaviate', + group: 'vectorDb', + configs: [ + { + key: 'weaviate_url', + label: 'Weaviate Url', + }, + { + key: 'weaviate_api_key', + label: 'Weaviate Api Key', + }, + ], + }, +] + +export const useSettingView = ({ fields }: { fields: any }) => { + const { setToast } = useContext(ToastContext) + const { account: currentAccount } = useContext(AuthContext) + + const [isLoading, setIsLoading] = useState(false) + + const { data: configsData, refetch: refetchConfigs } = useConfigsService() + + const [createConfig] = useCreateConfigService() + const [updateConfig] = useUpdateConfigService() + + const initialValue = fields?.reduce((acc: any, field: any) => { + acc[field.key] = configsData?.find( + (config: any) => config.account_id === currentAccount?.id && config.key === field.key, + ).value + return acc + }, {}) + + const handleSubmit = async (values: any) => { + setIsLoading(true) + + try { + const promises = fields.map(async (field: any) => { + const value = values[field.key] + + const config = configsData.find( + (config: any) => config.account_id === currentAccount?.id && config.key === field.key, + ) + + const data = { + key: field.key, + key_type: 'string', + is_secret: true, + is_required: true, + value, + } + + if (config) { + return updateConfig(config.id, data) + } else { + return createConfig(data) + } + }) + + await Promise.all(promises) + await refetchConfigs() + + setToast({ + message: 'Settings updated!', + type: 'positive', + open: true, + }) + } catch (e) { + // console.log(e) + + setToast({ + message: 'Failed to save!', + type: 'negative', + open: true, + }) + } + + setIsLoading(false) + } + + const formik = useFormik({ + initialValues: initialValue, + enableReinitialize: true, + onSubmit: async values => handleSubmit(values), + }) + + return { + formik, + isLoading, + handleSubmit, + } +} diff --git a/apps/ui/src/pages/Settings/Settings.tsx b/apps/ui/src/pages/Settings/Settings.tsx index c74924514..824c054b4 100644 --- a/apps/ui/src/pages/Settings/Settings.tsx +++ b/apps/ui/src/pages/Settings/Settings.tsx @@ -28,6 +28,8 @@ const Settings = ({ isModal = false }: { isModal?: boolean }) => { if (!configsData) return
+ console.log('SETTINGS_FIELDS', SETTINGS_FIELDS) + return ( diff --git a/apps/ui/src/pages/Settings/constants.ts b/apps/ui/src/pages/Settings/constants.ts new file mode 100644 index 000000000..6e9d9c9bc --- /dev/null +++ b/apps/ui/src/pages/Settings/constants.ts @@ -0,0 +1,28 @@ +import openAiLogo from 'assets/settings/openAi-logo.png' +import huggingFaceLogo from 'assets/settings/hf-logo.png' +import replicateLogo from 'assets/settings/replicate-logo.png' +import pineconeLogo from 'assets/settings/pinecone-logo.png' +import weaviateLogo from 'assets/settings/weaviate-logo.jpg' + +export const settingLogos = [ + { + logoSrc: openAiLogo, + settingName: 'OpenAI', + }, + { + logoSrc: huggingFaceLogo, + settingName: 'Hugging Face', + }, + { + logoSrc: replicateLogo, + settingName: 'Replicate', + }, + { + logoSrc: pineconeLogo, + settingName: 'Pinecone', + }, + { + logoSrc: weaviateLogo, + settingName: 'Weaviate', + }, +] diff --git a/apps/ui/src/plugins/contact/pages/Voice/VoiceView/VoiceView.tsx b/apps/ui/src/plugins/contact/pages/Voice/VoiceView/VoiceView.tsx index e3c5bdcc7..035d2c217 100644 --- a/apps/ui/src/plugins/contact/pages/Voice/VoiceView/VoiceView.tsx +++ b/apps/ui/src/plugins/contact/pages/Voice/VoiceView/VoiceView.tsx @@ -25,10 +25,12 @@ import { useVoiceView } from './useVoiceView' import { StyledButtonWrapper, StyledFieldsWrapper, + StyledImg, StyledInnerWrapper, StyledMainTextWrapper, StyledTextWrapper, } from 'pages/Toolkit/ToolView/ToolView' +import { voiceLogos } from '../constants' const VoiceView = ({ voiceSlug, @@ -50,6 +52,10 @@ const VoiceView = ({ const description = voice?.description || '' const fields = voice?.fields + const filteredLogos = voiceLogos.filter((toolLogo: any) => toolLogo.voiceName === voice.name) + + const logoSrc = filteredLogos?.[0]?.logoSrc || '' + return ( @@ -77,7 +83,7 @@ const VoiceView = ({ {!hideInfo && ( <> - {/* */} + { const { getChatModules } = useGetAccountModule() @@ -329,6 +330,9 @@ const ChatRouteLayout = () => { {location.pathname.includes('/chat') ? ( + + + handleTabClick(0, 'playground')}>{t('playground')} handleTabClick(1, 'sessions')} disabled={teamId ? true : false}> @@ -342,9 +346,7 @@ const ChatRouteLayout = () => { {t('More')} - - @@ -494,6 +496,8 @@ export const StyledChatWrapper = styled.div` display: flex; flex-direction: column; + + position: relative; ` const StyledOutletWrapper = styled.div` width: 100%; @@ -546,3 +550,8 @@ const StyledTableWrapper = styled.div` height: calc(100% - 50px); padding-top: 15px; ` +const StyledFocusButtonWrapper = styled.div` + position: absolute; + right: 20px; + top: 0; +` diff --git a/apps/ui/src/services/chat/useCreateChatMessageService.ts b/apps/ui/src/services/chat/useCreateChatMessageService.ts index 4c1b556b4..2f186b431 100644 --- a/apps/ui/src/services/chat/useCreateChatMessageService.ts +++ b/apps/ui/src/services/chat/useCreateChatMessageService.ts @@ -95,6 +95,7 @@ export const useCreateChatMessageService = ({ const token = new TextDecoder('utf-8').decode(value) aiMessage.message.data.content += token + aiMessage.message.data.content = aiMessage.message.data.content.replace(/\\n/g, '\n') upsertChatMessageInCache(aiMessage, { agentId, diff --git a/apps/ui/src/services/chat/useCreateClientChatMessage.tsx b/apps/ui/src/services/chat/useCreateClientChatMessage.tsx index fd28e387d..0eb8e493b 100644 --- a/apps/ui/src/services/chat/useCreateClientChatMessage.tsx +++ b/apps/ui/src/services/chat/useCreateClientChatMessage.tsx @@ -91,6 +91,7 @@ export const useCreateClientChatMessageService = ({ const token = new TextDecoder('utf-8').decode(value) aiMessage.message.data.content += token + aiMessage.message.data.content = aiMessage.message.data.content.replace(/\\n/g, '\n') upsertChatMessageInCache(aiMessage, { chatId: chat_id,