feat: use dropdown for goose selection (#1531)

This commit is contained in:
Yingjie He
2025-03-05 10:31:52 -08:00
committed by GitHub
parent 667c88f773
commit 89fc871e9c
3 changed files with 127 additions and 7 deletions
Generated
+1 -1
View File
@@ -3246,7 +3246,7 @@ dependencies = [
"nom_locate",
"rangemap",
"rayon",
"thiserror 2.0.11",
"thiserror 2.0.12",
"time",
"weezl",
]
+53 -6
View File
@@ -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<HTMLDivElement>(null);
const [isGooseModeMenuOpen, setIsGooseModeMenuOpen] = useState(false);
const [gooseMode, setGooseMode] = useState('auto');
const gooseModeDropdownRef = useRef<HTMLDivElement>(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({
<span
className="cursor-pointer flex items-center [&>svg]:size-4"
onClick={async () => {
console.log('Opening directory chooser');
if (hasMessages) {
window.electron.directoryChooser();
} else {
@@ -103,15 +141,24 @@ export default function BottomMenu({
<ChevronUp className="ml-1" />
</span>
<div className="relative flex items-center ml-6">
{/* Goose Mode Selector Dropdown */}
<div className="relative flex items-center ml-6" ref={gooseModeDropdownRef}>
<div
className="flex items-center cursor-pointer"
onClick={() => {
setView('settings');
}}
onClick={() => setIsGooseModeMenuOpen(!isGooseModeMenuOpen)}
>
<span>Goose Mode: {gooseMode}</span>
{isGooseModeMenuOpen ? (
<ChevronDown className="w-4 h-4 ml-1" />
) : (
<ChevronUp className="w-4 h-4 ml-1" />
)}
</div>
{/* Dropdown Menu */}
{isGooseModeMenuOpen && (
<BottomMenuModeSelection selectedMode={gooseMode} setSelectedMode={setGooseMode} />
)}
</div>
{/* Model Selector Dropdown - Only in development */}
@@ -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 (
<div className="absolute bottom-[24px] right-0 w-[120px] bg-bgApp rounded-lg border border-borderSubtle">
<div>
{modes.map((mode) => (
<label key={mode.value} className="block cursor-pointer">
<div
className="flex items-center justify-between p-2 text-textStandard hover:bg-bgSubtle transition-colors"
onClick={() => handleModeChange(mode.value)}
>
<div>
<p className="text-sm">{mode.value}</p>
</div>
<div className="relative">
<input
type="radio"
name="modes"
value={mode.value}
checked={selectedMode === mode.value}
onChange={() => handleModeChange(mode.value)}
className="peer sr-only"
/>
<div
className="h-4 w-4 rounded-full border border-gray-400 dark:border-gray-500
peer-checked:border-[6px] peer-checked:border-black dark:peer-checked:border-white
peer-checked:bg-white dark:peer-checked:bg-black
transition-all duration-200 ease-in-out"
></div>
</div>
</div>
</label>
))}
</div>
</div>
);
};