mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-09-25 11:16:43 +02:00
Improve Dev Experience (#2347)
* clean interfaces + improve dex experience * update formatting * update ports * ports * remove some number of unnecessary lines * remove unnecssary isPublicGroupSelector checks in all spots * add comment * update building
This commit is contained in:
@@ -1184,20 +1184,16 @@ export function AssistantEditor({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isPaidEnterpriseFeaturesEnabled &&
|
||||
userGroups &&
|
||||
userGroups.length > 0 && (
|
||||
<IsPublicGroupSelector
|
||||
formikProps={{
|
||||
values,
|
||||
isSubmitting,
|
||||
setFieldValue,
|
||||
...formikProps,
|
||||
}}
|
||||
objectName="assistant"
|
||||
enforceGroupSelection={false}
|
||||
/>
|
||||
)}
|
||||
<IsPublicGroupSelector
|
||||
formikProps={{
|
||||
values,
|
||||
isSubmitting,
|
||||
setFieldValue,
|
||||
...formikProps,
|
||||
}}
|
||||
objectName="assistant"
|
||||
enforceGroupSelection={false}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
@@ -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 (
|
||||
<Form className="gap-y-6 mt-8">
|
||||
<TextFormField
|
||||
@@ -285,7 +286,7 @@ export function CustomLLMProviderUpdateForm({
|
||||
name="custom_config_list"
|
||||
render={(arrayHelpers: ArrayHelpers<any[]>) => (
|
||||
<div>
|
||||
{values.custom_config_list.map((_, index) => {
|
||||
{formikProps.values.custom_config_list.map((_, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
@@ -371,7 +372,7 @@ export function CustomLLMProviderUpdateForm({
|
||||
<TextArrayField
|
||||
name="model_names"
|
||||
label="Model Names"
|
||||
values={values}
|
||||
values={formikProps.values}
|
||||
subtext={
|
||||
<>
|
||||
List the individual models that you want to make available as
|
||||
@@ -419,64 +420,12 @@ export function CustomLLMProviderUpdateForm({
|
||||
/>
|
||||
|
||||
{showAdvancedOptions && (
|
||||
<>
|
||||
{isPaidEnterpriseFeaturesEnabled && userGroups && (
|
||||
<>
|
||||
<BooleanFormField
|
||||
small
|
||||
removeIndent
|
||||
alignTop
|
||||
name="is_public"
|
||||
label="Is Public?"
|
||||
subtext="If set, this LLM Provider will be available to all users. If not, only the specified User Groups will be able to use it."
|
||||
/>
|
||||
|
||||
{userGroups &&
|
||||
userGroups.length > 0 &&
|
||||
!values.is_public && (
|
||||
<div>
|
||||
<Text>
|
||||
Select which User Groups should have access to this
|
||||
LLM Provider.
|
||||
</Text>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{userGroups.map((userGroup) => {
|
||||
const isSelected = values.groups.includes(
|
||||
userGroup.id
|
||||
);
|
||||
return (
|
||||
<Bubble
|
||||
key={userGroup.id}
|
||||
isSelected={isSelected}
|
||||
onClick={() => {
|
||||
if (isSelected) {
|
||||
setFieldValue(
|
||||
"groups",
|
||||
values.groups.filter(
|
||||
(id) => id !== userGroup.id
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setFieldValue("groups", [
|
||||
...values.groups,
|
||||
userGroup.id,
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex">
|
||||
<GroupsIcon />
|
||||
<div className="ml-1">{userGroup.name}</div>
|
||||
</div>
|
||||
</Bubble>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
<IsPublicGroupSelector
|
||||
formikProps={formikProps}
|
||||
objectName="LLM Provider"
|
||||
publicToWhom="all users"
|
||||
enforceGroupSelection={true}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div>
|
||||
|
@@ -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) => (
|
||||
<Form className="gap-y-4 items-stretch mt-6">
|
||||
{!hideAdvanced && (
|
||||
<TextFormField
|
||||
@@ -337,7 +338,9 @@ export function LLMProviderUpdateForm({
|
||||
{llmProviderDescriptor.llm_names.length > 0 && (
|
||||
<div className="w-full">
|
||||
<MultiSelectField
|
||||
selectedInitially={values.display_model_names}
|
||||
selectedInitially={
|
||||
formikProps.values.display_model_names
|
||||
}
|
||||
name="display_model_names"
|
||||
label="Display Models"
|
||||
subtext="Select the models to make available to users. Unselected models will not be available."
|
||||
@@ -348,70 +351,21 @@ export function LLMProviderUpdateForm({
|
||||
})
|
||||
)}
|
||||
onChange={(selected) =>
|
||||
setFieldValue("display_model_names", selected)
|
||||
formikProps.setFieldValue(
|
||||
"display_model_names",
|
||||
selected
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isPaidEnterpriseFeaturesEnabled && userGroups && (
|
||||
<>
|
||||
<BooleanFormField
|
||||
small
|
||||
removeIndent
|
||||
alignTop
|
||||
name="is_public"
|
||||
label="Is Public?"
|
||||
subtext="If set, this LLM Provider will be available to all users. If not, only the specified User Groups will be able to use it."
|
||||
/>
|
||||
|
||||
{userGroups &&
|
||||
userGroups.length > 0 &&
|
||||
!values.is_public && (
|
||||
<div>
|
||||
<Text>
|
||||
Select which User Groups should have access to
|
||||
this LLM Provider.
|
||||
</Text>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{userGroups.map((userGroup) => {
|
||||
const isSelected = values.groups.includes(
|
||||
userGroup.id
|
||||
);
|
||||
return (
|
||||
<Bubble
|
||||
key={userGroup.id}
|
||||
isSelected={isSelected}
|
||||
onClick={() => {
|
||||
if (isSelected) {
|
||||
setFieldValue(
|
||||
"groups",
|
||||
values.groups.filter(
|
||||
(id) => id !== userGroup.id
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setFieldValue("groups", [
|
||||
...values.groups,
|
||||
userGroup.id,
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex">
|
||||
<GroupsIcon />
|
||||
<div className="ml-1">
|
||||
{userGroup.name}
|
||||
</div>
|
||||
</div>
|
||||
</Bubble>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<IsPublicGroupSelector
|
||||
formikProps={formikProps}
|
||||
objectName="LLM Provider"
|
||||
publicToWhom="all users"
|
||||
enforceGroupSelection={true}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
@@ -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<Credential<any> | null>(null);
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
const [createConnectorToggle, setCreateConnectorToggle] = useState(false);
|
||||
|
||||
// Fetch credentials data
|
||||
const { data: credentials } = useSWR<Credential<any>[]>(
|
||||
buildSimilarCredentialInfoURL(connector),
|
||||
errorHandlingFetcher,
|
||||
@@ -70,76 +68,27 @@ export default function AddConnector({
|
||||
errorHandlingFetcher,
|
||||
{ refreshInterval: 5000 }
|
||||
);
|
||||
const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
|
||||
|
||||
// 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<string, any> & 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<FormikProps<any>>(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<any>) => {
|
||||
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<string, any>
|
||||
);
|
||||
|
||||
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<any>(
|
||||
{
|
||||
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<any | null>) => {
|
||||
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<string, any>
|
||||
),
|
||||
});
|
||||
|
||||
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 (
|
||||
<div className="mx-auto mb-8 w-full">
|
||||
{popup}
|
||||
<div className="mb-4">
|
||||
<HealthCheckBanner />
|
||||
</div>
|
||||
<Formik
|
||||
initialValues={createConnectorInitialValues(connector)}
|
||||
validationSchema={createConnectorValidationSchema(connector)}
|
||||
onSubmit={async (values) => {
|
||||
console.log(" Iam submiing the connector");
|
||||
const {
|
||||
name,
|
||||
groups,
|
||||
is_public: isPublic,
|
||||
pruneFreq,
|
||||
indexingStart,
|
||||
refreshFreq,
|
||||
...connector_specific_config
|
||||
} = values;
|
||||
|
||||
<AdminPageTitle
|
||||
includeDivider={false}
|
||||
icon={<SourceIcon iconSize={32} sourceType={connector} />}
|
||||
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<string, any>
|
||||
);
|
||||
|
||||
{formStep == 0 &&
|
||||
(connector == "google_drive" ? (
|
||||
<>
|
||||
<Card>
|
||||
<Title className="mb-2 text-lg">Select a credential</Title>
|
||||
<GDriveMain />
|
||||
</Card>
|
||||
<div className="mt-4 flex w-full justify-end">
|
||||
<button
|
||||
className="enabled:cursor-pointer disabled:bg-blue-200 bg-blue-400 flex gap-x-1 items-center text-white py-2.5 px-3.5 text-sm font-regular rounded-sm"
|
||||
disabled={!credentialActivated}
|
||||
onClick={() => nextFormStep()}
|
||||
>
|
||||
Continue
|
||||
<ArrowRight />
|
||||
</button>
|
||||
// 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<any>(
|
||||
{
|
||||
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 (
|
||||
<div className="mx-auto mb-8 w-full">
|
||||
{popup}
|
||||
|
||||
<div className="mb-4">
|
||||
<HealthCheckBanner />
|
||||
</div>
|
||||
</>
|
||||
) : connector == "gmail" ? (
|
||||
<>
|
||||
<Card>
|
||||
<Title className="mb-2 text-lg">Select a credential</Title>
|
||||
<GmailMain />
|
||||
</Card>
|
||||
<div className="mt-4 flex w-full justify-end">
|
||||
<button
|
||||
className="enabled:cursor-pointer disabled:bg-blue-200 bg-blue-400 flex gap-x-1 items-center text-white py-2.5 px-3.5 text-sm font-regular rounded-sm"
|
||||
disabled={!credentialActivated}
|
||||
onClick={() => nextFormStep()}
|
||||
>
|
||||
Continue
|
||||
<ArrowRight />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Card>
|
||||
<Title className="mb-2 text-lg">Select a credential</Title>
|
||||
<ModifyCredential
|
||||
showIfEmpty
|
||||
source={connector}
|
||||
defaultedCredential={currentCredential!}
|
||||
credentials={credentials}
|
||||
editableCredentials={editableCredentials}
|
||||
onDeleteCredential={onDeleteCredential}
|
||||
onSwitch={onSwap}
|
||||
/>
|
||||
{!createConnectorToggle && (
|
||||
<button
|
||||
className="mt-6 text-sm bg-background-900 px-2 py-1.5 flex text-text-200 flex-none rounded"
|
||||
onClick={() =>
|
||||
setCreateConnectorToggle(
|
||||
(createConnectorToggle) => !createConnectorToggle
|
||||
)
|
||||
}
|
||||
>
|
||||
Create New
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* 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 && (
|
||||
<Modal
|
||||
className="max-w-3xl rounded-lg"
|
||||
onOutsideClick={() => setCreateConnectorToggle(false)}
|
||||
>
|
||||
<>
|
||||
<Title className="mb-2 text-lg">
|
||||
Create a {getSourceDisplayName(connector)} credential
|
||||
</Title>
|
||||
<CreateCredential
|
||||
close
|
||||
refresh={refresh}
|
||||
sourceType={connector}
|
||||
setPopup={setPopup}
|
||||
onSwitch={onSwap}
|
||||
onClose={() => setCreateConnectorToggle(false)}
|
||||
/>
|
||||
</>
|
||||
</Modal>
|
||||
)}
|
||||
</Card>
|
||||
<div className="mt-4 flex w-full justify-end">
|
||||
<button
|
||||
className="enabled:cursor-pointer disabled:cursor-not-allowed disabled:bg-blue-200 bg-blue-400 flex gap-x-1 items-center text-white py-2.5 px-3.5 text-sm font-regular rounded-sm"
|
||||
disabled={currentCredential == null}
|
||||
onClick={() => nextFormStep()}
|
||||
>
|
||||
Continue
|
||||
<ArrowRight />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
<AdminPageTitle
|
||||
includeDivider={false}
|
||||
icon={<SourceIcon iconSize={32} sourceType={connector} />}
|
||||
title={displayName}
|
||||
/>
|
||||
|
||||
{formStep == 1 && (
|
||||
<>
|
||||
<Card>
|
||||
<Formik
|
||||
initialValues={formValues}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={() => {
|
||||
// Can be utilized for logging purposes
|
||||
}}
|
||||
>
|
||||
{(formikProps) => {
|
||||
setFormValues(formikProps.values);
|
||||
handleFormStatusChange(
|
||||
formikProps.isValid && isFormSubmittable(formikProps.values)
|
||||
);
|
||||
setAllowAdvanced(
|
||||
formikProps.isValid && isFormSubmittable(formikProps.values)
|
||||
);
|
||||
{formStep == 0 && (
|
||||
<Card>
|
||||
<Title className="mb-2 text-lg">Select a credential</Title>
|
||||
|
||||
return (
|
||||
<div className="w-full py-4 flex gap-y-6 flex-col max-w-2xl mx-auto">
|
||||
<DynamicConnectionForm
|
||||
values={formikProps.values}
|
||||
config={configuration}
|
||||
setSelectedFiles={setSelectedFiles}
|
||||
selectedFiles={selectedFiles}
|
||||
/>
|
||||
{isPaidEnterpriseFeaturesEnabled && (
|
||||
<>
|
||||
<IsPublicGroupSelector
|
||||
removeIndent
|
||||
formikProps={formikProps}
|
||||
objectName="Connector"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Formik>
|
||||
</Card>
|
||||
<div className={`mt-4 w-full grid grid-cols-3`}>
|
||||
{!noCredentials ? (
|
||||
<button
|
||||
className="border-border-dark mr-auto border flex gap-x-1 items-center text-text p-2.5 text-sm font-regular rounded-sm "
|
||||
onClick={() => prevFormStep()}
|
||||
>
|
||||
<ArrowLeft />
|
||||
Previous
|
||||
</button>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
<button
|
||||
className="enabled:cursor-pointer ml-auto disabled:bg-accent/50 disabled:cursor-not-allowed bg-accent flex mx-auto gap-x-1 items-center text-white py-2.5 px-3.5 text-sm font-regular rounded-sm"
|
||||
disabled={
|
||||
!isFormValid ||
|
||||
(connector == "file" && selectedFiles.length == 0)
|
||||
}
|
||||
onClick={async () => {
|
||||
await createConnector();
|
||||
}}
|
||||
>
|
||||
Create Connector
|
||||
<FiPlus className="text-white h-4 w-4" />
|
||||
</button>
|
||||
|
||||
{!(connector == "file") && (
|
||||
<div className="flex w-full justify-end">
|
||||
<button
|
||||
className={`enabled:cursor-pointer enabled:hover:underline disabled:cursor-not-allowed mt-auto enabled:text-text-600 disabled:text-text-400 ml-auto flex gap-x-1 items-center py-2.5 px-3.5 text-sm font-regular rounded-sm`}
|
||||
disabled={!isFormValid}
|
||||
onClick={() => {
|
||||
nextFormStep();
|
||||
}}
|
||||
>
|
||||
Advanced
|
||||
<ArrowRight />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{formStep === 2 && (
|
||||
<>
|
||||
<Card>
|
||||
<Formik
|
||||
initialValues={advancedSettings}
|
||||
validationSchema={advancedValidationSchema}
|
||||
onSubmit={() => {}}
|
||||
>
|
||||
{(formikProps) => {
|
||||
setAdvancedSettings(formikProps.values);
|
||||
|
||||
return (
|
||||
{connector == "google_drive" ? (
|
||||
<GDriveMain />
|
||||
) : connector == "gmail" ? (
|
||||
<GmailMain />
|
||||
) : (
|
||||
<>
|
||||
<AdvancedFormPage formikProps={formikProps} ref={formRef} />
|
||||
<div className="mt-4 flex w-full mx-auto max-w-2xl justify-start">
|
||||
<ModifyCredential
|
||||
showIfEmpty
|
||||
source={connector}
|
||||
defaultedCredential={currentCredential!}
|
||||
credentials={credentials}
|
||||
editableCredentials={editableCredentials}
|
||||
onDeleteCredential={onDeleteCredential}
|
||||
onSwitch={onSwap}
|
||||
/>
|
||||
{!createConnectorToggle && (
|
||||
<button
|
||||
className="flex gap-x-1 bg-red-500 hover:bg-red-500/80 items-center text-white py-2.5 px-3.5 text-sm font-regular rounded "
|
||||
onClick={() => resetAdvancedConfigs(formikProps)}
|
||||
className="mt-6 text-sm bg-background-900 px-2 py-1.5 flex text-text-200 flex-none rounded"
|
||||
onClick={() =>
|
||||
setCreateConnectorToggle(
|
||||
(createConnectorToggle) => !createConnectorToggle
|
||||
)
|
||||
}
|
||||
>
|
||||
<TrashIcon size={20} className="text-white" />
|
||||
<div className="w-full items-center gap-x-2 flex">
|
||||
Reset
|
||||
</div>
|
||||
Create New
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 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 && (
|
||||
<Modal
|
||||
className="max-w-3xl rounded-lg"
|
||||
onOutsideClick={() => setCreateConnectorToggle(false)}
|
||||
>
|
||||
<>
|
||||
<Title className="mb-2 text-lg">
|
||||
Create a {getSourceDisplayName(connector)}{" "}
|
||||
credential
|
||||
</Title>
|
||||
<CreateCredential
|
||||
close
|
||||
refresh={refresh}
|
||||
sourceType={connector}
|
||||
setPopup={setPopup}
|
||||
onSwitch={onSwap}
|
||||
onClose={() => setCreateConnectorToggle(false)}
|
||||
/>
|
||||
</>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Formik>
|
||||
</Card>
|
||||
<div className={`mt-4 grid grid-cols-3 w-full `}>
|
||||
<button
|
||||
className="border-border-dark border mr-auto flex gap-x-1 items-center text-text py-2.5 px-3.5 text-sm font-regular rounded-sm"
|
||||
onClick={() => prevFormStep()}
|
||||
>
|
||||
<ArrowLeft />
|
||||
Previous
|
||||
</button>
|
||||
<button
|
||||
className="enabled:cursor-pointer ml-auto disabled:bg-accent/50 bg-accent flex mx-auto gap-x-1 items-center text-white py-2.5 px-3.5 text-sm font-regular rounded-sm"
|
||||
onClick={async () => {
|
||||
await createConnector();
|
||||
}}
|
||||
>
|
||||
Create Connector
|
||||
<FiPlus className="text-white h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{formStep == 1 && (
|
||||
<Card className="w-full py-8 flex gap-y-6 flex-col max-w-3xl px-12 mx-auto">
|
||||
<DynamicConnectionForm
|
||||
values={formikProps.values}
|
||||
config={configuration}
|
||||
setSelectedFiles={setSelectedFiles}
|
||||
selectedFiles={selectedFiles}
|
||||
/>
|
||||
|
||||
<IsPublicGroupSelector
|
||||
removeIndent
|
||||
formikProps={formikProps}
|
||||
objectName="Connector"
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{formStep === 2 && (
|
||||
<Card>
|
||||
<AdvancedFormPage />
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<NavigationRow
|
||||
activatedCredential={credentialActivated != null}
|
||||
isValid={formikProps.isValid}
|
||||
onSubmit={formikProps.handleSubmit}
|
||||
noCredentials={noCredentials}
|
||||
noAdvanced={connector !== "file"}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
|
91
web/src/app/admin/connectors/[connector]/NavigationRow.tsx
Normal file
91
web/src/app/admin/connectors/[connector]/NavigationRow.tsx
Normal file
@@ -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;
|
||||
}) => (
|
||||
<button
|
||||
className={`flex items-center gap-1 text-sm rounded-sm ${className}`}
|
||||
onMouseDown={() => !disabled && onClick()}
|
||||
disabled={disabled}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mt-4 w-full grid grid-cols-3">
|
||||
<div>
|
||||
{formStep > 0 && !noCredentials && (
|
||||
<SquareNavigationButton
|
||||
className="border border-text-400 mr-auto p-2.5"
|
||||
onClick={prevFormStep}
|
||||
>
|
||||
<ArrowLeft />
|
||||
Previous
|
||||
</SquareNavigationButton>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center">
|
||||
{(formStep > 0 || noCredentials) && (
|
||||
<SquareNavigationButton
|
||||
className="bg-accent text-white py-2.5 px-3.5 disabled:opacity-50"
|
||||
disabled={!isValid}
|
||||
onClick={onSubmit}
|
||||
>
|
||||
Create Connector
|
||||
<FiPlus className="h-4 w-4" />
|
||||
</SquareNavigationButton>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
{formStep === 0 && (
|
||||
<SquareNavigationButton
|
||||
className="bg-blue-400 text-white py-2.5 px-3.5 disabled:bg-blue-200"
|
||||
disabled={!activatedCredential}
|
||||
onClick={nextFormStep}
|
||||
>
|
||||
Continue
|
||||
<ArrowRight />
|
||||
</SquareNavigationButton>
|
||||
)}
|
||||
{noAdvanced && formStep === 1 && (
|
||||
<SquareNavigationButton
|
||||
className="text-text-600 disabled:text-text-400 py-2.5 px-3.5"
|
||||
disabled={!isValid}
|
||||
onClick={nextFormStep}
|
||||
>
|
||||
Advanced
|
||||
<ArrowRight />
|
||||
</SquareNavigationButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default NavigationRow;
|
@@ -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 (
|
||||
<div className="py-4 flex flex-col gap-y-6 rounded-lg max-w-2xl mx-auto">
|
||||
<h2 className="text-2xl font-bold mb-4 text-text-800">
|
||||
Advanced Configuration
|
||||
</h2>
|
||||
|
||||
const AdvancedFormPage = forwardRef<FormikProps<any>, AdvancedFormPageProps>(
|
||||
({ formikProps }, ref) => {
|
||||
const { indexingStart, refreshFreq, pruneFreq } = formikProps.values;
|
||||
<NumberInput
|
||||
description={`
|
||||
Checks all documents against the source to delete those that no longer exist.
|
||||
Note: This process checks every document, so be cautious when increasing frequency.
|
||||
Default is 30 days.
|
||||
Enter 0 to disable pruning for this connector.
|
||||
`}
|
||||
label="Prune Frequency (days)"
|
||||
name="pruneFreq"
|
||||
/>
|
||||
|
||||
return (
|
||||
<div className="py-4 rounded-lg max-w-2xl mx-auto">
|
||||
<h2 className="text-2xl font-bold mb-4 text-text-800">
|
||||
Advanced Configuration
|
||||
</h2>
|
||||
<NumberInput
|
||||
description="This is how frequently we pull new documents from the source (in minutes). If you input 0, we will never pull new documents for this connector."
|
||||
label="Refresh Frequency (minutes)"
|
||||
name="refreshFreq"
|
||||
/>
|
||||
|
||||
<Form>
|
||||
<div key="prune_freq">
|
||||
<NumberInput
|
||||
description={`
|
||||
Checks all documents against the source to delete those that no longer exist.
|
||||
Note: This process checks every document, so be cautious when increasing frequency.
|
||||
Default is 30 days.
|
||||
Enter 0 to disable pruning for this connector.
|
||||
`}
|
||||
value={pruneFreq}
|
||||
label="Prune Frequency (days)"
|
||||
name="pruneFreq"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div key="refresh_freq">
|
||||
<NumberInput
|
||||
description="This is how frequently we pull new documents from the source (in minutes). If you input 0, we will never pull new documents for this connector."
|
||||
value={refreshFreq}
|
||||
label="Refresh Frequency (minutes)"
|
||||
name="refreshFreq"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div key="indexing_start">
|
||||
<TextFormField
|
||||
type="date"
|
||||
subtext="Documents prior to this date will not be pulled in"
|
||||
optional
|
||||
label="Indexing Start Date"
|
||||
name="indexingStart"
|
||||
value={indexingStart!}
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
<TextFormField
|
||||
type="date"
|
||||
subtext="Documents prior to this date will not be pulled in"
|
||||
optional
|
||||
label="Indexing Start Date"
|
||||
name="indexingStart"
|
||||
/>
|
||||
<div className="mt-4 flex w-full mx-auto max-w-2xl justify-start">
|
||||
<button className="flex gap-x-1 bg-red-500 hover:bg-red-500/80 items-center text-white py-2.5 px-3.5 text-sm font-regular rounded ">
|
||||
<TrashIcon size={20} className="text-white" />
|
||||
<div className="w-full items-center gap-x-2 flex">Reset</div>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
AdvancedFormPage.displayName = "AdvancedFormPage";
|
||||
export default AdvancedFormPage;
|
||||
|
@@ -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 (
|
||||
<div className="w-full flex flex-col">
|
||||
<label className="block text-base font-medium text-text-700 mb-1">
|
||||
@@ -32,15 +26,6 @@ export default function NumberInput({
|
||||
type="number"
|
||||
name={name}
|
||||
min="-1"
|
||||
value={value === 0 && showNeverIfZero ? "Never" : value}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue =
|
||||
e.target.value === "Never" ? 0 : Number(e.target.value);
|
||||
setFieldValue(name, newValue);
|
||||
if (onChange) {
|
||||
onChange(newValue);
|
||||
}
|
||||
}}
|
||||
className={`mt-2 block w-full px-3 py-2
|
||||
bg-white border border-gray-300 rounded-md
|
||||
text-sm shadow-sm placeholder-gray-400
|
||||
|
@@ -1,37 +1,42 @@
|
||||
import CredentialSubText from "@/components/credentials/CredentialFields";
|
||||
import { ListOption, SelectOption } from "@/lib/connectors/connectors";
|
||||
import {
|
||||
ListOption,
|
||||
SelectOption,
|
||||
StringWithDescription,
|
||||
} from "@/lib/connectors/connectors";
|
||||
import { Field } from "formik";
|
||||
|
||||
export default function SelectInput({
|
||||
field,
|
||||
value,
|
||||
name,
|
||||
optional,
|
||||
description,
|
||||
options,
|
||||
label,
|
||||
}: {
|
||||
field: SelectOption;
|
||||
value: any;
|
||||
name: string;
|
||||
optional?: boolean;
|
||||
description?: string;
|
||||
options: StringWithDescription[];
|
||||
label?: string;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<label
|
||||
htmlFor={field.name}
|
||||
htmlFor={name}
|
||||
className="block text-sm font-medium text-text-700 mb-1"
|
||||
>
|
||||
{field.label}
|
||||
{field.optional && (
|
||||
<span className="text-text-500 ml-1">(optional)</span>
|
||||
)}
|
||||
{label}
|
||||
{optional && <span className="text-text-500 ml-1">(optional)</span>}
|
||||
</label>
|
||||
{field.description && (
|
||||
<CredentialSubText>{field.description}</CredentialSubText>
|
||||
)}
|
||||
{description && <CredentialSubText>{description}</CredentialSubText>}
|
||||
|
||||
<Field
|
||||
as="select"
|
||||
value={value}
|
||||
name={field.name}
|
||||
name={name}
|
||||
className="w-full p-2 border bg-input border-border-medium rounded-md bg-black focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
|
||||
>
|
||||
<option value="">Select an option</option>
|
||||
{field.options?.map((option: any) => (
|
||||
{options?.map((option: any) => (
|
||||
<option key={option.name} value={option.name}>
|
||||
{option.name}
|
||||
</option>
|
||||
|
@@ -1,28 +1,9 @@
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
Dispatch,
|
||||
FC,
|
||||
SetStateAction,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Formik, Form, Field, FieldArray, FormikProps } from "formik";
|
||||
import * as Yup from "yup";
|
||||
import { FaPlus } from "react-icons/fa";
|
||||
import { useUserGroups } from "@/lib/hooks";
|
||||
import { UserGroup, User, UserRole } from "@/lib/types";
|
||||
import { Divider } from "@tremor/react";
|
||||
import React, { Dispatch, FC, SetStateAction } from "react";
|
||||
import CredentialSubText, {
|
||||
AdminBooleanFormField,
|
||||
} from "@/components/credentials/CredentialFields";
|
||||
import { TrashIcon } from "@/components/icons/icons";
|
||||
import { FileUpload } from "@/components/admin/connectors/FileUpload";
|
||||
import { ConnectionConfiguration } from "@/lib/connectors/connectors";
|
||||
import { useFormContext } from "@/components/context/FormContext";
|
||||
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
|
||||
import { Text } from "@tremor/react";
|
||||
import { getCurrentUser } from "@/lib/user";
|
||||
import { FiUsers } from "react-icons/fi";
|
||||
import SelectInput from "./ConnectorInput/SelectInput";
|
||||
import NumberInput from "./ConnectorInput/NumberInput";
|
||||
import { TextFormField } from "@/components/admin/connectors/Field";
|
||||
@@ -63,6 +44,7 @@ const DynamicConnectionForm: FC<DynamicConnectionFormProps> = ({
|
||||
<div key={field.name}>
|
||||
{field.type == "file" ? (
|
||||
<FileUpload
|
||||
name={field.name}
|
||||
selectedFiles={selectedFiles}
|
||||
setSelectedFiles={setSelectedFiles}
|
||||
/>
|
||||
@@ -78,11 +60,16 @@ const DynamicConnectionForm: FC<DynamicConnectionFormProps> = ({
|
||||
) : field.type === "list" ? (
|
||||
<ListInput field={field} />
|
||||
) : field.type === "select" ? (
|
||||
<SelectInput field={field} value={values[field.name]} />
|
||||
<SelectInput
|
||||
name={field.name}
|
||||
optional={field.optional}
|
||||
description={field.description}
|
||||
options={field.options || []}
|
||||
label={field.label}
|
||||
/>
|
||||
) : field.type === "number" ? (
|
||||
<NumberInput
|
||||
label={field.label}
|
||||
value={values[field.name]}
|
||||
optional={field.optional}
|
||||
description={field.description}
|
||||
name={field.name}
|
||||
|
@@ -2,14 +2,12 @@ import { PopupSpec } from "@/components/admin/connectors/Popup";
|
||||
import { createConnector, runConnector } from "@/lib/connector";
|
||||
import { createCredential, linkCredential } from "@/lib/credential";
|
||||
import { FileConfig } from "@/lib/connectors/connectors";
|
||||
import { AdvancedConfigFinal } from "../../AddConnectorPage";
|
||||
|
||||
export const submitFiles = async (
|
||||
selectedFiles: File[],
|
||||
setPopup: (popup: PopupSpec) => void,
|
||||
setSelectedFiles: (files: File[]) => void,
|
||||
name: string,
|
||||
advancedConfig: AdvancedConfigFinal,
|
||||
isPublic: boolean,
|
||||
groups?: number[]
|
||||
) => {
|
||||
|
@@ -2,13 +2,14 @@ import { PopupSpec } from "@/components/admin/connectors/Popup";
|
||||
import { createConnector, runConnector } from "@/lib/connector";
|
||||
import { linkCredential } from "@/lib/credential";
|
||||
import { GoogleSitesConfig } from "@/lib/connectors/connectors";
|
||||
import { AdvancedConfigFinal } from "../../AddConnectorPage";
|
||||
|
||||
export const submitGoogleSite = async (
|
||||
selectedFiles: File[],
|
||||
base_url: any,
|
||||
setPopup: (popup: PopupSpec) => void,
|
||||
advancedConfig: AdvancedConfigFinal,
|
||||
refreshFreq: number,
|
||||
pruneFreq: number,
|
||||
indexingStart: Date,
|
||||
name?: string
|
||||
) => {
|
||||
const uploadCreateAndTriggerConnector = async () => {
|
||||
@@ -41,9 +42,9 @@ export const submitGoogleSite = async (
|
||||
base_url: base_url,
|
||||
zip_path: filePaths[0],
|
||||
},
|
||||
refresh_freq: advancedConfig.refreshFreq,
|
||||
prune_freq: advancedConfig.pruneFreq,
|
||||
indexing_start: advancedConfig.indexingStart,
|
||||
refresh_freq: refreshFreq,
|
||||
prune_freq: pruneFreq,
|
||||
indexing_start: indexingStart,
|
||||
});
|
||||
if (connectorErrorMsg || !connector) {
|
||||
setPopup({
|
||||
|
@@ -66,14 +66,7 @@ const RerankingDetailsForm = forwardRef<
|
||||
>
|
||||
{({ values, setFieldValue, resetForm }) => {
|
||||
const resetRerankingValues = () => {
|
||||
setRerankingDetails({
|
||||
...values,
|
||||
rerank_provider_type: null!,
|
||||
rerank_model_name: null,
|
||||
});
|
||||
setFieldValue("rerank_provider_type", null);
|
||||
setFieldValue("rerank_model_name", null);
|
||||
setFieldValue("rerank_api_key", null);
|
||||
resetForm();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -346,7 +339,7 @@ const RerankingDetailsForm = forwardRef<
|
||||
}}
|
||||
type="password"
|
||||
label="Cohere API Key"
|
||||
name="api_key"
|
||||
name="rerank_api_key"
|
||||
/>
|
||||
<div className="flex w-full justify-end mt-4">
|
||||
<Button
|
||||
|
@@ -1,18 +1,19 @@
|
||||
import { EmbeddingProvider } from "@/components/embedding/interfaces";
|
||||
import { NonNullChain } from "typescript";
|
||||
|
||||
// This is a slightly differnte interface than used in the backend
|
||||
// but is always used in conjunction with `AdvancedSearchConfiguration`
|
||||
export interface RerankingDetails {
|
||||
rerank_model_name: string | null;
|
||||
rerank_provider_type: RerankerProvider | null;
|
||||
rerank_api_key: string | null;
|
||||
rerank_api_url: string | null;
|
||||
num_rerank: number;
|
||||
}
|
||||
|
||||
export enum RerankerProvider {
|
||||
COHERE = "cohere",
|
||||
LITELLM = "litellm",
|
||||
}
|
||||
|
||||
export interface AdvancedSearchConfiguration {
|
||||
model_name: string;
|
||||
model_dim: number;
|
||||
@@ -24,19 +25,12 @@ export interface AdvancedSearchConfiguration {
|
||||
multilingual_expansion: string[];
|
||||
disable_rerank_for_streaming: boolean;
|
||||
api_url: string | null;
|
||||
num_rerank: number;
|
||||
}
|
||||
|
||||
export interface SavedSearchSettings extends RerankingDetails {
|
||||
model_name: string;
|
||||
model_dim: number;
|
||||
normalize: boolean;
|
||||
query_prefix: string;
|
||||
passage_prefix: string;
|
||||
index_name: string | null;
|
||||
multipass_indexing: boolean;
|
||||
multilingual_expansion: string[];
|
||||
disable_rerank_for_streaming: boolean;
|
||||
api_url: string | null;
|
||||
export interface SavedSearchSettings
|
||||
extends RerankingDetails,
|
||||
AdvancedSearchConfiguration {
|
||||
provider_type: EmbeddingProvider | null;
|
||||
}
|
||||
|
||||
|
@@ -158,14 +158,7 @@ export function ProviderCreationModal({
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({
|
||||
values,
|
||||
errors,
|
||||
touched,
|
||||
isSubmitting,
|
||||
handleSubmit,
|
||||
setFieldValue,
|
||||
}) => (
|
||||
{({ isSubmitting, handleSubmit, setFieldValue }) => (
|
||||
<Form onSubmit={handleSubmit} className="space-y-4">
|
||||
<Text className="text-lg mb-2">
|
||||
You are setting the credentials for this provider. To access
|
||||
|
@@ -14,169 +14,102 @@ interface AdvancedEmbeddingFormPageProps {
|
||||
value: any
|
||||
) => void;
|
||||
advancedEmbeddingDetails: AdvancedSearchConfiguration;
|
||||
numRerank: number;
|
||||
updateNumRerank: (value: number) => void;
|
||||
}
|
||||
|
||||
const AdvancedEmbeddingFormPage = forwardRef<
|
||||
FormikProps<any>,
|
||||
AdvancedEmbeddingFormPageProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
updateAdvancedEmbeddingDetails,
|
||||
advancedEmbeddingDetails,
|
||||
numRerank,
|
||||
updateNumRerank,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<div className="py-4 rounded-lg max-w-4xl px-4 mx-auto">
|
||||
<h2 className="text-2xl font-bold mb-4 text-text-800">
|
||||
Advanced Configuration
|
||||
</h2>
|
||||
<Formik
|
||||
innerRef={ref}
|
||||
initialValues={{
|
||||
multilingual_expansion:
|
||||
advancedEmbeddingDetails.multilingual_expansion,
|
||||
multipass_indexing: advancedEmbeddingDetails.multipass_indexing,
|
||||
disable_rerank_for_streaming:
|
||||
advancedEmbeddingDetails.disable_rerank_for_streaming,
|
||||
num_rerank: numRerank,
|
||||
}}
|
||||
validationSchema={Yup.object().shape({
|
||||
multilingual_expansion: Yup.array().of(Yup.string()),
|
||||
multipass_indexing: Yup.boolean(),
|
||||
disable_rerank_for_streaming: Yup.boolean(),
|
||||
})}
|
||||
onSubmit={async (_, { setSubmitting }) => {
|
||||
setSubmitting(false);
|
||||
}}
|
||||
enableReinitialize={true}
|
||||
>
|
||||
{({ values, setFieldValue }) => (
|
||||
<Form>
|
||||
<FieldArray name="multilingual_expansion">
|
||||
{({ push, remove }) => (
|
||||
<div>
|
||||
<label
|
||||
htmlFor="multilingual_expansion"
|
||||
className="block text-sm font-medium text-text-700 mb-1"
|
||||
>
|
||||
Multilingual Expansion
|
||||
<span className="text-text-500 ml-1">(optional)</span>
|
||||
</label>
|
||||
<CredentialSubText>
|
||||
List of languages for multilingual expansion. Leave empty
|
||||
for no additional expansion.
|
||||
</CredentialSubText>
|
||||
{values.multilingual_expansion.map(
|
||||
(_: any, index: number) => (
|
||||
<div key={index} className="w-full flex mb-4">
|
||||
<Field
|
||||
name={`multilingual_expansion.${index}`}
|
||||
className={`w-full bg-input text-sm p-2 border border-border-medium rounded-md
|
||||
>(({ updateAdvancedEmbeddingDetails, advancedEmbeddingDetails }, ref) => {
|
||||
return (
|
||||
<div className="py-4 rounded-lg max-w-4xl px-4 mx-auto">
|
||||
<h2 className="text-2xl font-bold mb-4 text-text-800">
|
||||
Advanced Configuration
|
||||
</h2>
|
||||
<Formik
|
||||
innerRef={ref}
|
||||
initialValues={advancedEmbeddingDetails}
|
||||
validationSchema={Yup.object().shape({
|
||||
multilingual_expansion: Yup.array().of(Yup.string()),
|
||||
multipass_indexing: Yup.boolean(),
|
||||
disable_rerank_for_streaming: Yup.boolean(),
|
||||
num_rerank: Yup.number(),
|
||||
})}
|
||||
onSubmit={async (_, { setSubmitting }) => {
|
||||
setSubmitting(false);
|
||||
}}
|
||||
validate={(values) => {
|
||||
// Call updateAdvancedEmbeddingDetails for each changed field
|
||||
Object.entries(values).forEach(([key, value]) => {
|
||||
updateAdvancedEmbeddingDetails(
|
||||
key as keyof AdvancedSearchConfiguration,
|
||||
value
|
||||
);
|
||||
});
|
||||
}}
|
||||
enableReinitialize={true}
|
||||
>
|
||||
{({ values }) => (
|
||||
<Form>
|
||||
<FieldArray name="multilingual_expansion">
|
||||
{({ push, remove }) => (
|
||||
<div className="w-full">
|
||||
{values.multilingual_expansion.map(
|
||||
(_: any, index: number) => (
|
||||
<div key={index} className="w-full flex mb-4">
|
||||
<Field
|
||||
name={`multilingual_expansion.${index}`}
|
||||
className={`w-full bg-input text-sm p-2 border border-border-medium rounded-md
|
||||
focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 mr-2`}
|
||||
onChange={(
|
||||
e: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const newValue = [
|
||||
...values.multilingual_expansion,
|
||||
];
|
||||
newValue[index] = e.target.value;
|
||||
setFieldValue("multilingual_expansion", newValue);
|
||||
updateAdvancedEmbeddingDetails(
|
||||
"multilingual_expansion",
|
||||
newValue
|
||||
);
|
||||
}}
|
||||
value={values.multilingual_expansion[index]}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
remove(index);
|
||||
const newValue =
|
||||
values.multilingual_expansion.filter(
|
||||
(_: any, i: number) => i !== index
|
||||
);
|
||||
setFieldValue("multilingual_expansion", newValue);
|
||||
updateAdvancedEmbeddingDetails(
|
||||
"multilingual_expansion",
|
||||
newValue
|
||||
);
|
||||
}}
|
||||
className={`p-2 my-auto bg-input flex-none rounded-md
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => remove(index)}
|
||||
className={`p-2 my-auto bg-input flex-none rounded-md
|
||||
bg-red-500 text-white hover:bg-red-600
|
||||
focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-opacity-50`}
|
||||
>
|
||||
<TrashIcon className="text-white my-auto" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => push("")}
|
||||
className={`mt-2 p-2 bg-rose-500 text-xs text-white rounded-md flex items-center
|
||||
>
|
||||
<TrashIcon className="text-white my-auto" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => push("")}
|
||||
className={`mt-2 p-2 bg-rose-500 text-xs text-white rounded-md flex items-center
|
||||
hover:bg-rose-600 focus:outline-none focus:ring-2 focus:ring-rose-500 focus:ring-opacity-50`}
|
||||
>
|
||||
<FaPlus className="mr-2" />
|
||||
Add Language
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</FieldArray>
|
||||
>
|
||||
<FaPlus className="mr-2" />
|
||||
Add Language
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</FieldArray>
|
||||
|
||||
<BooleanFormField
|
||||
subtext="Enable multipass indexing for both mini and large chunks."
|
||||
optional
|
||||
checked={values.multipass_indexing}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const checked = e.target.checked;
|
||||
updateAdvancedEmbeddingDetails("multipass_indexing", checked);
|
||||
setFieldValue("multipass_indexing", checked);
|
||||
}}
|
||||
label="Multipass Indexing"
|
||||
name="multipassIndexing"
|
||||
/>
|
||||
<BooleanFormField
|
||||
subtext="Disable reranking for streaming to improve response time."
|
||||
optional
|
||||
checked={values.disable_rerank_for_streaming}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const checked = e.target.checked;
|
||||
updateAdvancedEmbeddingDetails(
|
||||
"disable_rerank_for_streaming",
|
||||
checked
|
||||
);
|
||||
setFieldValue("disable_rerank_for_streaming", checked);
|
||||
}}
|
||||
label="Disable Rerank for Streaming"
|
||||
name="disableRerankForStreaming"
|
||||
/>
|
||||
<NumberInput
|
||||
onChange={(value: number) => {
|
||||
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"
|
||||
/>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
<BooleanFormField
|
||||
subtext="Enable multipass indexing for both mini and large chunks."
|
||||
optional
|
||||
label="Multipass Indexing"
|
||||
name="multipass_indexing"
|
||||
/>
|
||||
<BooleanFormField
|
||||
subtext="Disable reranking for streaming to improve response time."
|
||||
optional
|
||||
label="Disable Rerank for Streaming"
|
||||
name="disable_rerank_for_streaming"
|
||||
/>
|
||||
<NumberInput
|
||||
description="Number of results to rerank"
|
||||
optional={false}
|
||||
label="Number of Results to Rerank"
|
||||
name="num_rerank"
|
||||
/>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
export default AdvancedEmbeddingFormPage;
|
||||
|
||||
AdvancedEmbeddingFormPage.displayName = "AdvancedEmbeddingFormPage";
|
||||
export default AdvancedEmbeddingFormPage;
|
||||
|
@@ -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<RerankingDetails>({
|
||||
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() {
|
||||
<>
|
||||
<Card>
|
||||
<AdvancedEmbeddingFormPage
|
||||
updateNumRerank={(newNumRerank: number) =>
|
||||
setRerankingDetails({
|
||||
...rerankingDetails,
|
||||
num_rerank: newNumRerank,
|
||||
})
|
||||
}
|
||||
numRerank={rerankingDetails.num_rerank}
|
||||
advancedEmbeddingDetails={advancedEmbeddingDetails}
|
||||
updateAdvancedEmbeddingDetails={updateAdvancedEmbeddingDetails}
|
||||
/>
|
||||
|
@@ -29,7 +29,7 @@ export const UserGroupCreationForm = ({
|
||||
const isUpdate = existingUserGroup !== undefined;
|
||||
|
||||
return (
|
||||
<Modal onOutsideClick={onClose}>
|
||||
<Modal className="w-fit" onOutsideClick={onClose}>
|
||||
<div className="px-8 py-6 bg-background">
|
||||
<h2 className="text-xl font-bold flex">
|
||||
{isUpdate ? "Update a User Group" : "Create a new User Group"}
|
||||
|
@@ -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 = <T extends IsPublicGroupSelectorFormType>({
|
||||
formikProps,
|
||||
objectName,
|
||||
@@ -27,10 +30,11 @@ export const IsPublicGroupSelector = <T extends IsPublicGroupSelectorFormType>({
|
||||
}) => {
|
||||
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 = <T extends IsPublicGroupSelectorFormType>({
|
||||
if (isLoadingUser || userGroupsIsLoading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
if (!isPaidEnterpriseFeaturesEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (shouldHideContent && enforceGroupSelection) {
|
||||
return (
|
||||
|
@@ -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<HTMLInputElement>) => 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<HTMLInputElement>) => 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 && <SubLabel>{subtext}</SubLabel>}
|
||||
<div className={`w-full flex ${includeRevert && "gap-x-2"}`}>
|
||||
<Field
|
||||
onChange={handleChange}
|
||||
min={min}
|
||||
as={isTextArea ? "textarea" : "input"}
|
||||
type={type}
|
||||
defaultValue={value}
|
||||
@@ -211,26 +216,7 @@ export function TextFormField({
|
||||
disabled={disabled}
|
||||
placeholder={placeholder}
|
||||
autoComplete={autoCompleteDisabled ? "off" : undefined}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{includeRevert && (
|
||||
<div className="flex-none mt-auto">
|
||||
<button
|
||||
className="text-xs h-[35px] my-auto p-1.5 rounded bg-background-900 border-border-dark text-text-300 flex gap-x-1"
|
||||
onClick={(e) => {
|
||||
if (onChange) {
|
||||
onChange({
|
||||
target: { value: "" },
|
||||
} as React.ChangeEvent<HTMLInputElement>);
|
||||
}
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<EditIcon className="text-netural-300 my-auto" />
|
||||
<p className="my-auto">Revert</p>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{explanationText && (
|
||||
|
@@ -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<FileUploadProps> = ({
|
||||
name,
|
||||
selectedFiles,
|
||||
setSelectedFiles,
|
||||
message,
|
||||
}) => {
|
||||
const [dragActive, setDragActive] = useState(false);
|
||||
const { setFieldValue } = useFormikContext();
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -21,6 +25,9 @@ export const FileUpload: FC<FileUploadProps> = ({
|
||||
onDrop={(acceptedFiles) => {
|
||||
setSelectedFiles(acceptedFiles);
|
||||
setDragActive(false);
|
||||
if (name) {
|
||||
setFieldValue(name, acceptedFiles);
|
||||
}
|
||||
}}
|
||||
onDragLeave={() => setDragActive(false)}
|
||||
onDragEnter={() => setDragActive(true)}
|
||||
|
@@ -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 && (
|
||||
<IsPublicGroupSelector
|
||||
formikProps={formikProps}
|
||||
objectName="credential"
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { Button, Text, Card } from "@tremor/react";
|
||||
|
||||
import { FaNewspaper } from "react-icons/fa";
|
||||
import { FaNewspaper, FaPaperPlane, FaTractor, FaTrash } from "react-icons/fa";
|
||||
import { TextFormField } from "@/components/admin/connectors/Field";
|
||||
import { Form, Formik, FormikHelpers } from "formik";
|
||||
import { PopupSpec } from "@/components/admin/connectors/Popup";
|
||||
@@ -48,7 +48,7 @@ const EditCredential = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<div className="flex flex-col gap-y-6">
|
||||
<Text>
|
||||
Ensure that you update to a credential with the proper permissions!
|
||||
</Text>
|
||||
@@ -58,37 +58,37 @@ const EditCredential = ({
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{({ isSubmitting, setFieldValue }) => (
|
||||
{({ isSubmitting, resetForm }) => (
|
||||
<Form>
|
||||
<Card className="mt-4 flex flex-col gap-y-4">
|
||||
<TextFormField
|
||||
includeRevert
|
||||
name="name"
|
||||
placeholder={credential.name || ""}
|
||||
label="Name (optional):"
|
||||
/>
|
||||
|
||||
{Object.entries(credential.credential_json).map(([key, value]) => (
|
||||
<TextFormField
|
||||
includeRevert
|
||||
onChange={(e) => 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]) => (
|
||||
<TextFormField
|
||||
includeRevert
|
||||
key={key}
|
||||
onChange={(e) => setFieldValue(key, e.target.value)}
|
||||
name={key}
|
||||
placeholder={value}
|
||||
label={getDisplayNameForCredentialKey(key)}
|
||||
type={
|
||||
key.toLowerCase().includes("token") ||
|
||||
key.toLowerCase().includes("password")
|
||||
? "password"
|
||||
: "text"
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Card>
|
||||
<div className="flex mt-8 justify-end">
|
||||
))}
|
||||
<div className="flex justify-between w-full">
|
||||
<Button type="button" onClick={() => resetForm()}>
|
||||
<div className="flex gap-x-2 items-center w-full border-none">
|
||||
<FaTrash />
|
||||
<p>Reset Changes</p>
|
||||
</div>
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
|
@@ -45,7 +45,7 @@ export function CustomModelForm({
|
||||
});
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting, setFieldValue }) => (
|
||||
{({ isSubmitting }) => (
|
||||
<Form>
|
||||
<TextFormField
|
||||
name="model_name"
|
||||
@@ -61,15 +61,10 @@ export function CustomModelForm({
|
||||
subtext="The dimensionality of the embeddings generated by the model"
|
||||
placeholder="E.g. '768'"
|
||||
autoCompleteDisabled={true}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
// Allow only integer values
|
||||
if (value === "" || /^[0-9]+$/.test(value)) {
|
||||
setFieldValue("model_dim", value);
|
||||
}
|
||||
}}
|
||||
type="number"
|
||||
/>
|
||||
<TextFormField
|
||||
min={-1}
|
||||
name="description"
|
||||
label="Description:"
|
||||
subtext="Description of your model"
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import * as Yup from "yup";
|
||||
import { IsPublicGroupSelectorFormType } from "@/components/IsPublicGroupSelector";
|
||||
import { ConfigurableSources, ValidInputTypes, ValidSources } from "../types";
|
||||
|
||||
export type InputType =
|
||||
@@ -808,6 +810,68 @@ For example, specifying .*-support.* as a "channel" will cause the connector to
|
||||
],
|
||||
},
|
||||
};
|
||||
export function createConnectorInitialValues(
|
||||
connector: ConfigurableSources
|
||||
): Record<string, any> & IsPublicGroupSelectorFormType {
|
||||
const configuration = connectorConfigs[connector];
|
||||
|
||||
return {
|
||||
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 }
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function createConnectorValidationSchema(
|
||||
connector: ConfigurableSources
|
||||
): Yup.ObjectSchema<Record<string, any>> {
|
||||
const configuration = connectorConfigs[connector];
|
||||
|
||||
return 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()
|
||||
: field.type === "file"
|
||||
? Yup.mixed()
|
||||
: Yup.string();
|
||||
|
||||
if (!field.optional) {
|
||||
schema = schema.required(`${field.label} is required`);
|
||||
}
|
||||
|
||||
acc[field.name] = schema;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
),
|
||||
// These are advanced settings
|
||||
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"),
|
||||
});
|
||||
}
|
||||
|
||||
// CONNECTORS
|
||||
export interface ConnectorBase<T> {
|
||||
|
Reference in New Issue
Block a user