🔖 fix: Remove Local State from Bookmark Menu (#5181)

* chore: remove redundant

* fix: bookmark menu statefulness by removing local state
This commit is contained in:
Danny Avila 2025-01-04 12:01:13 -05:00 committed by GitHub
parent 7c61115a88
commit 766657da83
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 24 additions and 26 deletions

View file

@ -2,7 +2,7 @@ import { useRecoilState } from 'recoil';
import { Settings2 } from 'lucide-react'; import { Settings2 } from 'lucide-react';
import { Root, Anchor } from '@radix-ui/react-popover'; import { Root, Anchor } from '@radix-ui/react-popover';
import { useState, useEffect, useMemo } from 'react'; import { useState, useEffect, useMemo } from 'react';
import { tPresetUpdateSchema, EModelEndpoint, isParamEndpoint } from 'librechat-data-provider'; import { tConvoUpdateSchema, EModelEndpoint, isParamEndpoint } from 'librechat-data-provider';
import type { TPreset, TInterfaceConfig } from 'librechat-data-provider'; import type { TPreset, TInterfaceConfig } from 'librechat-data-provider';
import { EndpointSettings, SaveAsPresetDialog, AlternativeSettings } from '~/components/Endpoints'; import { EndpointSettings, SaveAsPresetDialog, AlternativeSettings } from '~/components/Endpoints';
import { PluginStoreDialog, TooltipAnchor } from '~/components'; import { PluginStoreDialog, TooltipAnchor } from '~/components';
@ -123,7 +123,7 @@ export default function HeaderOptions({
open={saveAsDialogShow} open={saveAsDialogShow}
onOpenChange={setSaveAsDialogShow} onOpenChange={setSaveAsDialogShow}
preset={ preset={
tPresetUpdateSchema.parse({ tConvoUpdateSchema.parse({
...conversation, ...conversation,
}) as TPreset }) as TPreset
} }

View file

@ -27,15 +27,14 @@ const BookmarkMenu: FC = () => {
const conversation = useRecoilValue(store.conversationByIndex(0)) || undefined; const conversation = useRecoilValue(store.conversationByIndex(0)) || undefined;
const conversationId = conversation?.conversationId ?? ''; const conversationId = conversation?.conversationId ?? '';
const updateConvoTags = useBookmarkSuccess(conversationId); const updateConvoTags = useBookmarkSuccess(conversationId);
const tags = conversation?.tags;
const menuId = useId(); const menuId = useId();
const [isMenuOpen, setIsMenuOpen] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false);
const [isDialogOpen, setIsDialogOpen] = useState(false); const [isDialogOpen, setIsDialogOpen] = useState(false);
const [tags, setTags] = useState<string[]>(conversation?.tags || []);
const mutation = useTagConversationMutation(conversationId, { const mutation = useTagConversationMutation(conversationId, {
onSuccess: (newTags: string[], vars) => { onSuccess: (newTags: string[], vars) => {
setTags(newTags);
updateConvoTags(newTags); updateConvoTags(newTags);
const tagElement = document.getElementById(vars.tag); const tagElement = document.getElementById(vars.tag);
console.log('tagElement', tagElement); console.log('tagElement', tagElement);
@ -82,12 +81,13 @@ const BookmarkMenu: FC = () => {
const allTags = const allTags =
queryClient.getQueryData<TConversationTag[]>([QueryKeys.conversationTags]) ?? []; queryClient.getQueryData<TConversationTag[]>([QueryKeys.conversationTags]) ?? [];
const existingTags = allTags.map((t) => t.tag); const existingTags = allTags.map((t) => t.tag);
const filteredTags = tags.filter((t) => existingTags.includes(t)); const filteredTags = tags?.filter((t) => existingTags.includes(t));
logger.log('tag_mutation', 'BookmarkMenu - handleSubmit: tags after filtering', filteredTags); logger.log('tag_mutation', 'BookmarkMenu - handleSubmit: tags after filtering', filteredTags);
const newTags = filteredTags.includes(tag) const newTags =
? filteredTags.filter((t) => t !== tag) filteredTags?.includes(tag) === true
: [...filteredTags, tag]; ? filteredTags.filter((t) => t !== tag)
: [...(filteredTags ?? []), tag];
logger.log('tag_mutation', 'BookmarkMenu - handleSubmit: tags after', newTags); logger.log('tag_mutation', 'BookmarkMenu - handleSubmit: tags after', newTags);
mutation.mutate({ mutation.mutate({
@ -115,16 +115,17 @@ const BookmarkMenu: FC = () => {
if (data) { if (data) {
for (const tag of data) { for (const tag of data) {
const isSelected = tags.includes(tag.tag); const isSelected = tags?.includes(tag.tag);
items.push({ items.push({
id: tag.tag, id: tag.tag,
hideOnClick: false,
label: tag.tag, label: tag.tag,
icon: isSelected ? ( hideOnClick: false,
<BookmarkFilledIcon className="size-4" /> icon:
) : ( isSelected === true ? (
<BookmarkIcon className="size-4" /> <BookmarkFilledIcon className="size-4" />
), ) : (
<BookmarkIcon className="size-4" />
),
onClick: () => handleSubmit(tag.tag), onClick: () => handleSubmit(tag.tag),
disabled: mutation.isLoading, disabled: mutation.isLoading,
}); });
@ -142,7 +143,7 @@ const BookmarkMenu: FC = () => {
if (mutation.isLoading) { if (mutation.isLoading) {
return <Spinner aria-label="Spinner" />; return <Spinner aria-label="Spinner" />;
} }
if (tags.length > 0) { if ((tags?.length ?? 0) > 0) {
return <BookmarkFilledIcon className="icon-sm" aria-label="Filled Bookmark" />; return <BookmarkFilledIcon className="icon-sm" aria-label="Filled Bookmark" />;
} }
return <BookmarkIcon className="icon-sm" aria-label="Bookmark" />; return <BookmarkIcon className="icon-sm" aria-label="Bookmark" />;
@ -155,6 +156,7 @@ const BookmarkMenu: FC = () => {
menuId={menuId} menuId={menuId}
isOpen={isMenuOpen} isOpen={isMenuOpen}
setIsOpen={setIsMenuOpen} setIsOpen={setIsMenuOpen}
keyPrefix={`${conversationId}-bookmark-`}
trigger={ trigger={
<TooltipAnchor <TooltipAnchor
description={localize('com_ui_bookmarks_add')} description={localize('com_ui_bookmarks_add')}
@ -177,8 +179,8 @@ const BookmarkMenu: FC = () => {
/> />
<BookmarkEditDialog <BookmarkEditDialog
tags={tags} tags={tags}
setTags={setTags}
open={isDialogOpen} open={isDialogOpen}
setTags={updateConvoTags}
setOpen={setIsDialogOpen} setOpen={setIsDialogOpen}
triggerRef={newBookmarkRef} triggerRef={newBookmarkRef}
conversationId={conversationId} conversationId={conversationId}

View file

@ -1,6 +1,6 @@
import React, { useMemo, useState, useEffect, useCallback } from 'react'; import React, { useMemo, useState, useEffect, useCallback } from 'react';
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query'; import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
import { getSettingsKeys, tPresetUpdateSchema } from 'librechat-data-provider'; import { getSettingsKeys, tConvoUpdateSchema } from 'librechat-data-provider';
import type { TPreset } from 'librechat-data-provider'; import type { TPreset } from 'librechat-data-provider';
import { SaveAsPresetDialog } from '~/components/Endpoints'; import { SaveAsPresetDialog } from '~/components/Endpoints';
import { useSetIndexOptions, useLocalize } from '~/hooks'; import { useSetIndexOptions, useLocalize } from '~/hooks';
@ -106,7 +106,7 @@ export default function Parameters() {
}, [parameters, setConversation]); }, [parameters, setConversation]);
const openDialog = useCallback(() => { const openDialog = useCallback(() => {
const newPreset = tPresetUpdateSchema.parse({ const newPreset = tConvoUpdateSchema.parse({
...conversation, ...conversation,
}) as TPreset; }) as TPreset;
setPreset(newPreset); setPreset(newPreset);

View file

@ -4,6 +4,7 @@ import type * as t from '~/common';
import { cn } from '~/utils'; import { cn } from '~/utils';
interface DropdownProps { interface DropdownProps {
keyPrefix?: string;
trigger: React.ReactNode; trigger: React.ReactNode;
items: t.MenuItemProps[]; items: t.MenuItemProps[];
isOpen: boolean; isOpen: boolean;
@ -20,6 +21,7 @@ interface DropdownProps {
} }
const DropdownPopup: React.FC<DropdownProps> = ({ const DropdownPopup: React.FC<DropdownProps> = ({
keyPrefix,
trigger, trigger,
items, items,
isOpen, isOpen,
@ -53,7 +55,7 @@ const DropdownPopup: React.FC<DropdownProps> = ({
} }
return ( return (
<Ariakit.MenuItem <Ariakit.MenuItem
key={index} key={`${keyPrefix ?? ''}${index}`}
id={item.id} id={item.id}
className={cn( className={cn(
'group flex w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-3.5 text-sm text-text-primary outline-none transition-colors duration-200 hover:bg-surface-hover focus:bg-surface-hover md:px-2.5 md:py-2', 'group flex w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-3.5 text-sm text-text-primary outline-none transition-colors duration-200 hover:bg-surface-hover focus:bg-surface-hover md:px-2.5 md:py-2',

View file

@ -630,12 +630,6 @@ export const tConvoUpdateSchema = tConversationSchema.merge(
}), }),
); );
export const tPresetUpdateSchema = tConversationSchema.merge(
z.object({
endpoint: extendedModelEndpointSchema.nullable(),
}),
);
export type TPreset = z.infer<typeof tPresetSchema>; export type TPreset = z.infer<typeof tPresetSchema>;
export type TSetOption = ( export type TSetOption = (