🔖 feat: Enhance Bookmarks UX, add RBAC, toggle via librechat.yaml (#3747)

* chore: update package version to 0.7.416

* chore: Update Role.js imports order

* refactor: move updateTagsInConvo to tags route, add RBAC for tags

* refactor: add updateTagsInConvoOptions

* fix: loading state for bookmark form

* refactor: update primaryText class in TitleButton component

* refactor: remove duplicate bookmarks and theming

* refactor: update EditIcon component to use React.forwardRef

* refactor: add _id field to tConversationTagSchema

* refactor: remove promises

* refactor: move mutation logic from BookmarkForm -> BookmarkEditDialog

* refactor: update button class in BookmarkForm component

* fix: conversation mutations and add better logging to useConversationTagMutation

* refactor: update logger message in BookmarkEditDialog component

* refactor: improve UI consistency in BookmarkNav and NewChat components

* refactor: update logger message in BookmarkEditDialog component

* refactor: Add tags prop to BookmarkForm component

* refactor: Update BookmarkForm to avoid tag mutation if the tag already exists; also close dialog on submission programmatically

* refactor: general role helper function to support updating access permissions for different permission types

* refactor: Update getLatestText function to handle undefined values in message.content

* refactor: Update useHasAccess hook to handle null role values for authenticated users

* feat: toggle bookmarks access

* refactor: Update PromptsCommand to handle access permissions for prompts

* feat: updateConversationSelector

* refactor: rename `vars` to `tagToDelete` for clarity

* fix: prevent recreation of deleted tags in BookmarkMenu on Item Click

* ci: mock updateBookmarksAccess function

* ci: mock updateBookmarksAccess function
This commit is contained in:
Danny Avila 2024-08-22 17:09:05 -04:00 committed by GitHub
parent 366e4c5adb
commit f86e9dd04c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 530 additions and 298 deletions

View file

@ -21,6 +21,7 @@ import {
useSharedLinksInfiniteQuery,
} from './queries';
import {
logger,
/* Shared Links */
addSharedLink,
deleteSharedLink,
@ -96,6 +97,7 @@ export const useUpdateConversationMutation = (
*/
export const useTagConversationMutation = (
conversationId: string,
options?: t.updateTagsInConvoOptions,
): UseMutationResult<t.TTagConversationResponse, unknown, t.TTagConversationRequest, unknown> => {
const query = useConversationTagsQuery();
const { updateTagsInConversation } = useUpdateTagsInConvo();
@ -103,13 +105,17 @@ export const useTagConversationMutation = (
(payload: t.TTagConversationRequest) =>
dataService.addTagToConversation(conversationId, payload),
{
onSuccess: (updatedTags) => {
onSuccess: (updatedTags, ...rest) => {
// Because the logic for calculating the bookmark count is complex,
// the client does not perform the calculation,
// but instead refetch the data from the API.
query.refetch();
updateTagsInConversation(conversationId, updatedTags);
options?.onSuccess?.(updatedTags, ...rest);
},
onError: options?.onError,
onMutate: options?.onMutate,
},
);
};
@ -296,62 +302,99 @@ export const useDeleteSharedLinkMutation = (
});
const current = queryClient.getQueryData<t.ConversationData>([QueryKeys.sharedLinks]);
refetch({
refetchPage: (page, index) => index === (current?.pages.length || 1) - 1,
refetchPage: (page, index) => index === (current?.pages.length ?? 1) - 1,
});
onSuccess?.(_data, vars, context);
},
...(_options || {}),
..._options,
});
};
// Add a tag or update tag information (tag, description, position, etc.)
export const useConversationTagMutation = (
tag?: string,
options?: t.UpdateConversationTagOptions,
): UseMutationResult<t.TConversationTagResponse, unknown, t.TConversationTagRequest, unknown> => {
export const useConversationTagMutation = ({
context,
tag,
options,
}: {
context: string;
tag?: string;
options?: t.UpdateConversationTagOptions;
}): UseMutationResult<t.TConversationTagResponse, unknown, t.TConversationTagRequest, unknown> => {
const queryClient = useQueryClient();
const { ..._options } = options || {};
const { onSuccess, ..._options } = options || {};
const onMutationSuccess: typeof onSuccess = (_data, vars) => {
queryClient.setQueryData<t.TConversationTag[]>([QueryKeys.conversationTags], (queryData) => {
if (!queryData) {
return [
{
count: 1,
position: 0,
tag: Constants.SAVED_TAG,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
] as t.TConversationTag[];
}
if (tag === undefined || !tag.length) {
// Check if the tag already exists
const existingTagIndex = queryData.findIndex((item) => item.tag === _data.tag);
if (existingTagIndex !== -1) {
logger.log(
'tag_mutation',
`"Created" tag exists, updating from ${context}`,
queryData,
_data,
);
// If the tag exists, update it
const updatedData = [...queryData];
updatedData[existingTagIndex] = { ...updatedData[existingTagIndex], ..._data };
return updatedData.sort((a, b) => a.position - b.position);
} else {
// If the tag doesn't exist, add it
logger.log(
'tag_mutation',
`"Created" tag is new, adding from ${context}`,
queryData,
_data,
);
return [...queryData, _data].sort((a, b) => a.position - b.position);
}
}
logger.log('tag_mutation', `Updating tag from ${context}`, queryData, _data);
return updateConversationTag(queryData, vars, _data, tag);
});
if (vars.addToConversation === true && vars.conversationId != null && _data.tag) {
const currentConvo = queryClient.getQueryData<t.TConversation>([
QueryKeys.conversation,
vars.conversationId,
]);
if (!currentConvo) {
return;
}
logger.log(
'tag_mutation',
`\`updateTagsInConversation\` Update from ${context}`,
currentConvo,
);
updateTagsInConversation(vars.conversationId, [...(currentConvo.tags || []), _data.tag]);
}
// Change the tag title to the new title
if (tag != null) {
replaceTagsInAllConversations(tag, _data.tag);
}
};
const { updateTagsInConversation, replaceTagsInAllConversations } = useUpdateTagsInConvo();
return useMutation(
(payload: t.TConversationTagRequest) =>
tag
tag != null
? dataService.updateConversationTag(tag, payload)
: dataService.createConversationTag(payload),
{
onSuccess: (_data, vars) => {
queryClient.setQueryData<t.TConversationTag[]>([QueryKeys.conversationTags], (data) => {
if (!data) {
return [
{
count: 1,
position: 0,
tag: Constants.SAVED_TAG,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
] as t.TConversationTag[];
}
if (!tag) {
return [...data, _data].sort((a, b) => a.position - b.position);
}
return updateConversationTag(data, vars, _data, tag);
});
if (vars.addToConversation && vars.conversationId && _data.tag) {
const currentConvo = queryClient.getQueryData<t.TConversation>([
QueryKeys.conversation,
vars.conversationId,
]);
if (!currentConvo) {
return;
}
updateTagsInConversation(vars.conversationId, [...(currentConvo.tags || []), _data.tag]);
}
// Change the tag title to the new title
if (tag) {
replaceTagsInAllConversations(tag, _data.tag);
}
onSuccess: (...args) => {
onMutationSuccess(...args);
onSuccess?.(...args);
},
...(_options || {}),
..._options,
},
);
};
@ -407,20 +450,22 @@ export const useDeleteConversationTagMutation = (
): UseMutationResult<t.TConversationTagResponse, unknown, string, void> => {
const queryClient = useQueryClient();
const deleteTagInAllConversations = useDeleteTagInConversations();
const { onSuccess, ..._options } = options || {};
return useMutation((tag: string) => dataService.deleteConversationTag(tag), {
onSuccess: (_data, vars, context) => {
onSuccess: (_data, tagToDelete, context) => {
queryClient.setQueryData<t.TConversationTag[]>([QueryKeys.conversationTags], (data) => {
if (!data) {
return data;
}
return data.filter((t) => t.tag !== vars);
return data.filter((t) => t.tag !== tagToDelete);
});
deleteTagInAllConversations(vars);
onSuccess?.(_data, vars, context);
deleteTagInAllConversations(tagToDelete);
onSuccess?.(_data, tagToDelete, context);
},
...(_options || {}),
..._options,
});
};
@ -817,7 +862,8 @@ export const useUpdateAssistantMutation = (
({ assistant_id, data }: { assistant_id: string; data: t.AssistantUpdateParams }) => {
const { endpoint } = data;
const endpointsConfig = queryClient.getQueryData<t.TEndpointsConfig>([QueryKeys.endpoints]);
const version = endpointsConfig?.[endpoint].version ?? defaultAssistantsVersion[endpoint];
const endpointConfig = endpointsConfig?.[endpoint];
const version = endpointConfig?.version ?? defaultAssistantsVersion[endpoint];
return dataService.updateAssistant({
data,
version,