diff --git a/web/src/app/admin/assistants/AssistantEditor.tsx b/web/src/app/admin/assistants/AssistantEditor.tsx index f8bdf55f7452..1c32beae14a4 100644 --- a/web/src/app/admin/assistants/AssistantEditor.tsx +++ b/web/src/app/admin/assistants/AssistantEditor.tsx @@ -1184,20 +1184,16 @@ export function AssistantEditor({ /> - {isPaidEnterpriseFeaturesEnabled && - userGroups && - userGroups.length > 0 && ( - - )} + )} diff --git a/web/src/app/admin/configuration/llm/CustomLLMProviderUpdateForm.tsx b/web/src/app/admin/configuration/llm/CustomLLMProviderUpdateForm.tsx index 204d054a9910..5252e6aede54 100644 --- a/web/src/app/admin/configuration/llm/CustomLLMProviderUpdateForm.tsx +++ b/web/src/app/admin/configuration/llm/CustomLLMProviderUpdateForm.tsx @@ -28,6 +28,7 @@ import { PopupSpec } from "@/components/admin/connectors/Popup"; import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled"; import * as Yup from "yup"; import isEqual from "lodash/isEqual"; +import { IsPublicGroupSelector } from "@/components/IsPublicGroupSelector"; function customConfigProcessing(customConfigsList: [string, string][]) { const customConfig: { [key: string]: string } = {}; @@ -209,7 +210,7 @@ export function CustomLLMProviderUpdateForm({ setSubmitting(false); }} > - {({ values, setFieldValue }) => { + {(formikProps) => { return (
) => (
- {values.custom_config_list.map((_, index) => { + {formikProps.values.custom_config_list.map((_, index) => { return (
List the individual models that you want to make available as @@ -419,64 +420,12 @@ export function CustomLLMProviderUpdateForm({ /> {showAdvancedOptions && ( - <> - {isPaidEnterpriseFeaturesEnabled && userGroups && ( - <> - - - {userGroups && - userGroups.length > 0 && - !values.is_public && ( -
- - Select which User Groups should have access to this - LLM Provider. - -
- {userGroups.map((userGroup) => { - const isSelected = values.groups.includes( - userGroup.id - ); - return ( - { - if (isSelected) { - setFieldValue( - "groups", - values.groups.filter( - (id) => id !== userGroup.id - ) - ); - } else { - setFieldValue("groups", [ - ...values.groups, - userGroup.id, - ]); - } - }} - > -
- -
{userGroup.name}
-
-
- ); - })} -
-
- )} - - )} - + )}
diff --git a/web/src/app/admin/configuration/llm/LLMProviderUpdateForm.tsx b/web/src/app/admin/configuration/llm/LLMProviderUpdateForm.tsx index 677fa4bd929c..f461ffbe8891 100644 --- a/web/src/app/admin/configuration/llm/LLMProviderUpdateForm.tsx +++ b/web/src/app/admin/configuration/llm/LLMProviderUpdateForm.tsx @@ -24,6 +24,7 @@ import { PopupSpec } from "@/components/admin/connectors/Popup"; import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled"; import * as Yup from "yup"; import isEqual from "lodash/isEqual"; +import { IsPublicGroupSelector } from "@/components/IsPublicGroupSelector"; export function LLMProviderUpdateForm({ llmProviderDescriptor, @@ -219,7 +220,7 @@ export function LLMProviderUpdateForm({ setSubmitting(false); }} > - {({ values, setFieldValue }) => ( + {(formikProps) => ( {!hideAdvanced && ( 0 && (
- setFieldValue("display_model_names", selected) + formikProps.setFieldValue( + "display_model_names", + selected + ) } />
)} - {isPaidEnterpriseFeaturesEnabled && userGroups && ( - <> - - - {userGroups && - userGroups.length > 0 && - !values.is_public && ( -
- - Select which User Groups should have access to - this LLM Provider. - -
- {userGroups.map((userGroup) => { - const isSelected = values.groups.includes( - userGroup.id - ); - return ( - { - if (isSelected) { - setFieldValue( - "groups", - values.groups.filter( - (id) => id !== userGroup.id - ) - ); - } else { - setFieldValue("groups", [ - ...values.groups, - userGroup.id, - ]); - } - }} - > -
- -
- {userGroup.name} -
-
-
- ); - })} -
-
- )} - - )} + )} diff --git a/web/src/app/admin/connectors/[connector]/AddConnectorPage.tsx b/web/src/app/admin/connectors/[connector]/AddConnectorPage.tsx index 895abf95d1a7..af30479aaa03 100644 --- a/web/src/app/admin/connectors/[connector]/AddConnectorPage.tsx +++ b/web/src/app/admin/connectors/[connector]/AddConnectorPage.tsx @@ -1,19 +1,17 @@ "use client"; -import * as Yup from "yup"; -import { TrashIcon } from "@/components/icons/icons"; import { errorHandlingFetcher } from "@/lib/fetcher"; import useSWR, { mutate } from "swr"; import { HealthCheckBanner } from "@/components/health/healthcheck"; -import { Card, Divider, Title } from "@tremor/react"; +import { Card, Title } from "@tremor/react"; import { AdminPageTitle } from "@/components/admin/Title"; import { buildSimilarCredentialInfoURL } from "@/app/admin/connector/[ccPairId]/lib"; import { usePopup } from "@/components/admin/connectors/Popup"; import { useFormContext } from "@/components/context/FormContext"; import { getSourceDisplayName } from "@/lib/sources"; import { SourceIcon } from "@/components/SourceIcon"; -import { useRef, useState, useEffect } from "react"; +import { useState } from "react"; import { submitConnector } from "@/components/admin/connectors/ConnectorForm"; import { deleteCredential, linkCredential } from "@/lib/credential"; import { submitFiles } from "./pages/utils/files"; @@ -27,38 +25,38 @@ import { Credential, credentialTemplates } from "@/lib/connectors/credentials"; import { ConnectionConfiguration, connectorConfigs, + createConnectorInitialValues, + createConnectorValidationSchema, } from "@/lib/connectors/connectors"; import { Modal } from "@/components/Modal"; -import { ArrowRight } from "@phosphor-icons/react"; -import { ArrowLeft } from "@phosphor-icons/react/dist/ssr"; -import { FiPlus } from "react-icons/fi"; import GDriveMain from "./pages/gdrive/GoogleDrivePage"; import { GmailMain } from "./pages/gmail/GmailPage"; import { useGmailCredentials, useGoogleDriveCredentials, } from "./pages/utils/hooks"; -import { Formik, FormikProps } from "formik"; -import { - IsPublicGroupSelector, - IsPublicGroupSelectorFormType, -} from "@/components/IsPublicGroupSelector"; -import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled"; +import { Formik } from "formik"; +import { IsPublicGroupSelector } from "@/components/IsPublicGroupSelector"; +import NavigationRow from "./NavigationRow"; -export type AdvancedConfigFinal = { - pruneFreq: number | null; - refreshFreq: number | null; - indexingStart: Date | null; -}; +export interface AdvancedConfig { + refreshFreq: number; + pruneFreq: number; + indexingStart: string; +} export default function AddConnector({ connector, }: { connector: ConfigurableSources; }) { + // State for managing credentials and files const [currentCredential, setCurrentCredential] = useState | null>(null); + const [selectedFiles, setSelectedFiles] = useState([]); + const [createConnectorToggle, setCreateConnectorToggle] = useState(false); + // Fetch credentials data const { data: credentials } = useSWR[]>( buildSimilarCredentialInfoURL(connector), errorHandlingFetcher, @@ -70,76 +68,27 @@ export default function AddConnector({ errorHandlingFetcher, { refreshInterval: 5000 } ); - const [selectedFiles, setSelectedFiles] = useState([]); + // Get credential template and configuration const credentialTemplate = credentialTemplates[connector]; + const configuration: ConnectionConfiguration = connectorConfigs[connector]; - const { - setFormStep, - setAllowAdvanced, - setAlowCreate, - formStep, - nextFormStep, - prevFormStep, - } = useFormContext(); - + // Form context and popup management + const { setFormStep, setAlowCreate, formStep, nextFormStep, prevFormStep } = + useFormContext(); const { popup, setPopup } = usePopup(); - const configuration: ConnectionConfiguration = connectorConfigs[connector]; - const [formValues, setFormValues] = useState< - Record & IsPublicGroupSelectorFormType - >({ - name: "", - groups: [], - is_public: true, - ...configuration.values.reduce( - (acc, field) => { - if (field.type === "select") { - acc[field.name] = null; - } else if (field.type === "list") { - acc[field.name] = field.default || []; - } else if (field.type === "checkbox") { - acc[field.name] = field.default || false; - } else if (field.default !== undefined) { - acc[field.name] = field.default; - } - return acc; - }, - {} as { [record: string]: any } - ), - }); - - const isPaidEnterpriseFeaturesEnabled = usePaidEnterpriseFeaturesEnabled(); - - // Default to 10 minutes unless otherwise specified - const defaultAdvancedSettings = { - refreshFreq: formValues.overrideDefaultFreq || 10, - pruneFreq: 30, - indexingStart: null as string | null, - }; - - const [advancedSettings, setAdvancedSettings] = useState( - defaultAdvancedSettings - ); - - const [createConnectorToggle, setCreateConnectorToggle] = useState(false); - const formRef = useRef>(null); - - const [isFormValid, setIsFormValid] = useState(false); - - const handleFormStatusChange = (isValid: boolean) => { - setIsFormValid(isValid || connector == "file"); - }; - + // Hooks for Google Drive and Gmail credentials const { liveGDriveCredential } = useGoogleDriveCredentials(); - const { liveGmailCredential } = useGmailCredentials(); + // Check if credential is activated const credentialActivated = (connector === "google_drive" && liveGDriveCredential) || (connector === "gmail" && liveGmailCredential) || currentCredential; + // Check if there are no credentials const noCredentials = credentialTemplate == null; if (noCredentials && 1 != formStep) { @@ -150,164 +99,20 @@ export default function AddConnector({ setFormStep(Math.min(formStep, 0)); } - const resetAdvancedConfigs = (formikProps: FormikProps) => { - formikProps.resetForm({ values: defaultAdvancedSettings }); - setAdvancedSettings(defaultAdvancedSettings); - }; - const convertStringToDateTime = (indexingStart: string | null) => { return indexingStart ? new Date(indexingStart) : null; }; - const createConnector = async () => { - const { - name, - groups, - is_public: isPublic, - ...connector_specific_config - } = formValues; - const { pruneFreq, indexingStart, refreshFreq } = advancedSettings; - - // Apply transforms from connectors.ts configuration - const transformedConnectorSpecificConfig = Object.entries( - connector_specific_config - ).reduce( - (acc, [key, value]) => { - const matchingConfigValue = configuration.values.find( - (configValue) => configValue.name === key - ); - if ( - matchingConfigValue && - "transform" in matchingConfigValue && - matchingConfigValue.transform - ) { - acc[key] = matchingConfigValue.transform(value as string[]); - } else { - acc[key] = value; - } - return acc; - }, - {} as Record - ); - - const AdvancedConfig: AdvancedConfigFinal = { - pruneFreq: advancedSettings.pruneFreq * 60 * 60 * 24, - indexingStart: convertStringToDateTime(indexingStart), - refreshFreq: advancedSettings.refreshFreq * 60, - }; - - // google sites-specific handling - if (connector == "google_sites") { - const response = await submitGoogleSite( - selectedFiles, - formValues?.base_url, - setPopup, - AdvancedConfig, - name - ); - if (response) { - setTimeout(() => { - window.open("/admin/indexing/status", "_self"); - }, 1000); - } - return; - } - - // file-specific handling - if (connector == "file" && selectedFiles.length > 0) { - const response = await submitFiles( - selectedFiles, - setPopup, - setSelectedFiles, - name, - AdvancedConfig, - isPublic, - groups - ); - if (response) { - setTimeout(() => { - window.open("/admin/indexing/status", "_self"); - }, 1000); - } - return; - } - - const { message, isSuccess, response } = await submitConnector( - { - connector_specific_config: transformedConnectorSpecificConfig, - input_type: connector == "web" ? "load_state" : "poll", // single case - name: name, - source: connector, - refresh_freq: refreshFreq * 60 || null, - prune_freq: pruneFreq * 60 * 60 * 24 || null, - indexing_start: convertStringToDateTime(indexingStart), - is_public: isPublic, - groups: groups, - }, - undefined, - credentialActivated ? false : true, - isPublic - ); - // If no credential - if (!credentialActivated) { - if (isSuccess) { - setPopup({ - message: "Connector created! Redirecting to connector home page", - type: "success", - }); - setTimeout(() => { - window.open("/admin/indexing/status", "_self"); - }, 1000); - } else { - setPopup({ message: message, type: "error" }); - } - } - - // Without credential - if (credentialActivated && isSuccess && response) { - const credential = - currentCredential || liveGDriveCredential || liveGmailCredential; - const linkCredentialResponse = await linkCredential( - response.id, - credential?.id!, - name, - isPublic, - groups - ); - if (linkCredentialResponse.ok) { - setPopup({ - message: "Connector created! Redirecting to connector home page", - type: "success", - }); - setTimeout(() => { - window.open("/admin/indexing/status", "_self"); - }, 1000); - } else { - const errorData = await linkCredentialResponse.json(); - setPopup({ - message: errorData.message, - type: "error", - }); - } - } else if (isSuccess) { - setPopup({ - message: - "Credential created succsfully! Redirecting to connector home page", - type: "success", - }); - } else { - setPopup({ message: message, type: "error" }); - } - }; - const displayName = getSourceDisplayName(connector) || connector; if (!credentials || !editableCredentials) { return <>; } + // Credential handler functions const refresh = () => { mutate(buildSimilarCredentialInfoURL(connector)); }; + const onDeleteCredential = async (credential: Credential) => { const response = await deleteCredential(credential.id, true); if (response.ok) { @@ -334,291 +139,256 @@ export default function AddConnector({ refresh(); }; - const validationSchema = Yup.object().shape({ - name: Yup.string().required("Connector Name is required"), - ...configuration.values.reduce( - (acc, field) => { - let schema: any = - field.type === "select" - ? Yup.string() - : field.type === "list" - ? Yup.array().of(Yup.string()) - : field.type === "checkbox" - ? Yup.boolean() - : Yup.string(); - - if (!field.optional) { - schema = schema.required(`${field.label} is required`); - } - acc[field.name] = schema; - return acc; - }, - {} as Record - ), - }); - - const advancedValidationSchema = Yup.object().shape({ - indexingStart: Yup.string().nullable(), - pruneFreq: Yup.number().min(0, "Prune frequency must be non-negative"), - refreshFreq: Yup.number().min(0, "Refresh frequency must be non-negative"), - }); - - const isFormSubmittable = (values: any) => { - return ( - values.name.trim() !== "" && - Object.keys(values).every((key) => { - const field = configuration.values.find((f) => f.name === key); - return field?.optional || values[key] !== ""; - }) - ); + const onSuccess = () => { + setPopup({ + message: "Connector created! Redirecting to connector home page", + type: "success", + }); + setTimeout(() => { + window.open("/admin/indexing/status", "_self"); + }, 1000); }; return ( -
- {popup} -
- -
+ { + console.log(" Iam submiing the connector"); + const { + name, + groups, + is_public: isPublic, + pruneFreq, + indexingStart, + refreshFreq, + ...connector_specific_config + } = values; - } - title={displayName} - /> + // Apply transforms from connectors.ts configuration + const transformedConnectorSpecificConfig = Object.entries( + connector_specific_config + ).reduce( + (acc, [key, value]) => { + const matchingConfigValue = configuration.values.find( + (configValue) => configValue.name === key + ); + if ( + matchingConfigValue && + "transform" in matchingConfigValue && + matchingConfigValue.transform + ) { + acc[key] = matchingConfigValue.transform(value as string[]); + } else { + acc[key] = value; + } + return acc; + }, + {} as Record + ); - {formStep == 0 && - (connector == "google_drive" ? ( - <> - - Select a credential - - -
- + // Apply advanced configuration-specific transforms. + const advancedConfiguration: any = { + pruneFreq: pruneFreq * 60 * 60 * 24, + indexingStart: convertStringToDateTime(indexingStart), + refreshFreq: refreshFreq * 60, + }; + + // Google sites-specific handling + if (connector == "google_sites") { + const response = await submitGoogleSite( + selectedFiles, + values?.base_url, + setPopup, + advancedConfiguration.refreshFreq, + advancedConfiguration.pruneFreq, + advancedConfiguration.indexingStart, + name + ); + if (response) { + onSuccess(); + } + return; + } + + // File-specific handling + if (connector == "file" && selectedFiles.length > 0) { + const response = await submitFiles( + selectedFiles, + setPopup, + setSelectedFiles, + name, + isPublic, + groups + ); + if (response) { + onSuccess(); + } + return; + } + + const { message, isSuccess, response } = await submitConnector( + { + connector_specific_config: transformedConnectorSpecificConfig, + input_type: connector == "web" ? "load_state" : "poll", // single case + name: name, + source: connector, + refresh_freq: advancedConfiguration.refreshFreq || null, + prune_freq: advancedConfiguration.pruneFreq || null, + indexing_start: advancedConfiguration.indexingStart || null, + is_public: isPublic, + groups: groups, + }, + undefined, + credentialActivated ? false : true, + isPublic + ); + // If no credential + if (!credentialActivated) { + if (isSuccess) { + onSuccess(); + } else { + setPopup({ message: message, type: "error" }); + } + } + + // Without credential + if (credentialActivated && isSuccess && response) { + const credential = + currentCredential || liveGDriveCredential || liveGmailCredential; + const linkCredentialResponse = await linkCredential( + response.id, + credential?.id!, + name, + isPublic, + groups + ); + if (linkCredentialResponse.ok) { + onSuccess(); + } else { + const errorData = await linkCredentialResponse.json(); + setPopup({ + message: errorData.message, + type: "error", + }); + } + } else if (isSuccess) { + onSuccess(); + } else { + setPopup({ message: message, type: "error" }); + } + return; + }} + > + {(formikProps) => { + return ( +
+ {popup} + +
+
- - ) : connector == "gmail" ? ( - <> - - Select a credential - - -
- -
- - ) : ( - <> - - Select a credential - - {!createConnectorToggle && ( - - )} - {/* NOTE: connector will never be google_drive, since the ternary above will - prevent that, but still keeping this here for safety in case the above changes. */} - {(connector as ValidSources) !== "google_drive" && - createConnectorToggle && ( - setCreateConnectorToggle(false)} - > - <> - - Create a {getSourceDisplayName(connector)} credential - - setCreateConnectorToggle(false)} - /> - - - )} - -
- -
- - ))} + } + title={displayName} + /> - {formStep == 1 && ( - <> - - { - // Can be utilized for logging purposes - }} - > - {(formikProps) => { - setFormValues(formikProps.values); - handleFormStatusChange( - formikProps.isValid && isFormSubmittable(formikProps.values) - ); - setAllowAdvanced( - formikProps.isValid && isFormSubmittable(formikProps.values) - ); + {formStep == 0 && ( + + Select a credential - return ( -
- - {isPaidEnterpriseFeaturesEnabled && ( - <> - - - )} -
- ); - }} -
-
-
- {!noCredentials ? ( - - ) : ( -
- )} - - - {!(connector == "file") && ( -
- -
- )} -
- - )} - - {formStep === 2 && ( - <> - - {}} - > - {(formikProps) => { - setAdvancedSettings(formikProps.values); - - return ( + {connector == "google_drive" ? ( + + ) : connector == "gmail" ? ( + + ) : ( <> - -
+ + {!createConnectorToggle && ( -
+ )} + + {/* NOTE: connector will never be google_drive, since the ternary above will + prevent that, but still keeping this here for safety in case the above changes. */} + {(connector as ValidSources) !== "google_drive" && + createConnectorToggle && ( + setCreateConnectorToggle(false)} + > + <> + + Create a {getSourceDisplayName(connector)}{" "} + credential + + setCreateConnectorToggle(false)} + /> + + + )} - ); - }} -
-
-
- - + )} + + )} + + {formStep == 1 && ( + + + + + + )} + + {formStep === 2 && ( + + + + )} + +
- - )} -
+ ); + }} + ); } diff --git a/web/src/app/admin/connectors/[connector]/NavigationRow.tsx b/web/src/app/admin/connectors/[connector]/NavigationRow.tsx new file mode 100644 index 000000000000..933e4c9d06ff --- /dev/null +++ b/web/src/app/admin/connectors/[connector]/NavigationRow.tsx @@ -0,0 +1,91 @@ +import { useFormContext } from "@/components/context/FormContext"; +import { ArrowLeft, ArrowRight } from "@phosphor-icons/react"; +import { FiPlus } from "react-icons/fi"; + +const NavigationRow = ({ + noAdvanced, + noCredentials, + activatedCredential, + onSubmit, + isValid, +}: { + isValid: boolean; + onSubmit: () => void; + noAdvanced: boolean; + noCredentials: boolean; + activatedCredential: boolean; +}) => { + const { formStep, prevFormStep, nextFormStep } = useFormContext(); + const SquareNavigationButton = ({ + onClick, + disabled, + className, + children, + }: { + onClick: () => void; + disabled?: boolean; + className: string; + children: React.ReactNode; + }) => ( + + ); + + return ( +
+
+ {formStep > 0 && !noCredentials && ( + + + Previous + + )} +
+ +
+ {(formStep > 0 || noCredentials) && ( + + Create Connector + + + )} +
+ +
+ {formStep === 0 && ( + + Continue + + + )} + {noAdvanced && formStep === 1 && ( + + Advanced + + + )} +
+
+ ); +}; +export default NavigationRow; diff --git a/web/src/app/admin/connectors/[connector]/pages/Advanced.tsx b/web/src/app/admin/connectors/[connector]/pages/Advanced.tsx index 8bb96d54db1c..0f50a7043b8b 100644 --- a/web/src/app/admin/connectors/[connector]/pages/Advanced.tsx +++ b/web/src/app/admin/connectors/[connector]/pages/Advanced.tsx @@ -1,66 +1,47 @@ -import React, { Dispatch, forwardRef, SetStateAction } from "react"; -import { Formik, Form, FormikProps } from "formik"; -import * as Yup from "yup"; +import React from "react"; import NumberInput from "./ConnectorInput/NumberInput"; import { TextFormField } from "@/components/admin/connectors/Field"; +import { TrashIcon } from "@/components/icons/icons"; -interface AdvancedFormPageProps { - formikProps: FormikProps<{ - indexingStart: string | null; - pruneFreq: number; - refreshFreq: number; - }>; -} +const AdvancedFormPage = () => { + return ( +
+

+ Advanced Configuration +

-const AdvancedFormPage = forwardRef, AdvancedFormPageProps>( - ({ formikProps }, ref) => { - const { indexingStart, refreshFreq, pruneFreq } = formikProps.values; + - return ( -
-

- Advanced Configuration -

+ - -
- -
- -
- -
- -
- -
- + +
+
- ); - } -); +
+ ); +}; -AdvancedFormPage.displayName = "AdvancedFormPage"; export default AdvancedFormPage; diff --git a/web/src/app/admin/connectors/[connector]/pages/ConnectorInput/NumberInput.tsx b/web/src/app/admin/connectors/[connector]/pages/ConnectorInput/NumberInput.tsx index a62864495ef8..b7fcb49cf1ea 100644 --- a/web/src/app/admin/connectors/[connector]/pages/ConnectorInput/NumberInput.tsx +++ b/web/src/app/admin/connectors/[connector]/pages/ConnectorInput/NumberInput.tsx @@ -3,23 +3,17 @@ import { Field, useFormikContext } from "formik"; export default function NumberInput({ label, - value, optional, description, name, showNeverIfZero, - onChange, }: { - value?: number; label: string; name: string; optional?: boolean; description?: string; showNeverIfZero?: boolean; - onChange?: (value: number) => void; }) { - const { setFieldValue } = useFormikContext(); - return (
- )} - + > + + Add Language + +
+ )} + - ) => { - const checked = e.target.checked; - updateAdvancedEmbeddingDetails("multipass_indexing", checked); - setFieldValue("multipass_indexing", checked); - }} - label="Multipass Indexing" - name="multipassIndexing" - /> - ) => { - const checked = e.target.checked; - updateAdvancedEmbeddingDetails( - "disable_rerank_for_streaming", - checked - ); - setFieldValue("disable_rerank_for_streaming", checked); - }} - label="Disable Rerank for Streaming" - name="disableRerankForStreaming" - /> - { - updateNumRerank(value); - setFieldValue("num_rerank", value); - }} - description="Number of results to rerank" - optional={false} - value={values.num_rerank} - label="Number of Results to Rerank" - name="num_rerank" - /> - - )} - -
- ); - } -); + + + + + )} + +
+ ); +}); +export default AdvancedEmbeddingFormPage; AdvancedEmbeddingFormPage.displayName = "AdvancedEmbeddingFormPage"; -export default AdvancedEmbeddingFormPage; diff --git a/web/src/app/admin/embeddings/pages/EmbeddingFormPage.tsx b/web/src/app/admin/embeddings/pages/EmbeddingFormPage.tsx index 91691963ff51..6f1f9bcdf3e5 100644 --- a/web/src/app/admin/embeddings/pages/EmbeddingFormPage.tsx +++ b/web/src/app/admin/embeddings/pages/EmbeddingFormPage.tsx @@ -41,11 +41,11 @@ export default function EmbeddingForm() { multilingual_expansion: [], disable_rerank_for_streaming: false, api_url: null, + num_rerank: 0, }); const [rerankingDetails, setRerankingDetails] = useState({ rerank_api_key: "", - num_rerank: 0, rerank_provider_type: null, rerank_model_name: "", rerank_api_url: null, @@ -117,11 +117,12 @@ export default function EmbeddingForm() { multilingual_expansion: searchSettings.multilingual_expansion, disable_rerank_for_streaming: searchSettings.disable_rerank_for_streaming, + num_rerank: searchSettings.num_rerank, api_url: null, }); + setRerankingDetails({ rerank_api_key: searchSettings.rerank_api_key, - num_rerank: searchSettings.num_rerank, rerank_provider_type: searchSettings.rerank_provider_type, rerank_model_name: searchSettings.rerank_model_name, rerank_api_url: searchSettings.rerank_api_url, @@ -132,14 +133,12 @@ export default function EmbeddingForm() { const originalRerankingDetails: RerankingDetails = searchSettings ? { rerank_api_key: searchSettings.rerank_api_key, - num_rerank: searchSettings.num_rerank, rerank_provider_type: searchSettings.rerank_provider_type, rerank_model_name: searchSettings.rerank_model_name, rerank_api_url: searchSettings.rerank_api_url, } : { rerank_api_key: "", - num_rerank: 0, rerank_provider_type: null, rerank_model_name: "", rerank_api_url: null, @@ -422,13 +421,6 @@ export default function EmbeddingForm() { <> - setRerankingDetails({ - ...rerankingDetails, - num_rerank: newNumRerank, - }) - } - numRerank={rerankingDetails.num_rerank} advancedEmbeddingDetails={advancedEmbeddingDetails} updateAdvancedEmbeddingDetails={updateAdvancedEmbeddingDetails} /> diff --git a/web/src/app/ee/admin/groups/UserGroupCreationForm.tsx b/web/src/app/ee/admin/groups/UserGroupCreationForm.tsx index dcc3422c4e8c..c87bfb5e5844 100644 --- a/web/src/app/ee/admin/groups/UserGroupCreationForm.tsx +++ b/web/src/app/ee/admin/groups/UserGroupCreationForm.tsx @@ -29,7 +29,7 @@ export const UserGroupCreationForm = ({ const isUpdate = existingUserGroup !== undefined; return ( - +

{isUpdate ? "Update a User Group" : "Create a new User Group"} diff --git a/web/src/components/IsPublicGroupSelector.tsx b/web/src/components/IsPublicGroupSelector.tsx index f48c112b8539..6c7aaa170978 100644 --- a/web/src/components/IsPublicGroupSelector.tsx +++ b/web/src/components/IsPublicGroupSelector.tsx @@ -1,3 +1,4 @@ +import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled"; import React, { useState, useEffect } from "react"; import { FormikProps, FieldArray, ArrayHelpers, ErrorMessage } from "formik"; import { Text, Divider } from "@tremor/react"; @@ -12,6 +13,8 @@ export type IsPublicGroupSelectorFormType = { groups: number[]; }; +// This should be included for all forms that require groups / public access +// to be set, and access to this / permissioning should be handled within this component itself. export const IsPublicGroupSelector = ({ formikProps, objectName, @@ -27,10 +30,11 @@ export const IsPublicGroupSelector = ({ }) => { const { data: userGroups, isLoading: userGroupsIsLoading } = useUserGroups(); const { isAdmin, user, isLoadingUser, isCurator } = useUser(); + const isPaidEnterpriseFeaturesEnabled = usePaidEnterpriseFeaturesEnabled(); const [shouldHideContent, setShouldHideContent] = useState(false); useEffect(() => { - if (user && userGroups) { + if (user && userGroups && isPaidEnterpriseFeaturesEnabled) { const isUserAdmin = user.role === UserRole.ADMIN; if (!isUserAdmin) { formikProps.setFieldValue("is_public", false); @@ -55,6 +59,9 @@ export const IsPublicGroupSelector = ({ if (isLoadingUser || userGroupsIsLoading) { return
Loading...
; } + if (!isPaidEnterpriseFeaturesEnabled) { + return null; + } if (shouldHideContent && enforceGroupSelection) { return ( diff --git a/web/src/components/admin/connectors/Field.tsx b/web/src/components/admin/connectors/Field.tsx index cbbc7b6d5ceb..a76dfbad17c0 100644 --- a/web/src/components/admin/connectors/Field.tsx +++ b/web/src/components/admin/connectors/Field.tsx @@ -104,7 +104,6 @@ export function TextFormField({ subtext, placeholder, value, - onChange, type = "text", optional, includeRevert, @@ -121,6 +120,8 @@ export function TextFormField({ explanationLink, small, removeLabel, + min, + onChange, }: { value?: string; name: string; @@ -128,7 +129,6 @@ export function TextFormField({ label: string; subtext?: string | JSX.Element; placeholder?: string; - onChange?: (e: React.ChangeEvent) => void; includeRevert?: boolean; optional?: boolean; type?: string; @@ -144,11 +144,14 @@ export function TextFormField({ explanationText?: string; explanationLink?: string; small?: boolean; + min?: number; + onChange?: (e: React.ChangeEvent) => void; }) { let heightString = defaultHeight || ""; if (isTextArea && !heightString) { heightString = "h-28"; } + const [field, , helpers] = useField(name); const { setValue } = helpers; @@ -186,6 +189,8 @@ export function TextFormField({ {subtext && {subtext}}
- {includeRevert && ( -
- -
- )}
{explanationText && ( diff --git a/web/src/components/admin/connectors/FileUpload.tsx b/web/src/components/admin/connectors/FileUpload.tsx index 52dbef6006db..ea778153daca 100644 --- a/web/src/components/admin/connectors/FileUpload.tsx +++ b/web/src/components/admin/connectors/FileUpload.tsx @@ -1,3 +1,4 @@ +import { useFormikContext } from "formik"; import { FC, useState } from "react"; import React from "react"; import Dropzone from "react-dropzone"; @@ -6,14 +7,17 @@ interface FileUploadProps { selectedFiles: File[]; setSelectedFiles: (files: File[]) => void; message?: string; + name?: string; } export const FileUpload: FC = ({ + name, selectedFiles, setSelectedFiles, message, }) => { const [dragActive, setDragActive] = useState(false); + const { setFieldValue } = useFormikContext(); return (
@@ -21,6 +25,9 @@ export const FileUpload: FC = ({ onDrop={(acceptedFiles) => { setSelectedFiles(acceptedFiles); setDragActive(false); + if (name) { + setFieldValue(name, acceptedFiles); + } }} onDragLeave={() => setDragActive(false)} onDragEnter={() => setDragActive(true)} diff --git a/web/src/components/credentials/actions/CreateCredential.tsx b/web/src/components/credentials/actions/CreateCredential.tsx index 36c204d94ce6..0a5d3cb23ca3 100644 --- a/web/src/components/credentials/actions/CreateCredential.tsx +++ b/web/src/components/credentials/actions/CreateCredential.tsx @@ -1,10 +1,10 @@ -import React, { useState, useEffect } from "react"; +import React, { useState } from "react"; import { Button, Card } from "@tremor/react"; import { ValidSources } from "@/lib/types"; import { FaAccusoft } from "react-icons/fa"; import { submitCredential } from "@/components/admin/connectors/CredentialForm"; import { TextFormField } from "@/components/admin/connectors/Field"; -import { Form, Formik, FormikHelpers, FormikProps } from "formik"; +import { Form, Formik, FormikHelpers } from "formik"; import { PopupSpec } from "@/components/admin/connectors/Popup"; import { getSourceDocLink } from "@/lib/sources"; import GDriveMain from "@/app/admin/connectors/[connector]/pages/gdrive/GoogleDrivePage"; @@ -178,7 +178,7 @@ export default function CreateCredential({ initialValues={ { name: "", - is_public: isAdmin, + is_public: isAdmin || !isPaidEnterpriseFeaturesEnabled, groups: [], } as formType } @@ -232,7 +232,7 @@ export default function CreateCredential({ setShowAdvancedOptions={setShowAdvancedOptions} /> )} - {(showAdvancedOptions || !isAdmin) && ( + {showAdvancedOptions && ( +
Ensure that you update to a credential with the proper permissions! @@ -58,37 +58,37 @@ const EditCredential = ({ validationSchema={validationSchema} onSubmit={handleSubmit} > - {({ isSubmitting, setFieldValue }) => ( + {({ isSubmitting, resetForm }) => (
- + + + {Object.entries(credential.credential_json).map(([key, value]) => ( setFieldValue("name", e.target.value)} - name="name" - placeholder={credential.name || ""} - label="Name (optional):" + key={key} + name={key} + placeholder={value} + label={getDisplayNameForCredentialKey(key)} + type={ + key.toLowerCase().includes("token") || + key.toLowerCase().includes("password") + ? "password" + : "text" + } /> - - {Object.entries(credential.credential_json).map( - ([key, value]) => ( - setFieldValue(key, e.target.value)} - name={key} - placeholder={value} - label={getDisplayNameForCredentialKey(key)} - type={ - key.toLowerCase().includes("token") || - key.toLowerCase().includes("password") - ? "password" - : "text" - } - /> - ) - )} - -
+ ))} +
+