diff --git a/Cargo.lock b/Cargo.lock index 9982d5ba0d52..d940a2818307 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3246,7 +3246,7 @@ dependencies = [ "nom_locate", "rangemap", "rayon", - "thiserror 2.0.11", + "thiserror 2.0.12", "time", "weezl", ] diff --git a/ui/desktop/src/components/BottomMenu.tsx b/ui/desktop/src/components/BottomMenu.tsx index de2d96dd348d..24639fbed74b 100644 --- a/ui/desktop/src/components/BottomMenu.tsx +++ b/ui/desktop/src/components/BottomMenu.tsx @@ -6,6 +6,7 @@ import { ModelRadioList } from './settings/models/ModelRadioList'; import { Document, ChevronUp, ChevronDown } from './icons'; import type { View } from '../ChatWindow'; import { getApiUrl, getSecretKey } from '../config'; +import { BottomMenuModeSelection } from './BottomMenuModeSelection'; export default function BottomMenu({ hasMessages, @@ -15,11 +16,14 @@ export default function BottomMenu({ setView: (view: View) => void; }) { const [isModelMenuOpen, setIsModelMenuOpen] = useState(false); - const [gooseMode, setGooseMode] = useState('auto'); const { currentModel } = useModel(); const { recentModels } = useRecentModels(); // Get recent models const dropdownRef = useRef(null); + const [isGooseModeMenuOpen, setIsGooseModeMenuOpen] = useState(false); + const [gooseMode, setGooseMode] = useState('auto'); + const gooseModeDropdownRef = useRef(null); + // Add effect to handle clicks outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -79,6 +83,41 @@ export default function BottomMenu({ }; }, [isModelMenuOpen]); + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + gooseModeDropdownRef.current && + !gooseModeDropdownRef.current.contains(event.target as Node) + ) { + setIsGooseModeMenuOpen(false); + } + }; + + if (isGooseModeMenuOpen) { + document.addEventListener('mousedown', handleClickOutside); + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isGooseModeMenuOpen]); + + useEffect(() => { + const handleEsc = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + setIsGooseModeMenuOpen(false); + } + }; + + if (isGooseModeMenuOpen) { + window.addEventListener('keydown', handleEsc); + } + + return () => { + window.removeEventListener('keydown', handleEsc); + }; + }, [isGooseModeMenuOpen]); + let envModelProvider = null; if (window.electron.getConfig().GOOSE_MODEL && window.electron.getConfig().GOOSE_PROVIDER) { envModelProvider = `${window.electron.getConfig().GOOSE_MODEL} - ${window.electron.getConfig().GOOSE_PROVIDER}`; @@ -90,7 +129,6 @@ export default function BottomMenu({ { - console.log('Opening directory chooser'); if (hasMessages) { window.electron.directoryChooser(); } else { @@ -103,15 +141,24 @@ export default function BottomMenu({ -
+ {/* Goose Mode Selector Dropdown */} +
{ - setView('settings'); - }} + onClick={() => setIsGooseModeMenuOpen(!isGooseModeMenuOpen)} > Goose Mode: {gooseMode} + {isGooseModeMenuOpen ? ( + + ) : ( + + )}
+ + {/* Dropdown Menu */} + {isGooseModeMenuOpen && ( + + )}
{/* Model Selector Dropdown - Only in development */} diff --git a/ui/desktop/src/components/BottomMenuModeSelection.tsx b/ui/desktop/src/components/BottomMenuModeSelection.tsx new file mode 100644 index 000000000000..c442e2b85a4f --- /dev/null +++ b/ui/desktop/src/components/BottomMenuModeSelection.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { getApiUrl, getSecretKey } from '../config'; + +export const BottomMenuModeSelection = ({ selectedMode, setSelectedMode }) => { + const modes = [ + { + value: 'auto', + }, + { + value: 'approve', + }, + { + value: 'chat', + }, + ]; + + const handleModeChange = async (newMode: string) => { + const storeResponse = await fetch(getApiUrl('/configs/store'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Secret-Key': getSecretKey(), + }, + body: JSON.stringify({ + key: 'GOOSE_MODE', + value: newMode, + isSecret: false, + }), + }); + + if (!storeResponse.ok) { + const errorText = await storeResponse.text(); + console.error('Store response error:', errorText); + throw new Error(`Failed to store new goose mode: ${newMode}`); + } + setSelectedMode(newMode); + }; + + return ( +
+
+ {modes.map((mode) => ( + + ))} +
+
+ ); +};