From 9eb62370a45237cbd89e38d6a6f0a785644704a8 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Sun, 22 Jun 2025 11:45:15 -0400 Subject: [PATCH] refactor: update CheckboxButton to support controlled state and enhance ToolsDropdown with permission-based toggles for web search and code interpreter --- .../components/Chat/Input/CodeInterpreter.tsx | 2 +- .../components/Chat/Input/ToolsDropdown.tsx | 99 +++++++++++-------- .../src/components/Chat/Input/WebSearch.tsx | 2 +- client/src/components/ui/CheckboxButton.tsx | 21 ++-- client/src/hooks/Plugins/useToolToggle.ts | 11 ++- 5 files changed, 84 insertions(+), 51 deletions(-) diff --git a/client/src/components/Chat/Input/CodeInterpreter.tsx b/client/src/components/Chat/Input/CodeInterpreter.tsx index 7a06887dd2..a2667ed2b9 100644 --- a/client/src/components/Chat/Input/CodeInterpreter.tsx +++ b/client/src/components/Chat/Input/CodeInterpreter.tsx @@ -29,7 +29,7 @@ function CodeInterpreter() { { const localize = useLocalize(); - const { conversationId } = useBadgeRowContext(); + const { webSearch, codeInterpreter } = useBadgeRowContext(); const isDisabled = disabled ?? false; const [isPopoverActive, setIsPopoverActive] = useState(false); + const canUseWebSearch = useHasAccess({ + permissionType: PermissionTypes.WEB_SEARCH, + permission: Permissions.USE, + }); + + const canRunCode = useHasAccess({ + permissionType: PermissionTypes.RUN_CODE, + permission: Permissions.USE, + }); + + const handleWebSearchToggle = useCallback(() => { + const newValue = !webSearch.toggleState; + webSearch.debouncedChange({ isChecked: newValue }); + }, [webSearch]); + + const handleCodeInterpreterToggle = useCallback(() => { + const newValue = !codeInterpreter.toggleState; + codeInterpreter.debouncedChange({ isChecked: newValue }); + }, [codeInterpreter]); + const dropdownItems = useMemo(() => { - return [ + const items: MenuItemProps[] = [ { render: () => (
@@ -26,41 +48,40 @@ const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => { ), hideOnClick: false, }, - { - label: 'Search connectors', - onClick: () => { - // TODO: Implement search connectors functionality - console.log('Search connectors clicked'); - }, - icon: , - badge: 'NEW', - }, - { - label: 'Create an image', - onClick: () => { - // TODO: Implement create image functionality - console.log('Create an image clicked'); - }, - icon: , - }, - { - label: 'Search the web', - onClick: () => { - // TODO: Implement web search functionality - console.log('Search the web clicked'); - }, - icon: , - }, - { - label: 'Write or code', - onClick: () => { - // TODO: Implement write or code functionality - console.log('Write or code clicked'); - }, - icon: , - }, ]; - }, []); + + if (canUseWebSearch) { + items.push({ + onClick: handleWebSearchToggle, + hideOnClick: true, + render: (props) => ( +
+
+ + {localize('com_ui_web_search')} +
+
+ ), + }); + } + + if (canRunCode) { + items.push({ + onClick: handleCodeInterpreterToggle, + hideOnClick: true, + render: (props) => ( +
+
+ + {localize('com_assistants_code_interpreter')} +
+
+ ), + }); + } + + return items; + }, [canUseWebSearch, canRunCode, localize, handleWebSearchToggle, handleCodeInterpreterToggle]); const menuTrigger = ( , isChecked: boolean) => void; + setValue?: (values: { e?: React.ChangeEvent; isChecked: boolean }) => void; } ->(({ icon, label, setValue, className, defaultChecked, isCheckedClassName }, ref) => { +>(({ icon, label, setValue, className, checked, defaultChecked, isCheckedClassName }, ref) => { const checkbox = useCheckboxStore(); const isChecked = useStoreState(checkbox, (state) => state?.value); const onChange = (e: React.ChangeEvent) => { @@ -21,20 +22,28 @@ const CheckboxButton = React.forwardRef< if (typeof isChecked !== 'boolean') { return; } - setValue?.(e, !isChecked); + setValue?.({ e, isChecked: !isChecked }); }; + + // Sync with controlled checked prop useEffect(() => { - if (defaultChecked) { + if (checked !== undefined) { + checkbox.setValue(checked); + } + }, [checked, checkbox]); + + // Set initial value from defaultChecked + useEffect(() => { + if (defaultChecked !== undefined && checked === undefined) { checkbox.setValue(defaultChecked); } - }, [defaultChecked, checkbox]); + }, [defaultChecked, checked, checkbox]); return ( + externalIsAuthenticated ?? (authConfig ? (authQuery?.data?.authenticated ?? false) : false), + [externalIsAuthenticated, authConfig, authQuery.data?.authenticated], + ); const isToolEnabled = useMemo(() => { return ephemeralAgent?.[toolKey] ?? false; @@ -72,10 +75,10 @@ export function useToolToggle({ ); const handleChange = useCallback( - (e: React.ChangeEvent, isChecked: boolean) => { + ({ e, isChecked }: { e?: React.ChangeEvent; isChecked: boolean }) => { if (isAuthenticated !== undefined && !isAuthenticated && setIsDialogOpen) { setIsDialogOpen(true); - e.preventDefault(); + e?.preventDefault?.(); return; } setToggleState(isChecked);