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
17 changed files with 164 additions and 127 deletions

View File

@@ -170,7 +170,7 @@ def associate_credential_to_connector(
connector_id=connector_id, connector_id=connector_id,
credential_id=credential_id, credential_id=credential_id,
cc_pair_name=metadata.name, 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, groups=metadata.groups,
) )

View File

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

View File

@@ -112,7 +112,7 @@ export default function Page() {
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
onKeyDown={handleKeyPress} 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) {Object.entries(categorizedSources)

View File

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

View File

@@ -1,5 +1,5 @@
import { SubLabel } from "@/components/admin/connectors/Field"; import { SubLabel } from "@/components/admin/connectors/Field";
import { Field } from "formik"; import { Field, useFormikContext } from "formik";
export default function NumberInput({ export default function NumberInput({
label, label,
@@ -8,6 +8,7 @@ export default function NumberInput({
description, description,
name, name,
showNeverIfZero, showNeverIfZero,
onChange,
}: { }: {
value?: number; value?: number;
label: string; label: string;
@@ -15,7 +16,10 @@ export default function NumberInput({
optional?: boolean; optional?: boolean;
description?: string; description?: string;
showNeverIfZero?: boolean; showNeverIfZero?: boolean;
onChange?: (value: number) => void;
}) { }) {
const { setFieldValue } = useFormikContext();
return ( return (
<div className="w-full flex flex-col"> <div className="w-full flex flex-col">
<label className="block text-base font-medium text-text-700 mb-1"> <label className="block text-base font-medium text-text-700 mb-1">
@@ -29,6 +33,14 @@ export default function NumberInput({
name={name} name={name}
min="-1" min="-1"
value={value === 0 && showNeverIfZero ? "Never" : value} 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 className={`mt-2 block w-full px-3 py-2
bg-white border border-gray-300 rounded-md bg-white border border-gray-300 rounded-md
text-sm shadow-sm placeholder-gray-400 text-sm shadow-sm placeholder-gray-400

View File

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

View File

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

View File

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

View File

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

View File

@@ -457,7 +457,7 @@ export function CCPairIndexingStatusTable({
placeholder="Search connectors..." placeholder="Search connectors..."
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} 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()}> <Button className="h-9" onClick={() => toggleSources()}>

View File

@@ -1,12 +1,9 @@
"use client"; "use client";
import ReactMarkdown from "react-markdown";
import { SettingsContext } from "@/components/settings/SettingsProvider"; import { SettingsContext } from "@/components/settings/SettingsProvider";
import { useContext, useState, useRef, useLayoutEffect } from "react"; import { useContext, useState, useRef, useLayoutEffect } from "react";
import remarkGfm from "remark-gfm";
import { Popover } from "@/components/popover/Popover"; import { Popover } from "@/components/popover/Popover";
import { ChevronDownIcon } from "@/components/icons/icons"; import { ChevronDownIcon } from "@/components/icons/icons";
import { Divider } from "@tremor/react";
import { MinimalMarkdown } from "@/components/chat_search/MinimalMarkdown"; import { MinimalMarkdown } from "@/components/chat_search/MinimalMarkdown";
export function ChatBanner() { export function ChatBanner() {
@@ -34,27 +31,6 @@ export function ChatBanner() {
return null; 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 ( return (
<div <div
className={` className={`
@@ -65,7 +41,6 @@ export function ChatBanner() {
w-full w-full
mx-auto mx-auto
relative relative
bg-background-100
shadow-sm shadow-sm
rounded rounded
border-l-8 border-l-400 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" className="line-clamp-2 text-center w-full overflow-hidden pr-8"
> >
<MinimalMarkdown <MinimalMarkdown
className=""
content={settings.enterpriseSettings.custom_header_content} content={settings.enterpriseSettings.custom_header_content}
/> />
</div> </div>
@@ -90,7 +64,6 @@ export function ChatBanner() {
className="absolute top-0 left-0 invisible w-full" className="absolute top-0 left-0 invisible w-full"
> >
<MinimalMarkdown <MinimalMarkdown
className=""
content={settings.enterpriseSettings.custom_header_content} content={settings.enterpriseSettings.custom_header_content}
/> />
</div> </div>

View File

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

View File

@@ -149,6 +149,17 @@ export function TextFormField({
if (isTextArea && !heightString) { if (isTextArea && !heightString) {
heightString = "h-28"; 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 ( return (
<div className="w-full"> <div className="w-full">
@@ -200,7 +211,7 @@ export function TextFormField({
disabled={disabled} disabled={disabled}
placeholder={placeholder} placeholder={placeholder}
autoComplete={autoCompleteDisabled ? "off" : undefined} autoComplete={autoCompleteDisabled ? "off" : undefined}
// onChange={onChange} onChange={handleChange}
/> />
{includeRevert && ( {includeRevert && (
<div className="flex-none mt-auto"> <div className="flex-none mt-auto">
@@ -396,17 +407,27 @@ export const BooleanFormField = ({
alignTop, alignTop,
checked, checked,
}: BooleanFormFieldProps) => { }: 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 ( return (
<div> <div>
<label className="flex text-sm"> <label className="flex text-sm">
<Field <Field
disabled={disabled}
name={name}
checked={checked}
type="checkbox" type="checkbox"
{...field}
checked={checked !== undefined ? checked : field.value}
disabled={disabled}
onChange={handleChange}
className={`${removeIndent ? "mr-2" : "mx-3"} className={`${removeIndent ? "mr-2" : "mx-3"}
px-5 w-3.5 h-3.5 ${alignTop ? "mt-1" : "my-auto"}`} px-5 w-3.5 h-3.5 ${alignTop ? "mt-1" : "my-auto"}`}
{...(onChange ? { onChange } : {})}
/> />
{!noLabel && ( {!noLabel && (
<div> <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 { NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA } from "@/lib/constants";
import { ChatSession } from "@/app/chat/interfaces"; import { ChatSession } from "@/app/chat/interfaces";
import Link from "next/link"; import Link from "next/link";
import { SettingsContext } from "../settings/SettingsProvider";
import { pageType } from "@/app/chat/sessionSidebar/types"; import { pageType } from "@/app/chat/sessionSidebar/types";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { ChatBanner } from "@/app/chat/ChatBanner"; import { ChatBanner } from "@/app/chat/ChatBanner";
@@ -65,7 +64,7 @@ export default function FunctionalHeader({
router.push(newChatUrl); router.push(newChatUrl);
}; };
return ( 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"> <div className="mt-2 mx-2.5 cursor-pointer text-text-700 relative flex w-full">
<LogoType <LogoType
assistantId={currentChatSession?.persona_id} assistantId={currentChatSession?.persona_id}
@@ -130,7 +129,7 @@ export default function FunctionalHeader({
</div> </div>
{page != "assistants" && ( {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> </div>
); );

View File

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

View File

@@ -115,7 +115,7 @@ export function TagFilter({
<FiTag className="mr-1 my-auto" /> <FiTag className="mr-1 my-auto" />
Tags Tags
</div> </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.length > 0 ? (
filteredTags.map((tag) => ( filteredTags.map((tag) => (
<div <div

View File

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