Minor search UX improvements + Critical connector fixes (#2259)

This commit is contained in:
pablodanswer 2024-08-30 11:47:52 -07:00 committed by GitHub
parent 8f26728a29
commit 183569061b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 164 additions and 127 deletions

View File

@ -170,7 +170,7 @@ def associate_credential_to_connector(
connector_id=connector_id,
credential_id=credential_id,
cc_pair_name=metadata.name,
is_public=metadata.is_public or True,
is_public=True if metadata.is_public is None else metadata.is_public,
groups=metadata.groups,
)

View File

@ -552,6 +552,7 @@ def create_connector_from_model(
try:
_validate_connector_allowed(connector_data.source)
connector_base = _check_connector_permissions(connector_data, user)
return create_connector(
db_session=db_session,
connector_data=connector_base,

View File

@ -112,7 +112,7 @@ export default function Page() {
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
onKeyDown={handleKeyPress}
className="flex mt-2 max-w-sm h-9 w-full rounded-md border-2 border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
className="ml-1 w-96 h-9 flex-none rounded-md border border-border bg-background-50 px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
/>
{Object.entries(categorizedSources)

View File

@ -44,7 +44,6 @@ import {
IsPublicGroupSelectorFormType,
} from "@/components/IsPublicGroupSelector";
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
import { AdminBooleanFormField } from "@/components/credentials/CredentialFields";
export type AdvancedConfigFinal = {
pruneFreq: number | null;
@ -489,6 +488,7 @@ export default function AddConnector({
}}
>
{(formikProps) => {
console.log(formikProps.values);
setFormValues(formikProps.values);
handleFormStatusChange(
formikProps.isValid && isFormSubmittable(formikProps.values)

View File

@ -1,5 +1,5 @@
import { SubLabel } from "@/components/admin/connectors/Field";
import { Field } from "formik";
import { Field, useFormikContext } from "formik";
export default function NumberInput({
label,
@ -8,6 +8,7 @@ export default function NumberInput({
description,
name,
showNeverIfZero,
onChange,
}: {
value?: number;
label: string;
@ -15,7 +16,10 @@ export default function NumberInput({
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">
@ -29,6 +33,14 @@ export default function NumberInput({
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

View File

@ -92,7 +92,7 @@ const RerankingDetailsForm = forwardRef<
.nullable()
.oneOf(Object.values(RerankerProvider))
.optional(),
api_key: Yup.string().nullable(),
rerank_api_key: Yup.string().nullable(),
num_rerank: Yup.number().min(1, "Must be at least 1"),
})}
onSubmit={async (_, { setSubmitting }) => {
@ -132,6 +132,7 @@ const RerankingDetailsForm = forwardRef<
...values,
rerank_provider_type: card.rerank_provider_type!,
rerank_model_name: card.modelName,
rerank_api_key: null,
});
setFieldValue(
"rerank_provider_type",
@ -192,15 +193,18 @@ const RerankingDetailsForm = forwardRef<
>
<div className="w-full px-4">
<TextFormField
placeholder={values.api_key || undefined}
placeholder={values.rerank_api_key || undefined}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setRerankingDetails({ ...values, api_key: value });
setFieldValue("api_key", value);
setRerankingDetails({
...values,
rerank_api_key: value,
});
setFieldValue("rerank_api_key", value);
}}
type="password"
label="Cohere API Key"
name="api_key"
name="rerank_api_key"
/>
<div className="mt-4 flex justify-between">
<Button

View File

@ -4,7 +4,7 @@ import { NonNullChain } from "typescript";
export interface RerankingDetails {
rerank_model_name: string | null;
rerank_provider_type: RerankerProvider | null;
api_key: string | null;
rerank_api_key: string | null;
num_rerank: number;
}

View File

@ -15,6 +15,7 @@ interface AdvancedEmbeddingFormPageProps {
) => void;
advancedEmbeddingDetails: AdvancedSearchConfiguration;
numRerank: number;
updateNumRerank: (value: number) => void;
}
const AdvancedEmbeddingFormPage = forwardRef<
@ -22,7 +23,12 @@ const AdvancedEmbeddingFormPage = forwardRef<
AdvancedEmbeddingFormPageProps
>(
(
{ updateAdvancedEmbeddingDetails, advancedEmbeddingDetails, numRerank },
{
updateAdvancedEmbeddingDetails,
advancedEmbeddingDetails,
numRerank,
updateNumRerank,
},
ref
) => {
return (
@ -154,6 +160,10 @@ const AdvancedEmbeddingFormPage = forwardRef<
name="disableRerankForStreaming"
/>
<NumberInput
onChange={(value: number) => {
updateNumRerank(value);
setFieldValue("num_rerank", value);
}}
description="Number of results to rerank"
optional={false}
value={values.num_rerank}

View File

@ -44,7 +44,7 @@ export default function EmbeddingForm() {
});
const [rerankingDetails, setRerankingDetails] = useState<RerankingDetails>({
api_key: "",
rerank_api_key: "",
num_rerank: 0,
rerank_provider_type: null,
rerank_model_name: "",
@ -118,7 +118,7 @@ export default function EmbeddingForm() {
searchSettings.disable_rerank_for_streaming,
});
setRerankingDetails({
api_key: searchSettings.api_key,
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,
@ -128,13 +128,13 @@ export default function EmbeddingForm() {
const originalRerankingDetails: RerankingDetails = searchSettings
? {
api_key: searchSettings.api_key,
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,
}
: {
api_key: "",
rerank_api_key: "",
num_rerank: 0,
rerank_provider_type: null,
rerank_model_name: "",
@ -415,6 +415,12 @@ export default function EmbeddingForm() {
<>
<Card>
<AdvancedEmbeddingFormPage
updateNumRerank={(value: number) =>
setRerankingDetails({
...rerankingDetails,
num_rerank: value,
})
}
numRerank={rerankingDetails.num_rerank}
advancedEmbeddingDetails={advancedEmbeddingDetails}
updateAdvancedEmbeddingDetails={updateAdvancedEmbeddingDetails}

View File

@ -457,7 +457,7 @@ export function CCPairIndexingStatusTable({
placeholder="Search connectors..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="ml-2 w-96 h-9 flex-none rounded-md border border-border bg-background-50 px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
className="ml-1 w-96 h-9 flex-none rounded-md border border-border bg-background-50 px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
/>
<Button className="h-9" onClick={() => toggleSources()}>

View File

@ -1,12 +1,9 @@
"use client";
import ReactMarkdown from "react-markdown";
import { SettingsContext } from "@/components/settings/SettingsProvider";
import { useContext, useState, useRef, useLayoutEffect } from "react";
import remarkGfm from "remark-gfm";
import { Popover } from "@/components/popover/Popover";
import { ChevronDownIcon } from "@/components/icons/icons";
import { Divider } from "@tremor/react";
import { MinimalMarkdown } from "@/components/chat_search/MinimalMarkdown";
export function ChatBanner() {
@ -34,27 +31,6 @@ export function ChatBanner() {
return null;
}
const renderMarkdown = (className: string) => (
<ReactMarkdown
className={`w-full text-wrap break-word ${className}`}
components={{
a: ({ node, ...props }) => (
<a
{...props}
className="text-sm text-link hover:text-link-hover"
target="_blank"
rel="noopener noreferrer"
/>
),
p: ({ node, ...props }) => (
<p {...props} className="text-wrap break-word text-sm m-0 w-full" />
),
}}
remarkPlugins={[remarkGfm]}
>
{settings.enterpriseSettings?.custom_header_content}
</ReactMarkdown>
);
return (
<div
className={`
@ -65,7 +41,6 @@ export function ChatBanner() {
w-full
mx-auto
relative
bg-background-100
shadow-sm
rounded
border-l-8 border-l-400
@ -81,7 +56,6 @@ export function ChatBanner() {
className="line-clamp-2 text-center w-full overflow-hidden pr-8"
>
<MinimalMarkdown
className=""
content={settings.enterpriseSettings.custom_header_content}
/>
</div>
@ -90,7 +64,6 @@ export function ChatBanner() {
className="absolute top-0 left-0 invisible w-full"
>
<MinimalMarkdown
className=""
content={settings.enterpriseSettings.custom_header_content}
/>
</div>

View File

@ -22,7 +22,7 @@ export const IsPublicGroupSelector = <T extends IsPublicGroupSelectorFormType>({
enforceGroupSelection?: boolean;
}) => {
const { data: userGroups, isLoading: userGroupsIsLoading } = useUserGroups();
const { isAdmin, user, isLoadingUser } = useUser();
const { isAdmin, user, isLoadingUser, isCurator } = useUser();
const [shouldHideContent, setShouldHideContent] = useState(false);
useEffect(() => {
@ -87,42 +87,45 @@ export const IsPublicGroupSelector = <T extends IsPublicGroupSelectorFormType>({
)}
{(!formikProps.values.is_public ||
!isAdmin ||
formikProps.values.groups.length > 0) && (
<>
<div className="flex mt-4 gap-x-2 items-center">
<div className="block font-medium text-base">
Assign group access for this {objectName}
</div>
</div>
<Text className="mb-3">
{isAdmin || !enforceGroupSelection ? (
<>
This {objectName} will be visible/accessible by the groups
selected below
</>
) : (
<>
Curators must select one or more groups to give access to this{" "}
{objectName}
</>
)}
</Text>
<FieldArray
name="groups"
render={(arrayHelpers: ArrayHelpers) => (
<div className="flex gap-2 flex-wrap mb-4">
{userGroupsIsLoading ? (
<div className="animate-pulse bg-gray-200 h-8 w-32 rounded"></div>
isCurator ||
formikProps.values.groups.length > 0) &&
(userGroupsIsLoading ? (
<div className="animate-pulse bg-gray-200 h-8 w-32 rounded"></div>
) : (
userGroups &&
userGroups.length > 0 && (
<>
<div className="flex mt-4 gap-x-2 items-center">
<div className="block font-medium text-base">
Assign group access for this {objectName}
</div>
</div>
<Text className="mb-3">
{isAdmin || !enforceGroupSelection ? (
<>
This {objectName} will be visible/accessible by the groups
selected below
</>
) : (
userGroups &&
userGroups.map((userGroup: UserGroup) => {
const ind = formikProps.values.groups.indexOf(userGroup.id);
let isSelected = ind !== -1;
return (
<div
key={userGroup.id}
className={`
<>
Curators must select one or more groups to give access to
this {objectName}
</>
)}
</Text>
<FieldArray
name="groups"
render={(arrayHelpers: ArrayHelpers) => (
<div className="flex gap-2 flex-wrap mb-4">
{userGroups.map((userGroup: UserGroup) => {
const ind = formikProps.values.groups.indexOf(
userGroup.id
);
let isSelected = ind !== -1;
return (
<div
key={userGroup.id}
className={`
px-3
py-1
rounded-lg
@ -132,32 +135,33 @@ export const IsPublicGroupSelector = <T extends IsPublicGroupSelectorFormType>({
flex
cursor-pointer
${isSelected ? "bg-background-strong" : "hover:bg-hover"}
`}
onClick={() => {
if (isSelected) {
arrayHelpers.remove(ind);
} else {
arrayHelpers.push(userGroup.id);
}
}}
>
<div className="my-auto flex">
<FiUsers className="my-auto mr-2" /> {userGroup.name}
`}
onClick={() => {
if (isSelected) {
arrayHelpers.remove(ind);
} else {
arrayHelpers.push(userGroup.id);
}
}}
>
<div className="my-auto flex">
<FiUsers className="my-auto mr-2" />{" "}
{userGroup.name}
</div>
</div>
</div>
);
})
);
})}
</div>
)}
</div>
)}
/>
<ErrorMessage
name="groups"
component="div"
className="text-error text-sm mt-1"
/>
</>
)}
/>
<ErrorMessage
name="groups"
component="div"
className="text-error text-sm mt-1"
/>
</>
)
))}
</div>
);
};

View File

@ -149,6 +149,17 @@ export function TextFormField({
if (isTextArea && !heightString) {
heightString = "h-28";
}
const [field, , helpers] = useField(name);
const { setValue } = helpers;
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
setValue(e.target.value);
if (onChange) {
onChange(e as React.ChangeEvent<HTMLInputElement>);
}
};
return (
<div className="w-full">
@ -200,7 +211,7 @@ export function TextFormField({
disabled={disabled}
placeholder={placeholder}
autoComplete={autoCompleteDisabled ? "off" : undefined}
// onChange={onChange}
onChange={handleChange}
/>
{includeRevert && (
<div className="flex-none mt-auto">
@ -396,17 +407,27 @@ export const BooleanFormField = ({
alignTop,
checked,
}: BooleanFormFieldProps) => {
const [field, meta, helpers] = useField<boolean>(name);
const { setValue } = helpers;
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.checked);
if (onChange) {
onChange(e);
}
};
return (
<div>
<label className="flex text-sm">
<Field
disabled={disabled}
name={name}
checked={checked}
type="checkbox"
{...field}
checked={checked !== undefined ? checked : field.value}
disabled={disabled}
onChange={handleChange}
className={`${removeIndent ? "mr-2" : "mx-3"}
px-5 w-3.5 h-3.5 ${alignTop ? "mt-1" : "my-auto"}`}
{...(onChange ? { onChange } : {})}
px-5 w-3.5 h-3.5 ${alignTop ? "mt-1" : "my-auto"}`}
/>
{!noLabel && (
<div>

View File

@ -7,7 +7,6 @@ import { NewChatIcon } from "../icons/icons";
import { NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA } from "@/lib/constants";
import { ChatSession } from "@/app/chat/interfaces";
import Link from "next/link";
import { SettingsContext } from "../settings/SettingsProvider";
import { pageType } from "@/app/chat/sessionSidebar/types";
import { useRouter } from "next/navigation";
import { ChatBanner } from "@/app/chat/ChatBanner";
@ -65,7 +64,7 @@ export default function FunctionalHeader({
router.push(newChatUrl);
};
return (
<div className="pb-6 left-0 sticky top-0 z-20 w-full relative flex">
<div className="left-0 sticky top-0 z-20 w-full relative flex">
<div className="mt-2 mx-2.5 cursor-pointer text-text-700 relative flex w-full">
<LogoType
assistantId={currentChatSession?.persona_id}
@ -130,7 +129,7 @@ export default function FunctionalHeader({
</div>
{page != "assistants" && (
<div className="h-24 left-0 absolute top-0 z-10 w-full bg-gradient-to-b via-50% z-[-1] from-background via-background to-background/10 flex" />
<div className="h-20 left-0 absolute top-0 z-10 w-full bg-gradient-to-b via-50% z-[-1] from-background via-background to-background/10 flex" />
)}
</div>
);

View File

@ -111,6 +111,19 @@ export function SourceSelector({
<DateRangeSelector value={timeRange} onValueChange={setTimeRange} />
</div>
{availableTags.length > 0 && (
<>
<div className="mt-4 mb-2">
<SectionTitle>Tags</SectionTitle>
</div>
<TagFilter
tags={availableTags}
selectedTags={selectedTags}
setSelectedTags={setSelectedTags}
/>
</>
)}
{existingSources.length > 0 && (
<div className="mt-4">
<div className="flex w-full gap-x-2 items-center">
@ -191,19 +204,6 @@ export function SourceSelector({
</div>
</>
)}
{availableTags.length > 0 && (
<>
<div className="mt-4 mb-2">
<SectionTitle>Tags</SectionTitle>
</div>
<TagFilter
tags={availableTags}
selectedTags={selectedTags}
setSelectedTags={setSelectedTags}
/>
</>
)}
</div>
);
}

View File

@ -115,7 +115,7 @@ export function TagFilter({
<FiTag className="mr-1 my-auto" />
Tags
</div>
<div className="flex flex-wrap gap-x-1 gap-y-1">
<div className="flex overflow-y-scroll max-h-96 flex-wrap gap-x-1 gap-y-1">
{filteredTags.length > 0 ? (
filteredTags.map((tag) => (
<div

View File

@ -8,6 +8,7 @@ interface UserContextType {
user: User | null;
isLoadingUser: boolean;
isAdmin: boolean;
isCurator: boolean;
refreshUser: () => Promise<void>;
}
@ -17,12 +18,16 @@ export function UserProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [isLoadingUser, setIsLoadingUser] = useState(true);
const [isAdmin, setIsAdmin] = useState(false);
const [isCurator, setIsCurator] = useState(false);
const fetchUser = async () => {
try {
const user = await getCurrentUser();
setUser(user);
setIsAdmin(user?.role === UserRole.ADMIN);
setIsCurator(
user?.role === UserRole.CURATOR || user?.role == UserRole.GLOBAL_CURATOR
);
} catch (error) {
console.error("Error fetching current user:", error);
} finally {
@ -40,7 +45,9 @@ export function UserProvider({ children }: { children: React.ReactNode }) {
};
return (
<UserContext.Provider value={{ user, isLoadingUser, isAdmin, refreshUser }}>
<UserContext.Provider
value={{ user, isLoadingUser, isAdmin, refreshUser, isCurator }}
>
{children}
</UserContext.Provider>
);